Carefully manage Keep-Alive/Close connection headers in all examples (#8966)

Motivation:

"Connection: close" header should be specified each time we're going
to close an underlying TCP connection when sending HTTP/1.1 reply.

Modifications:

Introduces changes made in #8914 for the following examples:

* WebSocket index page and WebSocket server handler
* HelloWorld server
* SPDY server handler
* HTTP/1.1 server handler from HTTP/2 HelloWorld example
* HTTP/1.1 server handler from tiles example

Result:

Keep-Alive connections management conforms with RFCs.
This commit is contained in:
Oleksii Kachaiev 2019-04-02 12:10:11 -07:00 committed by Norman Maurer
parent 6491f359c7
commit d0beb8dea1
7 changed files with 121 additions and 59 deletions

View File

@ -110,15 +110,18 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
public static final String HTTP_DATE_GMT_TIMEZONE = "GMT"; public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
public static final int HTTP_CACHE_SECONDS = 60; public static final int HTTP_CACHE_SECONDS = 60;
private FullHttpRequest request;
@Override @Override
public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
this.request = request;
if (!request.decoderResult().isSuccess()) { if (!request.decoderResult().isSuccess()) {
sendError(ctx, BAD_REQUEST); sendError(ctx, BAD_REQUEST);
return; return;
} }
if (!GET.equals(request.method())) { if (!GET.equals(request.method())) {
sendError(ctx, METHOD_NOT_ALLOWED); this.sendError(ctx, METHOD_NOT_ALLOWED);
return; return;
} }
@ -126,21 +129,21 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
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) {
sendError(ctx, FORBIDDEN); this.sendError(ctx, FORBIDDEN);
return; return;
} }
File file = new File(path); File file = new File(path);
if (file.isHidden() || !file.exists()) { if (file.isHidden() || !file.exists()) {
sendError(ctx, NOT_FOUND); this.sendError(ctx, NOT_FOUND);
return; return;
} }
if (file.isDirectory()) { if (file.isDirectory()) {
if (uri.endsWith("/")) { if (uri.endsWith("/")) {
sendListing(ctx, file, uri, keepAlive); this.sendListing(ctx, file, uri);
} else { } else {
sendRedirect(ctx, uri + '/', keepAlive); this.sendRedirect(ctx, uri + '/');
} }
return; return;
} }
@ -161,7 +164,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, keepAlive); this.sendNotModified(ctx);
return; return;
} }
} }
@ -182,6 +185,8 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
if (!keepAlive) { if (!keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
} else if (request.protocolVersion().equals(HTTP_1_0)) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
} }
// Write the initial line and the header. // Write the initial line and the header.
@ -266,7 +271,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, boolean keepAlive) { private void sendListing(ChannelHandlerContext ctx, File dir, String dirPath) {
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");
@ -306,22 +311,22 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
response.content().writeBytes(buffer); response.content().writeBytes(buffer);
buffer.release(); buffer.release();
sendAndCleanupConnection(ctx, response, keepAlive); this.sendAndCleanupConnection(ctx, response);
} }
private static void sendRedirect(ChannelHandlerContext ctx, String newUri, boolean keepAlive) { private void sendRedirect(ChannelHandlerContext ctx, String newUri) {
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);
sendAndCleanupConnection(ctx, response, keepAlive); this.sendAndCleanupConnection(ctx, response);
} }
private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse( FullHttpResponse response = new DefaultFullHttpResponse(
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");
sendAndCleanupConnection(ctx, response, false); this.sendAndCleanupConnection(ctx, response);
} }
/** /**
@ -330,24 +335,27 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
* @param ctx * @param ctx
* Context * Context
*/ */
private static void sendNotModified(ChannelHandlerContext ctx, boolean keepAlive) { private void sendNotModified(ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED); FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED);
setDateHeader(response); setDateHeader(response);
sendAndCleanupConnection(ctx, response, keepAlive); this.sendAndCleanupConnection(ctx, response);
} }
/** /**
* If Keep-Alive is disabled, attaches "Connection: close" header to the response * If Keep-Alive is disabled, attaches "Connection: close" header to the response
* and closes the connection after the response being sent. * and closes the connection after the response being sent.
*/ */
private static void sendAndCleanupConnection(ChannelHandlerContext ctx, FullHttpResponse response, private void sendAndCleanupConnection(ChannelHandlerContext ctx, FullHttpResponse response) {
boolean keepAlive) { final FullHttpRequest request = this.request;
final boolean keepAlive = HttpUtil.isKeepAlive(request);
HttpUtil.setContentLength(response, response.content().readableBytes()); HttpUtil.setContentLength(response, response.content().readableBytes());
if (!keepAlive) { if (!keepAlive) {
// We're going to close the connection as soon as the response is sent, // We're going to close the connection as soon as the response is sent,
// so we should also make it clear for the client. // so we should also make it clear for the client.
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
} else if (request.protocolVersion().equals(HTTP_1_0)) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
} }
ChannelFuture flushPromise = ctx.writeAndFlush(response); ChannelFuture flushPromise = ctx.writeAndFlush(response);

View File

@ -16,27 +16,28 @@
package io.netty.example.http.helloworld; package io.netty.example.http.helloworld;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.AsciiString; import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import static io.netty.handler.codec.http.HttpResponseStatus.*; import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpVersion.*; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
public class HttpHelloWorldServerHandler extends SimpleChannelInboundHandler<HttpObject> { public class HttpHelloWorldServerHandler extends SimpleChannelInboundHandler<HttpObject> {
private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' }; private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };
private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");
private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");
private static final AsciiString CONNECTION = AsciiString.cached("Connection");
private static final AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive");
@Override @Override
public void channelReadComplete(ChannelHandlerContext ctx) { public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush(); ctx.flush();
@ -52,11 +53,17 @@ public class HttpHelloWorldServerHandler extends SimpleChannelInboundHandler<Htt
response.headers().set(CONTENT_TYPE, "text/plain"); response.headers().set(CONTENT_TYPE, "text/plain");
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes()); response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
if (!keepAlive) { if (keepAlive && req.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set(CONNECTION, KEEP_ALIVE); response.headers().set(CONNECTION, KEEP_ALIVE);
ctx.write(response); } else {
// Tell the client we're going to close the connection.
response.headers().set(CONNECTION, CLOSE);
}
ChannelFuture f = ctx.write(response);
if (!keepAlive) {
f.addListener(ChannelFutureListener.CLOSE);
} }
} }
} }

View File

@ -155,8 +155,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
} catch (ErrorDataDecoderException e1) { } catch (ErrorDataDecoderException e1) {
e1.printStackTrace(); e1.printStackTrace();
responseContent.append(e1.getMessage()); responseContent.append(e1.getMessage());
writeResponse(ctx.channel()); writeResponse(ctx.channel(), true);
ctx.channel().close();
return; return;
} }
@ -180,8 +179,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
} catch (ErrorDataDecoderException e1) { } catch (ErrorDataDecoderException e1) {
e1.printStackTrace(); e1.printStackTrace();
responseContent.append(e1.getMessage()); responseContent.append(e1.getMessage());
writeResponse(ctx.channel()); writeResponse(ctx.channel(), true);
ctx.channel().close();
return; return;
} }
responseContent.append('o'); responseContent.append('o');
@ -307,24 +305,27 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
} }
private void writeResponse(Channel channel) { private void writeResponse(Channel channel) {
writeResponse(channel, false);
}
private void writeResponse(Channel channel, boolean forceClose) {
// Convert the response content to a ChannelBuffer. // Convert the response content to a ChannelBuffer.
ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8); ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
responseContent.setLength(0); responseContent.setLength(0);
// Decide whether to close the connection or not. // Decide whether to close the connection or not.
boolean close = request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true) boolean keepAlive = HttpUtil.isKeepAlive(request) && !forceClose;
|| request.protocolVersion().equals(HttpVersion.HTTP_1_0)
&& !request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE, true);
// Build the response object. // Build the response object.
FullHttpResponse response = new DefaultFullHttpResponse( FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf); HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
if (!close) {
// There's no need to add 'Content-Length' header
// if this is the last response.
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes()); response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
if (!keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
} else if (request.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
} }
Set<Cookie> cookies; Set<Cookie> cookies;
@ -343,7 +344,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
// Write the response. // Write the response.
ChannelFuture future = channel.writeAndFlush(response); ChannelFuture future = channel.writeAndFlush(response);
// Close the connection after the write operation is done if necessary. // Close the connection after the write operation is done if necessary.
if (close) { if (!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE); future.addListener(ChannelFutureListener.CLOSE);
} }
} }
@ -428,8 +429,20 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8"); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes()); response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
// Decide whether to close the connection or not.
boolean keepAlive = HttpUtil.isKeepAlive(request);
if (!keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
} else if (request.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
// Write the response. // Write the response.
ctx.channel().writeAndFlush(response); ChannelFuture future = ctx.channel().writeAndFlush(response);
// Close the connection after the write operation is done if necessary.
if (!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
} }
@Override @Override

View File

@ -17,7 +17,6 @@ package io.netty.example.http.websocketx.benchmarkserver;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
@ -36,9 +35,16 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.http.HttpMethod.*; import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpResponseStatus.*; import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpVersion.*; import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_0;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
/** /**
* Handles handshakes and messages * Handles handshakes and messages
@ -137,9 +143,15 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
} }
// Send the response and close the connection if necessary. // Send the response and close the connection if necessary.
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) { if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE); // Tell the client we're going to close the connection.
res.headers().set(CONNECTION, CLOSE);
ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
} else {
if (req.protocolVersion().equals(HTTP_1_0)) {
res.headers().set(CONNECTION, KEEP_ALIVE);
}
ctx.writeAndFlush(res);
} }
} }

View File

@ -17,7 +17,6 @@ package io.netty.example.http.websocketx.server;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
@ -31,11 +30,16 @@ import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandler;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpMethod.GET; import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_0;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
/** /**
@ -69,7 +73,7 @@ public class WebSocketIndexPageHandler extends SimpleChannelInboundHandler<FullH
ByteBuf content = WebSocketServerIndexPage.getContent(webSocketLocation); ByteBuf content = WebSocketServerIndexPage.getContent(webSocketLocation);
FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content); FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
res.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8"); res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
HttpUtil.setContentLength(res, content.readableBytes()); HttpUtil.setContentLength(res, content.readableBytes());
sendHttpResponse(ctx, req, res); sendHttpResponse(ctx, req, res);
@ -94,9 +98,15 @@ public class WebSocketIndexPageHandler extends SimpleChannelInboundHandler<FullH
} }
// Send the response and close the connection if necessary. // Send the response and close the connection if necessary.
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) { if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE); // Tell the client we're going to close the connection.
res.headers().set(CONNECTION, CLOSE);
ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
} else {
if (req.protocolVersion().equals(HTTP_1_0)) {
res.headers().set(CONNECTION, KEEP_ALIVE);
}
ctx.writeAndFlush(res);
} }
} }

View File

@ -18,8 +18,11 @@ package io.netty.example.http2.helloworld.server;
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE; import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_0;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@ -32,7 +35,6 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpHeaderValues;
/** /**
* HTTP handler that responds with a "Hello World" * HTTP handler that responds with a "Hello World"
@ -59,11 +61,15 @@ public class HelloWorldHttp1Handler extends SimpleChannelInboundHandler<FullHttp
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes()); response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
if (!keepAlive) { if (keepAlive) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE); if (req.protocolVersion().equals(HTTP_1_0)) {
} else { response.headers().set(CONNECTION, KEEP_ALIVE);
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE); }
ctx.write(response); ctx.write(response);
} else {
// Tell the client we're going to close the connection.
response.headers().set(CONNECTION, CLOSE);
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} }
} }

View File

@ -17,8 +17,11 @@
package io.netty.example.http2.tiles; package io.netty.example.http2.tiles;
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpUtil.isKeepAlive; import static io.netty.handler.codec.http.HttpUtil.isKeepAlive;
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE; import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_0;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -26,7 +29,6 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpHeaderValues;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -49,9 +51,13 @@ public final class Http1RequestHandler extends Http2RequestHandler {
HttpUtil.setContentLength(response, response.content().readableBytes()); HttpUtil.setContentLength(response, response.content().readableBytes());
ctx.executor().schedule(() -> { ctx.executor().schedule(() -> {
if (isKeepAlive(request)) { if (isKeepAlive(request)) {
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE); if (request.protocolVersion().equals(HTTP_1_0)) {
response.headers().set(CONNECTION, KEEP_ALIVE);
}
ctx.writeAndFlush(response); ctx.writeAndFlush(response);
} else { } else {
// Tell the client we're going to close the connection.
response.headers().set(CONNECTION, CLOSE);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} }
}, latency, TimeUnit.MILLISECONDS); }, latency, TimeUnit.MILLISECONDS);