aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorJarno Elonen <elonen@iki.fi>2015-05-24 14:57:04 +0300
committerJarno Elonen <elonen@iki.fi>2015-05-24 14:57:04 +0300
commitdde976d5b0ef7d237e32679bbe1a1a717bd0007e (patch)
tree309d7aff1514e19d02e1dfa8bdf9b021ddf30f93 /core
parenta3481989f3cc7d7cd69685349fd6f3d2b370bbee (diff)
downloadnanohttpd-dde976d5b0ef7d237e32679bbe1a1a717bd0007e.tar.gz
Make 'Connection: Close' handling HTTP 1.1 compliant. Closes #133
Diffstat (limited to 'core')
-rw-r--r--core/src/main/java/fi/iki/elonen/NanoHTTPD.java42
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java17
2 files changed, 46 insertions, 13 deletions
diff --git a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
index 50e074f..61e7d8d 100644
--- a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
+++ b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
@@ -468,6 +468,8 @@ public abstract class NanoHTTPD {
private String remoteIp;
+ private String protocolVersion;
+
public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
this.tempFileManager = tempFileManager;
this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);
@@ -516,15 +518,14 @@ public abstract class NanoHTTPD {
}
// If there's another token, its protocol version,
- // followed by HTTP headers. Ignore version but parse headers.
+ // followed by HTTP headers.
// NOTE: this now forces header names lower case since they are
// case insensitive and vary by client.
if (st.hasMoreTokens()) {
- if (!st.nextToken().equals("HTTP/1.1")) {
- throw new ResponseException(Response.Status.UNSUPPORTED_HTTP_VERSION, "Only HTTP/1.1 is supported.");
- }
+ protocolVersion = st.nextToken();
} else {
- NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange..");
+ protocolVersion = "HTTP/1.1";
+ NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange. Assuming HTTP/1.1.");
}
String line = in.readLine();
while (line != null && line.trim().length() > 0) {
@@ -716,6 +717,9 @@ public abstract class NanoHTTPD {
this.cookies = new CookieHandler(this.headers);
+ String connection = this.headers.get("connection");
+ boolean keepAlive = protocolVersion.equals("HTTP/1.1") && (connection == null || !connection.matches("(?i).*close.*"));
+
// Ok, now do the serve()
Response r = serve(this);
if (r == null) {
@@ -725,8 +729,12 @@ public abstract class NanoHTTPD {
this.cookies.unloadQueue(r);
r.setRequestMethod(this.method);
r.setGzipEncoding(acceptEncoding != null && acceptEncoding.contains("gzip"));
+ r.setKeepAlive(keepAlive);
r.send(this.outputStream);
}
+ if (!keepAlive || "close".equalsIgnoreCase(r.getHeader("connection"))) {
+ throw new SocketException("NanoHttpd Shutdown");
+ }
} catch (SocketException e) {
// throw it out to close socket object (finalAccept)
throw e;
@@ -1170,6 +1178,8 @@ public abstract class NanoHTTPD {
private boolean encodeAsGzip;
+ private boolean keepAlive;
+
/**
* Creates a fixed length response if totalBytes>=0, otherwise chunked.
*/
@@ -1184,6 +1194,7 @@ public abstract class NanoHTTPD {
this.contentLength = totalBytes;
}
this.chunkedTransfer = this.contentLength < 0;
+ keepAlive = true;
}
/**
@@ -1198,7 +1209,12 @@ public abstract class NanoHTTPD {
}
public String getHeader(String name) {
- return this.header.get(name);
+ for (String headerName : header.keySet()) {
+ if (headerName.equalsIgnoreCase(name)) {
+ return header.get(headerName);
+ }
+ }
+ return null;
}
public String getMimeType() {
@@ -1217,6 +1233,10 @@ public abstract class NanoHTTPD {
this.encodeAsGzip = encodeAsGzip;
}
+ public void setKeepAlive(boolean useKeepAlive) {
+ this.keepAlive = useKeepAlive;
+ }
+
private boolean headerAlreadySent(Map<String, String> header, String name) {
boolean alreadySent = false;
for (String headerName : header.keySet()) {
@@ -1255,7 +1275,9 @@ public abstract class NanoHTTPD {
}
}
- sendConnectionHeaderIfNotAlreadyPresent(pw, this.header);
+ if (!headerAlreadySent(header, "connection")) {
+ pw.print("Connection: " + (this.keepAlive ? "keep-alive" : "close") + "\r\n");
+ }
if (headerAlreadySent(this.header, "content-length")) {
encodeAsGzip = false;
@@ -1331,12 +1353,6 @@ public abstract class NanoHTTPD {
}
}
- protected void sendConnectionHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header) {
- if (!headerAlreadySent(header, "connection")) {
- pw.print("Connection: keep-alive\r\n");
- }
- }
-
protected long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header, long size) {
for (String headerName : header.keySet()) {
if (headerName.equalsIgnoreCase("content-length")) {
diff --git a/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java b/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java
index b5170db..e168814 100644
--- a/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java
@@ -99,9 +99,26 @@ public class HttpKeepAliveTest extends HttpServerTest {
for (int i = 0; i < 2048; i++) {
requestStream.write(request.getBytes());
requestStream.flush();
+ outputStream.reset();
session.execute();
assertResponse(outputStream, expected);
}
+
+ // Finally, try "Connection: Close"
+ String closeReq = request.replaceAll("HTTP/1.1", "HTTP/1.1\r\nConnection: Close");
+ expected[3] = "Connection: close";
+ requestStream.write(closeReq.getBytes());
+ outputStream.reset();
+ requestStream.flush();
+ // Server should now close the socket by throwing a
+ // SocketException:
+ try {
+ session.execute();
+ } catch (java.net.SocketException se) {
+ junit.framework.Assert.assertEquals(se.getMessage(), "NanoHttpd Shutdown");
+ }
+ assertResponse(outputStream, expected);
+
} finally {
tempFileManager.clear();
}