Recently I need a HTTP proxy server in an Android app to let the WebView inside the app to use server side API calls that does not set CORS header, so I chose to use NanoHTTPD which is the easiest way for Android. For HTTP GET requests all requests are fine, however for HTTP POST requests some weird behaviors from NanoHTTPD appears:
- The WebView received HTTP 400 response but the requests do not reach the serve() callback
- The remote server complained incomplete parameters
After some search it is confirmed to be a bug in NanoHTTPD and suggestion to fix the issue is also provided (StackOverflow post). However the most troublesome for the fix is that it needs to be fixed from the NanoHTTPD source code directly, which is not ideal that I included the dependency directly from Maven Central.
So instead of cloning a copy of NanoHTTPD source code and fix it there, I found a way without the need to do it. From HTTPSession.java (Github source) starting from line 413, we can see that the HTTPSession will receive a Response object from the handle() method which essentially just in turn calls the serve() method which will be overridden from our implementation. After that it will configure the response object and call the send() method to write the response to the OutputStream. After that only some cleanup is performed without touching the original input stream that it forgot to close.
I tried first to close the input stream in serve() method before returning, but it doesn’t work. So I tried to delay the input stream closing until after sending the response, but how? Did you remember the HTTPSession will call the send() function from the response object? By subclassing the Response class and overloading the send() method, we can accomplish what we want. My sample implementation (in Kotlin):
class Response2(val serveInputStream: InputStream, status:IStatus, mimeType:String?, data: InputStream?, totalBytes: Long) : Response(status, mimeType, data, totalBytes) {
override fun send(outputStream: OutputStream?) {
super.send(outputStream)
serveInputStream.close()
}
}
fun newFixedLengthResponse2(serveInputStream: InputStream, status: IStatus, mimeType: String?, data: InputStream?, totalBytes: Long): Response {
return Response2(serveInputStream, status, mimeType, data, totalBytes)
}
How to use? For any response that you created with newFixedLengthResponse(), use the new method newFixedLengthResponse2() instead, which needs one more input parameter for the input stream passed from serve(), e.g.
context.assets.open(file).let {
return newFixedLengthResponse2(this.inputStream, Response.Status.OK, URLConnection.guessContentTypeFromName(file), it, it.available().toLong())
}
By adding these changes, the input stream from the session can be properly closed and avoiding any issues for POST request.