'Sending an unbidden 408 response from Netty on connection timeout

According to the specs an HTTP server should send a 408 if it hasn't received a request in a certain time. This is a bit unintuitive as it means you can send a response without having received a request. One purpose is to kill long-lived keep-alive HTTP 1.1 connections that clients haven't closed.

To do this, I added an IdleStateEvent event and in there:

DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, 
                                          HttpResponseStatus.REQUEST_TIMEOUT);
resp.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
ctx.writeAndFlush(resp)
    .addListener(future -> {
        System.out.println("Finished " + future.cause());
    })
    .addListener(ChannelFutureListener.CLOSE);

And the output:

Finished io.netty.handler.codec.EncoderException: java.lang.IllegalStateException: cannot send more responses than requests

Is there a way to do this in Netty? Or a recommended way to close idle HTTP 1.1 connections?



Solution 1:[1]

According to the Netty javadoc you can use the IdleStateHandler class to close idle connections. Apparently, this handler will trigger an IdleStateEvent when a connection has no reads, or no writes, or both, for a period of time. This event can then be used to trigger shutting down a connection ... or to do other things.

The following example is copied from the javadoc:

 // An example that sends a ping message when there is no outbound traffic
 // for 30 seconds.  The connection is closed when there is no inbound traffic
 // for 60 seconds.

 public class MyChannelInitializer extends ChannelInitializer<Channel> {
      @Override
     public void initChannel(Channel channel) {
         channel.pipeline().addLast("idleStateHandler", new IdleStateHandler(60, 30, 0));
         channel.pipeline().addLast("myHandler", new MyHandler());
     }
 }

 // Handler should handle the IdleStateEvent triggered by IdleStateHandler.
 public class MyHandler extends ChannelDuplexHandler {
      @Override
     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
         if (evt instanceof IdleStateEvent) {
             IdleStateEvent e = (IdleStateEvent) evt;
             if (e.state() == IdleState.READER_IDLE) {
                 ctx.close();
             } else if (e.state() == IdleState.WRITER_IDLE) {
                 ctx.writeAndFlush(new PingMessage());
             }
         }
     }
 }

 ServerBootstrap bootstrap = ...;
 ...
 bootstrap.childHandler(new MyChannelInitializer());
 ...

Note: according to this Q&A, the IdleStateHandler should be the first handler in the pipeline.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Stephen C