Nanohttpd 是一款轻量型的java服务端框架。
github为https://github.com/NanoHttpd/nanohttpd
在集成使用到android上时,在日志中,反复会出现
03-14 17:28:25.858 E/StrictMode( 2530): java.lang.Throwable: Explicit termination method 'end' not called
03-14 17:28:25.858 E/StrictMode( 2530): at dalvik.system.CloseGuard.open(CloseGuard.java:184)
03-14 17:28:25.858 E/StrictMode( 2530): at java.util.zip.Deflater.<init>(Deflater.java:192)
03-14 17:28:25.858 E/StrictMode( 2530): at java.util.zip.GZIPOutputStream.<init>(GZIPOutputStream.java:81)
03-14 17:28:25.858 E/StrictMode( 2530): at java.util.zip.GZIPOutputStream.<init>(GZIPOutputStream.java:54)
03-14 17:28:25.858 E/StrictMode( 2530): at org.nanohttpd.protocols.http.response.Response.sendBodyWithCorrectEncoding(Response.java:308)
03-14 17:28:25.858 E/StrictMode( 2530): at org.nanohttpd.protocols.http.response.Response.sendBodyWithCorrectTransferAndEncoding(Response.java:299)
03-14 17:28:25.858 E/StrictMode( 2530): at org.nanohttpd.protocols.http.response.Response.send(Response.java:268)
03-14 17:28:25.858 E/StrictMode( 2530): at org.nanohttpd.protocols.http.HTTPSession.execute(HTTPSession.java:435)
03-14 17:28:25.858 E/StrictMode( 2530): at org.nanohttpd.protocols.http.ClientHandler.run(ClientHandler.java:75)
03-14 17:28:25.858 E/StrictMode( 2530): at java.lang.Thread.run(Thread.java:818)
原本没有奔溃,没有太在意,但是发现有点频繁,所以还是决定解决。在浏览了github上的讨论,有人提出这个问题,但是貌似没有人解决,于是自己下载源码,亲自修改源码解决
根源相对来说也明显:没有正常关闭流(绿色为我修改,红色为问题代码,紫色是其它修改)
org.nanohttpd.protocols.http.response.Response
/**
* Sends given response to the socket.
*/
public OutputStream send(OutputStream outputStream) {
SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.getDefault());
gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
try {
if (this.status == null) {
throw new Error("sendResponse(): Status can't be null.");
}
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, new ContentType(this.mimeType).getEncoding())), false);
pw.append("HTTP/1.1 ").append(this.status.getDescription()).append(" \r\n");
if (this.mimeType != null) {
printHeader(pw, "Content-Type", this.mimeType);
}
if (getHeader("date") == null) {
printHeader(pw, "Date", gmtFrmt.format(new Date()));
}
for (Entry<String, String> entry : this.header.entrySet()) {
printHeader(pw, entry.getKey(), entry.getValue());
}
for (String cookieHeader : this.cookieHeaders) {
printHeader(pw, "Set-Cookie", cookieHeader);
}
if (getHeader("connection") == null) {
printHeader(pw, "Connection", (this.keepAlive ? "keep-alive" : "close"));
}
if (getHeader("content-length") != null) {
setUseGzip(false);
}
if (useGzipWhenAccepted()) {
printHeader(pw, "Content-Encoding", "gzip");
setChunkedTransfer(true);
}
long pending = this.data != null ? this.contentLength : 0;
if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
printHeader(pw, "Transfer-Encoding", "chunked");
} else if (!useGzipWhenAccepted()) {
pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, pending);
}
pw.append("\r\n");
pw.flush();
OutputStream end = sendBodyWithCorrectTransferAndEncoding(outputStream, pending);
outputStream.flush();
NanoHTTPD.safeClose(this.data);
return end;
} catch (IOException ioe) {
NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe);
}
return outputStream;
}
private OutputStream sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException {
if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream);
OutputStream end = sendBodyWithCorrectEncoding(chunkedOutputStream, -1);
chunkedOutputStream.finish();
return end;
} else {
return sendBodyWithCorrectEncoding(outputStream, pending);
}
}
private OutputStream sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException {
if (useGzipWhenAccepted()) {
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
sendBody(gzipOutputStream, -1);
gzipOutputStream.finish();
return gzipOutputStream;
} else {
sendBody(outputStream, pending);
return outputStream;
}
}
以上,对于传入的流,作者对其进行了两次封装,并调用了finish方法,之后在HTTPSession中,最后会统一用NanoHTTPD.safeClose()进行关闭。但是,作者明显是漏了对于GZIPOutputStream的关闭,需要对其进行close调用。但是,直接在finish后面调用close不太合适,如果集成了websocket模块后,就会发现作者对管道之后还有用,所以按着作者的思路,我把最后生成的流又return回去,在整个程序的最后进行关闭。
org.nanohttpd.protocols.http.HTTPSession
@Override
public void execute() throws IOException {
Response r = null;
try {
// Read the first 8192 bytes.
// The full header should fit in here.
// Apache's default header limit is 8KB.
// Do NOT assume that a single read will get the entire header
// at once!
byte[] buf = new byte[HTTPSession.BUFSIZE];
this.splitbyte = 0;
this.rlen = 0;
int read = -1;
this.inputStream.mark(HTTPSession.BUFSIZE);
try {
read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE);
} catch (SSLException e) {
throw e;
} catch (IOException e) {
NanoHTTPD.safeClose(this.inputStream);
NanoHTTPD.safeClose(this.outputStream);
throw new SocketException("NanoHttpd Shutdown");
}
if (read == -1) {
// socket was been closed
NanoHTTPD.safeClose(this.inputStream);
NanoHTTPD.safeClose(this.outputStream);
throw new SocketException("NanoHttpd Shutdown");
}
while (read > 0) {
this.rlen += read;
this.splitbyte = findHeaderEnd(buf, this.rlen);
if (this.splitbyte > 0) {
break;
}
read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen);
}
if (this.splitbyte < this.rlen) {
this.inputStream.reset();
this.inputStream.skip(this.splitbyte);
}
this.parms = new HashMap<String, List<String>>();
if (null == this.headers) {
this.headers = new HashMap<String, String>();
} else {
this.headers.clear();
}
// Create a BufferedReader for parsing the header.
BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen)));
// Decode the header into parms and header java properties
Map<String, String> pre = new HashMap<String, String>();
decodeHeader(hin, pre, this.parms, this.headers);
if (null != this.remoteIp) {
this.headers.put("remote-addr", this.remoteIp);
this.headers.put("http-client-ip", this.remoteIp);
}
this.method = Method.lookup(pre.get("method"));
if (this.method == null) {
throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error. HTTP verb " + pre.get("method") + " unhandled.");
}
this.uri = pre.get("uri");
this.cookies = new CookieHandler(this.headers);
String connection = this.headers.get("connection");
boolean keepAlive = "HTTP/1.1".equals(protocolVersion) && (connection == null || !connection.matches("(?i).*close.*"));
// Ok, now do the serve()
// TODO: long body_size = getBodySize();
// TODO: long pos_before_serve = this.inputStream.totalRead()
// (requires implementation for totalRead())
r = httpd.handle(this);
// TODO: this.inputStream.skip(body_size -
// (this.inputStream.totalRead() - pos_before_serve))
if (r == null) {
throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
} else {
String acceptEncoding = this.headers.get("accept-encoding");
this.cookies.unloadQueue(r);
r.setRequestMethod(this.method);
if (acceptEncoding == null || !acceptEncoding.contains("gzip")) {
r.setUseGzip(false);
}
r.setKeepAlive(keepAlive);
OutputStream end = r.send(this.outputStream);
end.close();
}
if (!keepAlive || r.isCloseConnection()) {
throw new SocketException("NanoHttpd Shutdown");
}
} catch (SocketException e) {
// throw it out to close socket object (finalAccept)
throw e;
} catch (SocketTimeoutException ste) {
// treat socket timeouts the same way we treat socket exceptions
// i.e. close the stream & finalAccept object by throwing the
// exception up the call stack.
throw ste;
} catch (SSLException ssle) {
//Response resp = Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SSL PROTOCOL FAILURE: " + ssle.getMessage());
//resp.send(this.outputStream);
NanoHTTPD.safeClose(this.outputStream);
} catch (IOException ioe) {
//Response resp = Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
//resp.send(this.outputStream);
NanoHTTPD.safeClose(this.outputStream);
} catch (ResponseException re) {
//Response resp = Response.newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
//resp.send(this.outputStream);
NanoHTTPD.safeClose(this.outputStream);
} finally {
NanoHTTPD.safeClose(r);
this.tempFileManager.clear();
}
}
如此修改(不知道有没有漏掉),目前没有发现再报此问题。至于紫色的部分,我个人觉得不合理,在端口已经异常的情况下,我认为不用再发消息,只会导致循环报错。