From 60f55066cee7b11f902ca4a1259ff0021649411c Mon Sep 17 00:00:00 2001 From: Keith Wait Date: Sat, 11 Aug 2018 21:48:30 -0500 Subject: [PATCH 1/5] add file responses --- src/android/NanoHTTPDWebserver.java | 106 +++++++++++++++++++++++++++- src/android/Webserver.java | 9 +++ src/www/webserver.js | 16 +++++ webserver.js | 6 ++ 4 files changed, 136 insertions(+), 1 deletion(-) diff --git a/src/android/NanoHTTPDWebserver.java b/src/android/NanoHTTPDWebserver.java index b74ce22..858a40e 100644 --- a/src/android/NanoHTTPDWebserver.java +++ b/src/android/NanoHTTPDWebserver.java @@ -81,6 +81,105 @@ public class NanoHTTPDWebserver extends NanoHTTPD { } } + Response serveFile(Map 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(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 = Response.newFixedLengthResponse(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(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(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(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 = getForbiddenResponse("Reading file failed."); + } + + return res; + } + @Override public Response serve(IHTTPSession session) { Log.d(this.getClass().getName(), "New request is incoming!"); @@ -97,7 +196,7 @@ public class NanoHTTPDWebserver extends NanoHTTPD { pluginResult.setKeepCallback(true); this.webserver.onRequestCallbackContext.sendPluginResult(pluginResult); - while (!this.webserver.responses.containsKey(requestUUID)) { + while (!this.webserver.responses.containsKey(requestUUID) && !this.webserver.responses.containsKey('file')) { try { Thread.sleep(1); } catch (InterruptedException e) { @@ -105,6 +204,11 @@ public class NanoHTTPDWebserver extends NanoHTTPD { } } + if (this.webserver.responses.containsKey('file')) { + // TODO should specify a more correct mime-type + return serveFile(session.getHeaders(), new File((String) this.webserver.responses.get('file')), 'application/octet-stream'); + } + JSONObject responseObject = (JSONObject) this.webserver.responses.get(requestUUID); Log.d(this.getClass().getName(), "responseObject: " + responseObject.toString()); Response response = null; diff --git a/src/android/Webserver.java b/src/android/Webserver.java index cf82547..812825a 100644 --- a/src/android/Webserver.java +++ b/src/android/Webserver.java @@ -44,6 +44,10 @@ public class Webserver extends CordovaPlugin { this.sendResponse(args, callbackContext); return true; } + else if ("sendFileResponse".equals(action)) { + this.sendFileResponse(args, callbackContext); + return true; + } return false; // Returning false results in a "MethodNotFound" error. } @@ -96,6 +100,11 @@ public class Webserver extends CordovaPlugin { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); } + private void sendFileResponse(JSONArray args, CallbackContext callbackContext) throws JSONException { + this.responses.put('file', args.get(1)); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); + } + /** * Just register the onRequest and send no result. This is needed to save the callbackContext to * invoke it later diff --git a/src/www/webserver.js b/src/www/webserver.js index a1d2332..732e20c 100644 --- a/src/www/webserver.js +++ b/src/www/webserver.js @@ -4,6 +4,7 @@ const WEBSERVER_CLASS = 'Webserver'; const START_FUNCTION = 'start'; const ONREQUEST_FUNCTION = 'onRequest'; const SENDRESPONSE_FUNCION = 'sendResponse'; +const SENDFILE_FUNCION = 'sendFileResponse'; const STOP_FUNCTION = 'stop'; export function start(success_callback, error_callback, port) { @@ -45,6 +46,21 @@ export function sendResponse( ); } +export function sendFile( + requestId, + params, + success_callback, + error_callback +) { + exec( + success_callback, + error_callback, + WEBSERVER_CLASS, + SENDFILE_FUNCION, + [requestId, params] + ); +} + export function stop(success_callback, error_callback) { exec( success_callback, diff --git a/webserver.js b/webserver.js index 9ac93d6..720ac86 100644 --- a/webserver.js +++ b/webserver.js @@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { exports.start = start; exports.onRequest = onRequest; exports.sendResponse = sendResponse; +exports.sendFile = sendFile; exports.stop = stop; var _exec = require('cordova/exec'); @@ -18,6 +19,7 @@ var WEBSERVER_CLASS = 'Webserver'; var START_FUNCTION = 'start'; var ONREQUEST_FUNCTION = 'onRequest'; var SENDRESPONSE_FUNCION = 'sendResponse'; +var SENDFILE_FUNCION = 'sendFileResponse'; var STOP_FUNCTION = 'stop'; function start(success_callback, error_callback, port) { @@ -38,6 +40,10 @@ function sendResponse(requestId, params, success_callback, error_callback) { (0, _exec2.default)(success_callback, error_callback, WEBSERVER_CLASS, SENDRESPONSE_FUNCION, [requestId, params]); } +function sendFile(requestId, params, success_callback, error_callback) { + (0, _exec2.default)(success_callback, error_callback, WEBSERVER_CLASS, SENDFILE_FUNCION, [requestId, params]); +} + function stop(success_callback, error_callback) { (0, _exec2.default)(success_callback, error_callback, WEBSERVER_CLASS, STOP_FUNCTION, []); } From 29ab495aadd22c46af1501cc9eb620a0bf6a43cd Mon Sep 17 00:00:00 2001 From: Szabolcs Balogh Date: Tue, 21 Aug 2018 11:57:01 +0300 Subject: [PATCH 2/5] iOS: clean up by removing the handled response --- plugin.xml | 1 + src/ios/SynchronizedDictionary.swift | 25 +++++++++++++++++++++++++ src/ios/Webserver.swift | 7 +++++-- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/ios/SynchronizedDictionary.swift diff --git a/plugin.xml b/plugin.xml index 9f78762..fc3d83e 100644 --- a/plugin.xml +++ b/plugin.xml @@ -28,6 +28,7 @@ + diff --git a/src/ios/SynchronizedDictionary.swift b/src/ios/SynchronizedDictionary.swift new file mode 100644 index 0000000..6bfdbf2 --- /dev/null +++ b/src/ios/SynchronizedDictionary.swift @@ -0,0 +1,25 @@ +public class SynchronizedDictionary { + 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 + } + } +} diff --git a/src/ios/Webserver.swift b/src/ios/Webserver.swift index 88e7c77..be25517 100644 --- a/src/ios/Webserver.swift +++ b/src/ios/Webserver.swift @@ -3,13 +3,13 @@ let TIMEOUT: Int = 60 * 3 * 1000000 var webServer: GCDWebServer = GCDWebServer() - var responses: Dictionary = [:] + var responses = SynchronizedDictionary() var onRequestCommand: CDVInvokedUrlCommand? = nil override func pluginInitialize() { self.webServer = GCDWebServer() self.onRequestCommand = nil - self.responses = [:] + self.responses = SynchronizedDictionary() 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!) } From 128933f72126af6a11ed6f67422066f1638d7216 Mon Sep 17 00:00:00 2001 From: Keith Wait Date: Mon, 3 Sep 2018 10:55:14 -0500 Subject: [PATCH 3/5] correct implementation errors and readme text --- README.md | 13 ++++++++- src/android/NanoHTTPDWebserver.java | 44 +++++++++++++++++++++-------- src/android/Webserver.java | 3 +- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a9cc31a..fa8fdb3 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,18 @@ The params have to look like this (there are not default values for the params!) } ``` +### sendFileResponse(responseObject, callbackSuccess, callbackError) (currently Android-only support) + +This method allows sending file content in response to an http request. It is intended that this would be used in conjunction with cordova-plugin-file to locate the path of the file data to be sent. + +The response object should look like this. Here, the provided path should be accessible by your cordova app and the type should be the mime type of the file. Note that the MIME type of the file can be found from the [.type property of the File object](https://developer.mozilla.org/en-US/docs/Web/API/File). +``` +{ + path: '/sdcard0/Downloads/whatever.txt', + type: 'text/plain' +} +``` + ## Example ```javascript @@ -106,4 +118,3 @@ Special thanks to: - https://github.com/NanoHttpd/nanohttpd - https://github.com/swisspol/GCDWebServer - diff --git a/src/android/NanoHTTPDWebserver.java b/src/android/NanoHTTPDWebserver.java index 858a40e..b6309b7 100644 --- a/src/android/NanoHTTPDWebserver.java +++ b/src/android/NanoHTTPDWebserver.java @@ -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,13 @@ 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 header, File file, String mime) { Response res; try { @@ -125,7 +136,7 @@ public class NanoHTTPDWebserver extends NanoHTTPD { // and the startFrom of the range is satisfiable // would return range from file // respond with not-modified - res = newFixedLengthResponse(Status.NOT_MODIFIED, mime, ""); + res = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, ""); res.addHeader("ETag", etag); } else { if (endAt < 0) { @@ -139,7 +150,7 @@ public class NanoHTTPDWebserver extends NanoHTTPD { FileInputStream fis = new FileInputStream(file); fis.skip(startFrom); - res = Response.newFixedLengthResponse(Status.PARTIAL_CONTENT, mime, fis, newLen); + 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); @@ -150,21 +161,21 @@ public class NanoHTTPDWebserver extends NanoHTTPD { if (headerIfRangeMissingOrMatching && range != null && startFrom >= fileLen) { // return the size of the file // 4xx responses are not trumped by if-none-match - res = newFixedLengthResponse(Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, ""); + 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(Status.NOT_MODIFIED, mime, ""); + 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(Status.NOT_MODIFIED, mime, ""); + res = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, ""); res.addHeader("ETag", etag); } else { // supply the file @@ -174,7 +185,7 @@ public class NanoHTTPDWebserver extends NanoHTTPD { } } } catch (IOException ioe) { - res = getForbiddenResponse("Reading file failed."); + res = newFixedLengthResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: Reading file failed."); } return res; @@ -196,7 +207,7 @@ public class NanoHTTPDWebserver extends NanoHTTPD { pluginResult.setKeepCallback(true); this.webserver.onRequestCallbackContext.sendPluginResult(pluginResult); - while (!this.webserver.responses.containsKey(requestUUID) && !this.webserver.responses.containsKey('file')) { + while (!this.webserver.responses.containsKey(requestUUID) && !this.webserver.responses.containsKey("file")) { try { Thread.sleep(1); } catch (InterruptedException e) { @@ -204,14 +215,25 @@ public class NanoHTTPDWebserver extends NanoHTTPD { } } - if (this.webserver.responses.containsKey('file')) { + JSONObject responseObject; + Response response = null; + + if (this.webserver.responses.containsKey("file")) { // TODO should specify a more correct mime-type - return serveFile(session.getHeaders(), new File((String) this.webserver.responses.get('file')), 'application/octet-stream'); + try { + responseObject = (JSONObject) this.webserver.responses.get("file"); + Log.d(this.getClass().getName(), "responseObject: " + responseObject.toString()); + return serveFile(session.getHeaders(), new File(responseObject.getString("path")), responseObject.getString("type")); + } + catch (JSONException e) { + e.printStackTrace(); + } + return response; } - JSONObject responseObject = (JSONObject) this.webserver.responses.get(requestUUID); + responseObject = (JSONObject) this.webserver.responses.get(requestUUID); Log.d(this.getClass().getName(), "responseObject: " + responseObject.toString()); - Response response = null; + try { response = newFixedLengthResponse( diff --git a/src/android/Webserver.java b/src/android/Webserver.java index 812825a..b6b9d48 100644 --- a/src/android/Webserver.java +++ b/src/android/Webserver.java @@ -101,7 +101,8 @@ public class Webserver extends CordovaPlugin { } private void sendFileResponse(JSONArray args, CallbackContext callbackContext) throws JSONException { - this.responses.put('file', args.get(1)); + Log.d(this.getClass().getName(), "Got sendResponse: " + args.toString()); + this.responses.put("file", args.get(0)); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); } From 752148e0c8529cb3c8a9fdbe985b9294a54c54a9 Mon Sep 17 00:00:00 2001 From: Keith Wait Date: Sun, 9 Sep 2018 17:00:54 -0500 Subject: [PATCH 4/5] check for request id when sending files avoids sending the file data in response to every single request issued after the one for the file. --- src/android/NanoHTTPDWebserver.java | 68 ++++++++++++++--------------- src/android/Webserver.java | 10 ----- src/www/webserver.js | 16 ------- webserver.js | 6 --- 4 files changed, 32 insertions(+), 68 deletions(-) diff --git a/src/android/NanoHTTPDWebserver.java b/src/android/NanoHTTPDWebserver.java index b6309b7..a018fe3 100644 --- a/src/android/NanoHTTPDWebserver.java +++ b/src/android/NanoHTTPDWebserver.java @@ -207,7 +207,7 @@ public class NanoHTTPDWebserver extends NanoHTTPD { pluginResult.setKeepCallback(true); this.webserver.onRequestCallbackContext.sendPluginResult(pluginResult); - while (!this.webserver.responses.containsKey(requestUUID) && !this.webserver.responses.containsKey("file")) { + while (!this.webserver.responses.containsKey(requestUUID)) { try { Thread.sleep(1); } catch (InterruptedException e) { @@ -215,45 +215,41 @@ public class NanoHTTPDWebserver extends NanoHTTPD { } } - JSONObject responseObject; + JSONObject responseObject = (JSONObject) this.webserver.responses.get(requestUUID); Response response = null; - - if (this.webserver.responses.containsKey("file")) { - // TODO should specify a more correct mime-type - try { - responseObject = (JSONObject) this.webserver.responses.get("file"); - Log.d(this.getClass().getName(), "responseObject: " + responseObject.toString()); - return serveFile(session.getHeaders(), new File(responseObject.getString("path")), responseObject.getString("type")); - } - catch (JSONException e) { - e.printStackTrace(); - } - return response; - } - - responseObject = (JSONObject) this.webserver.responses.get(requestUUID); 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.containsKey("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; } } diff --git a/src/android/Webserver.java b/src/android/Webserver.java index b6b9d48..cf82547 100644 --- a/src/android/Webserver.java +++ b/src/android/Webserver.java @@ -44,10 +44,6 @@ public class Webserver extends CordovaPlugin { this.sendResponse(args, callbackContext); return true; } - else if ("sendFileResponse".equals(action)) { - this.sendFileResponse(args, callbackContext); - return true; - } return false; // Returning false results in a "MethodNotFound" error. } @@ -100,12 +96,6 @@ public class Webserver extends CordovaPlugin { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); } - private void sendFileResponse(JSONArray args, CallbackContext callbackContext) throws JSONException { - Log.d(this.getClass().getName(), "Got sendResponse: " + args.toString()); - this.responses.put("file", args.get(0)); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); - } - /** * Just register the onRequest and send no result. This is needed to save the callbackContext to * invoke it later diff --git a/src/www/webserver.js b/src/www/webserver.js index 732e20c..a1d2332 100644 --- a/src/www/webserver.js +++ b/src/www/webserver.js @@ -4,7 +4,6 @@ const WEBSERVER_CLASS = 'Webserver'; const START_FUNCTION = 'start'; const ONREQUEST_FUNCTION = 'onRequest'; const SENDRESPONSE_FUNCION = 'sendResponse'; -const SENDFILE_FUNCION = 'sendFileResponse'; const STOP_FUNCTION = 'stop'; export function start(success_callback, error_callback, port) { @@ -46,21 +45,6 @@ export function sendResponse( ); } -export function sendFile( - requestId, - params, - success_callback, - error_callback -) { - exec( - success_callback, - error_callback, - WEBSERVER_CLASS, - SENDFILE_FUNCION, - [requestId, params] - ); -} - export function stop(success_callback, error_callback) { exec( success_callback, diff --git a/webserver.js b/webserver.js index 720ac86..9ac93d6 100644 --- a/webserver.js +++ b/webserver.js @@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { exports.start = start; exports.onRequest = onRequest; exports.sendResponse = sendResponse; -exports.sendFile = sendFile; exports.stop = stop; var _exec = require('cordova/exec'); @@ -19,7 +18,6 @@ var WEBSERVER_CLASS = 'Webserver'; var START_FUNCTION = 'start'; var ONREQUEST_FUNCTION = 'onRequest'; var SENDRESPONSE_FUNCION = 'sendResponse'; -var SENDFILE_FUNCION = 'sendFileResponse'; var STOP_FUNCTION = 'stop'; function start(success_callback, error_callback, port) { @@ -40,10 +38,6 @@ function sendResponse(requestId, params, success_callback, error_callback) { (0, _exec2.default)(success_callback, error_callback, WEBSERVER_CLASS, SENDRESPONSE_FUNCION, [requestId, params]); } -function sendFile(requestId, params, success_callback, error_callback) { - (0, _exec2.default)(success_callback, error_callback, WEBSERVER_CLASS, SENDFILE_FUNCION, [requestId, params]); -} - function stop(success_callback, error_callback) { (0, _exec2.default)(success_callback, error_callback, WEBSERVER_CLASS, STOP_FUNCTION, []); } From 1bee3d06049547ac4462e690d2ee9f4edc3fb19e Mon Sep 17 00:00:00 2001 From: Keith Wait Date: Sun, 9 Sep 2018 19:54:26 -0500 Subject: [PATCH 5/5] use correct method for key query --- src/android/NanoHTTPDWebserver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/NanoHTTPDWebserver.java b/src/android/NanoHTTPDWebserver.java index a018fe3..922c8b7 100644 --- a/src/android/NanoHTTPDWebserver.java +++ b/src/android/NanoHTTPDWebserver.java @@ -219,7 +219,7 @@ public class NanoHTTPDWebserver extends NanoHTTPD { Response response = null; Log.d(this.getClass().getName(), "responseObject: " + responseObject.toString()); - if (responseObject.containsKey("path")) { + 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"));