mirror of
https://github.com/bykof/cordova-plugin-webserver.git
synced 2026-04-20 00:02:45 +08:00
Merge branch 'master' of https://github.com/bykof/cordova-plugin-webserver
This commit is contained in:
@@ -21,6 +21,10 @@ import java.util.UUID;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class NanoHTTPDWebserver extends NanoHTTPD {
|
||||
|
||||
Webserver webserver;
|
||||
@@ -81,6 +85,112 @@ public class NanoHTTPDWebserver extends NanoHTTPD {
|
||||
}
|
||||
}
|
||||
|
||||
private Response newFixedFileResponse(File file, String mime) throws FileNotFoundException {
|
||||
Response res;
|
||||
res = newFixedLengthResponse(Response.Status.OK, mime, new FileInputStream(file), (int) file.length());
|
||||
res.addHeader("Accept-Ranges", "bytes");
|
||||
return res;
|
||||
}
|
||||
|
||||
Response serveFile(Map<String, String> header, File file, String mime) {
|
||||
Response res;
|
||||
try {
|
||||
// Calculate etag
|
||||
String etag = Integer.toHexString((file.getAbsolutePath() + file.lastModified() + "" + file.length()).hashCode());
|
||||
|
||||
// Support (simple) skipping:
|
||||
long startFrom = 0;
|
||||
long endAt = -1;
|
||||
String range = header.get("range");
|
||||
if (range != null) {
|
||||
if (range.startsWith("bytes=")) {
|
||||
range = range.substring("bytes=".length());
|
||||
int minus = range.indexOf('-');
|
||||
try {
|
||||
if (minus > 0) {
|
||||
startFrom = Long.parseLong(range.substring(0, minus));
|
||||
endAt = Long.parseLong(range.substring(minus + 1));
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get if-range header. If present, it must match etag or else we
|
||||
// should ignore the range request
|
||||
String ifRange = header.get("if-range");
|
||||
boolean headerIfRangeMissingOrMatching = (ifRange == null || etag.equals(ifRange));
|
||||
|
||||
String ifNoneMatch = header.get("if-none-match");
|
||||
boolean headerIfNoneMatchPresentAndMatching = ifNoneMatch != null && ("*".equals(ifNoneMatch) || ifNoneMatch.equals(etag));
|
||||
|
||||
// Change return code and add Content-Range header when skipping is
|
||||
// requested
|
||||
long fileLen = file.length();
|
||||
|
||||
if (headerIfRangeMissingOrMatching && range != null && startFrom >= 0 && startFrom < fileLen) {
|
||||
// range request that matches current etag
|
||||
// and the startFrom of the range is satisfiable
|
||||
if (headerIfNoneMatchPresentAndMatching) {
|
||||
// range request that matches current etag
|
||||
// and the startFrom of the range is satisfiable
|
||||
// would return range from file
|
||||
// respond with not-modified
|
||||
res = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");
|
||||
res.addHeader("ETag", etag);
|
||||
} else {
|
||||
if (endAt < 0) {
|
||||
endAt = fileLen - 1;
|
||||
}
|
||||
long newLen = endAt - startFrom + 1;
|
||||
if (newLen < 0) {
|
||||
newLen = 0;
|
||||
}
|
||||
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
fis.skip(startFrom);
|
||||
|
||||
res = newFixedLengthResponse(Response.Status.PARTIAL_CONTENT, mime, fis, newLen);
|
||||
res.addHeader("Accept-Ranges", "bytes");
|
||||
res.addHeader("Content-Length", "" + newLen);
|
||||
res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
|
||||
res.addHeader("ETag", etag);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (headerIfRangeMissingOrMatching && range != null && startFrom >= fileLen) {
|
||||
// return the size of the file
|
||||
// 4xx responses are not trumped by if-none-match
|
||||
res = newFixedLengthResponse(Response.Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, "");
|
||||
res.addHeader("Content-Range", "bytes */" + fileLen);
|
||||
res.addHeader("ETag", etag);
|
||||
} else if (range == null && headerIfNoneMatchPresentAndMatching) {
|
||||
// full-file-fetch request
|
||||
// would return entire file
|
||||
// respond with not-modified
|
||||
res = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");
|
||||
res.addHeader("ETag", etag);
|
||||
} else if (!headerIfRangeMissingOrMatching && headerIfNoneMatchPresentAndMatching) {
|
||||
// range request that doesn't match current etag
|
||||
// would return entire (different) file
|
||||
// respond with not-modified
|
||||
|
||||
res = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");
|
||||
res.addHeader("ETag", etag);
|
||||
} else {
|
||||
// supply the file
|
||||
res = newFixedFileResponse(file, mime);
|
||||
res.addHeader("Content-Length", "" + fileLen);
|
||||
res.addHeader("ETag", etag);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
res = newFixedLengthResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: Reading file failed.");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response serve(IHTTPSession session) {
|
||||
Log.d(this.getClass().getName(), "New request is incoming!");
|
||||
@@ -106,28 +216,40 @@ public class NanoHTTPDWebserver extends NanoHTTPD {
|
||||
}
|
||||
|
||||
JSONObject responseObject = (JSONObject) this.webserver.responses.get(requestUUID);
|
||||
Log.d(this.getClass().getName(), "responseObject: " + responseObject.toString());
|
||||
Response response = null;
|
||||
Log.d(this.getClass().getName(), "responseObject: " + responseObject.toString());
|
||||
|
||||
try {
|
||||
response = newFixedLengthResponse(
|
||||
Response.Status.lookup(responseObject.getInt("status")),
|
||||
getContentType(responseObject),
|
||||
responseObject.getString("body")
|
||||
);
|
||||
|
||||
Iterator<?> keys = responseObject.getJSONObject("headers").keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = (String) keys.next();
|
||||
response.addHeader(
|
||||
key,
|
||||
responseObject.getJSONObject("headers").getString(key)
|
||||
);
|
||||
if (responseObject.has("path")) {
|
||||
// TODO should specify a more correct mime-type
|
||||
try {
|
||||
return serveFile(session.getHeaders(), new File(responseObject.getString("path")), responseObject.getString("type"));
|
||||
}
|
||||
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
response = newFixedLengthResponse(
|
||||
Response.Status.lookup(responseObject.getInt("status")),
|
||||
getContentType(responseObject),
|
||||
responseObject.getString("body")
|
||||
);
|
||||
|
||||
Iterator<?> keys = responseObject.getJSONObject("headers").keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = (String) keys.next();
|
||||
response.addHeader(
|
||||
key,
|
||||
responseObject.getJSONObject("headers").getString(key)
|
||||
);
|
||||
}
|
||||
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
public class SynchronizedDictionary<KeyType:Hashable, ValueType> {
|
||||
private var dictionary: [KeyType:ValueType] = [:]
|
||||
private let accessQueue = DispatchQueue(label: "SynchronizedDictionaryAccess", attributes: .concurrent)
|
||||
|
||||
public func removeValue(forKey: KeyType) {
|
||||
self.accessQueue.async(flags:.barrier) {
|
||||
self.dictionary.removeValue(forKey: forKey)
|
||||
}
|
||||
}
|
||||
|
||||
public subscript(key: KeyType) -> ValueType? {
|
||||
set {
|
||||
self.accessQueue.async(flags:.barrier) {
|
||||
self.dictionary[key] = newValue
|
||||
}
|
||||
}
|
||||
get {
|
||||
var element: ValueType?
|
||||
self.accessQueue.sync {
|
||||
element = self.dictionary[key]
|
||||
}
|
||||
return element
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
let TIMEOUT: Int = 60 * 3 * 1000000
|
||||
|
||||
var webServer: GCDWebServer = GCDWebServer()
|
||||
var responses: Dictionary<String, Any> = [:]
|
||||
var responses = SynchronizedDictionary<AnyHashable,Any?>()
|
||||
var onRequestCommand: CDVInvokedUrlCommand? = nil
|
||||
|
||||
override func pluginInitialize() {
|
||||
self.webServer = GCDWebServer()
|
||||
self.onRequestCommand = nil
|
||||
self.responses = [:]
|
||||
self.responses = SynchronizedDictionary<AnyHashable,Any?>()
|
||||
self.initHTTPRequestHandlers()
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
response?.setValue(value, forAdditionalHeader: key)
|
||||
}
|
||||
|
||||
// Remove the handled response
|
||||
self.responses.removeValue(forKey: requestUUID)
|
||||
|
||||
// Complete the async response
|
||||
completionBlock(response!)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user