Carefully manage Keep-Alive connections in HttpStaticFileServer (#8914)

Motivation:

Simple rules:

* close the connection when sending any error
* specify "Connection: close" header when closing the connection
* successful responses should keep the connection intact when otherwise is not requested by the client

Modifications:

* "send response and cleanup the connection" logic moved to a helper
* for all successful responses set "Content-Lenght" header
* do not specify "Connection: Keep-Alive" header as far it's a default for HTTP/1.1
* set "Connection: close" header when necessary

Result:

Keep-Alive connections management is inlined with RFCs.
This commit is contained in:
Oleksii Kachaiev 2019-03-06 16:51:58 +02:00 committed by Norman Maurer
parent b2dc54c8c6
commit 902cdaae56

View File

@ -122,6 +122,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
return; return;
} }
final boolean keepAlive = HttpUtil.isKeepAlive(request);
final String uri = request.uri(); final String uri = request.uri();
final String path = sanitizeUri(uri); final String path = sanitizeUri(uri);
if (path == null) { if (path == null) {
@ -137,9 +138,9 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
if (file.isDirectory()) { if (file.isDirectory()) {
if (uri.endsWith("/")) { if (uri.endsWith("/")) {
sendListing(ctx, file, uri); sendListing(ctx, file, uri, keepAlive);
} else { } else {
sendRedirect(ctx, uri + '/'); sendRedirect(ctx, uri + '/', keepAlive);
} }
return; return;
} }
@ -160,7 +161,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000; long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
long fileLastModifiedSeconds = file.lastModified() / 1000; long fileLastModifiedSeconds = file.lastModified() / 1000;
if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) { if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
sendNotModified(ctx); sendNotModified(ctx, keepAlive);
return; return;
} }
} }
@ -178,8 +179,9 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
HttpUtil.setContentLength(response, fileLength); HttpUtil.setContentLength(response, fileLength);
setContentTypeHeader(response, file); setContentTypeHeader(response, file);
setDateAndCacheHeaders(response, file); setDateAndCacheHeaders(response, file);
if (HttpUtil.isKeepAlive(request)) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); if (!keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
} }
// Write the initial line and the header. // Write the initial line and the header.
@ -218,7 +220,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
}); });
// Decide whether to close the connection or not. // Decide whether to close the connection or not.
if (!HttpUtil.isKeepAlive(request)) { if (!keepAlive) {
// Close the connection when the whole content is written out. // Close the connection when the whole content is written out.
lastContentFuture.addListener(ChannelFutureListener.CLOSE); lastContentFuture.addListener(ChannelFutureListener.CLOSE);
} }
@ -264,7 +266,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[^-\\._]?[^<>&\\\"]*"); private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[^-\\._]?[^<>&\\\"]*");
private static void sendListing(ChannelHandlerContext ctx, File dir, String dirPath) { private static void sendListing(ChannelHandlerContext ctx, File dir, String dirPath, boolean keepAlive) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK); FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8"); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
@ -304,16 +306,14 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
response.content().writeBytes(buffer); response.content().writeBytes(buffer);
buffer.release(); buffer.release();
// Close the connection as soon as the error message is sent. sendAndCleanupConnection(ctx, response, keepAlive);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} }
private static void sendRedirect(ChannelHandlerContext ctx, String newUri) { private static void sendRedirect(ChannelHandlerContext ctx, String newUri, boolean keepAlive) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND); FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);
response.headers().set(HttpHeaderNames.LOCATION, newUri); response.headers().set(HttpHeaderNames.LOCATION, newUri);
// Close the connection as soon as the error message is sent. sendAndCleanupConnection(ctx, response, keepAlive);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} }
private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
@ -321,8 +321,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8)); HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
// Close the connection as soon as the error message is sent. sendAndCleanupConnection(ctx, response, false);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} }
/** /**
@ -331,12 +330,32 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
* @param ctx * @param ctx
* Context * Context
*/ */
private static void sendNotModified(ChannelHandlerContext ctx) { private static void sendNotModified(ChannelHandlerContext ctx, boolean keepAlive) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED); FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED);
setDateHeader(response); setDateHeader(response);
// Close the connection as soon as the error message is sent. sendAndCleanupConnection(ctx, response, keepAlive);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); }
/**
* If Keep-Alive is disabled, attaches "Connection: close" header to the response
* and closes the connection after the response being sent.
*/
private static void sendAndCleanupConnection(ChannelHandlerContext ctx, FullHttpResponse response,
boolean keepAlive) {
HttpUtil.setContentLength(response, response.content().readableBytes());
if (!keepAlive) {
// We're going to close the connection as soon as the response is sent,
// so we should also make it clear for the client.
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
}
ChannelFuture flushPromise = ctx.writeAndFlush(response);
if (!keepAlive) {
// Close the connection as soon as the response is sent.
flushPromise.addListener(ChannelFutureListener.CLOSE);
}
} }
/** /**