/*
 * Copyright 2016 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec.http;

import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;

import static io.netty.handler.codec.http.HttpUtil.*;

HttpServerKeepAliveHandler helps close persistent connections when appropriate.

The server channel is expected to set the proper 'Connection' header if it can handle persistent connections. HttpServerKeepAliveHandler will automatically close the channel for any LastHttpContent that corresponds to a client request for closing the connection, or if the HttpResponse associated with that LastHttpContent requested closing the connection or didn't have a self defined message length.

Since HttpServerKeepAliveHandler expects HttpObjects it should be added after HttpServerCodec but before any other handlers that might send a HttpResponse.

ChannelPipeline p = ...; ... p.addLast("serverCodec", new HttpServerCodec()); p.addLast("httpKeepAlive", new HttpServerKeepAliveHandler()); p.addLast("aggregator", new HttpObjectAggregator(1048576)); ... p.addLast("handler", new HttpRequestHandler()); 
/** * HttpServerKeepAliveHandler helps close persistent connections when appropriate. * <p> * The server channel is expected to set the proper 'Connection' header if it can handle persistent connections. {@link * HttpServerKeepAliveHandler} will automatically close the channel for any LastHttpContent that corresponds to a client * request for closing the connection, or if the HttpResponse associated with that LastHttpContent requested closing the * connection or didn't have a self defined message length. * <p> * Since {@link HttpServerKeepAliveHandler} expects {@link HttpObject}s it should be added after {@link HttpServerCodec} * but before any other handlers that might send a {@link HttpResponse}. <blockquote> * <pre> * {@link ChannelPipeline} p = ...; * ... * p.addLast("serverCodec", new {@link HttpServerCodec}()); * p.addLast("httpKeepAlive", <b>new {@link HttpServerKeepAliveHandler}()</b>); * p.addLast("aggregator", new {@link HttpObjectAggregator}(1048576)); * ... * p.addLast("handler", new HttpRequestHandler()); * </pre> * </blockquote> */
public class HttpServerKeepAliveHandler extends ChannelDuplexHandler { private static final String MULTIPART_PREFIX = "multipart"; private boolean persistentConnection = true; // Track pending responses to support client pipelining: https://tools.ietf.org/html/rfc7230#section-6.3.2 private int pendingResponses; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // read message and track if it was keepAlive if (msg instanceof HttpRequest) { final HttpRequest request = (HttpRequest) msg; if (persistentConnection) { pendingResponses += 1; persistentConnection = isKeepAlive(request); } } super.channelRead(ctx, msg); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { // modify message on way out to add headers if needed if (msg instanceof HttpResponse) { final HttpResponse response = (HttpResponse) msg; trackResponse(response); // Assume the response writer knows if they can persist or not and sets isKeepAlive on the response if (!isKeepAlive(response) || !isSelfDefinedMessageLength(response)) { // No longer keep alive as the client can't tell when the message is done unless we close connection pendingResponses = 0; persistentConnection = false; } // Server might think it can keep connection alive, but we should fix response header if we know better if (!shouldKeepAlive()) { setKeepAlive(response, false); } } if (msg instanceof LastHttpContent && !shouldKeepAlive()) { promise = promise.unvoid().addListener(ChannelFutureListener.CLOSE); } super.write(ctx, msg, promise); } private void trackResponse(HttpResponse response) { if (!isInformational(response)) { pendingResponses -= 1; } } private boolean shouldKeepAlive() { return pendingResponses != 0 || persistentConnection; }
Keep-alive only works if the client can detect when the message has ended without relying on the connection being closed.

  • See
  • See
  • See
Params:
  • response – The HttpResponse to check
Returns:true if the response has a self defined message length.
/** * Keep-alive only works if the client can detect when the message has ended without relying on the connection being * closed. * <p> * <ul> * <li>See <a href="https://tools.ietf.org/html/rfc7230#section-6.3"/></li> * <li>See <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2"/></li> * <li>See <a href="https://tools.ietf.org/html/rfc7230#section-3.3.3"/></li> * </ul> * * @param response The HttpResponse to check * * @return true if the response has a self defined message length. */
private static boolean isSelfDefinedMessageLength(HttpResponse response) { return isContentLengthSet(response) || isTransferEncodingChunked(response) || isMultipart(response) || isInformational(response) || response.status().code() == HttpResponseStatus.NO_CONTENT.code(); } private static boolean isInformational(HttpResponse response) { return response.status().codeClass() == HttpStatusClass.INFORMATIONAL; } private static boolean isMultipart(HttpResponse response) { String contentType = response.headers().get(HttpHeaderNames.CONTENT_TYPE); return contentType != null && contentType.regionMatches(true, 0, MULTIPART_PREFIX, 0, MULTIPART_PREFIX.length()); } }