안녕하세요.
Netty 기반의 http 요청을 처리하는 서버를 작성하였는데, client 에서 connection 종료시 exceptionCaught 이벤트 핸들러가 호출 되고 있습니다.
테스트를 해보면,
client 에서 get 요청 전송시 SimpleChannelInboundHandler의 channelRead0 이벤트가 2번 호출됩니다.
1번째는 HttpRequest, 2번째는 LastHttpContent 메세지가 전달되어 정상적으로 내부 로직 처리후 client로 응답 메세지를 보냅니다.
그리고 나서 exceptionCaught 이벤트가 호출되며 'java.io.IOException: 현재 연결은 원격 호스트에 의해 강제로 끊겼습니다' 에러가 발생하고 있습니다.
<Error>
ERROR 02.15 11:50:34.517 [nioEventLoopGroup-4-1 ] [HttpListenerHandler .exceptionCaught ]: 105 -
java.io.IOException: 현재 연결은 원격 호스트에 의해 강제로 끊겼습니다
at sun.nio.ch.SocketDispatcher.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:43)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
at sun.nio.ch.IOUtil.read(IOUtil.java:192)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:379)
at io.netty.buffer.UnpooledUnsafeDirectByteBuf.setBytes(UnpooledUnsafeDirectByteBuf.java:447)
at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:881)
at io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:242)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:119)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:111)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:137)
at java.lang.Thread.run(Thread.java:744)
< http 요청 / 응답 내용>
GET /testtest/users/TestUser3432124 HTTP/1.1
Host:
Connection: Keep-Alive, TE
TE: trailers
User-Agent: RPT-HTTPClient/0.3-3E
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 226
Connection: keep-alive
{
"result" : 393222,
"loginId" : "TestUser3432124",
}
<구현 코드>
public class TestHttpListenerHandler extends SimpleChannelInboundHandler<Object> {
private static final Logger logger = LoggerFactory.getLogger(TestHttpListenerHandler.class);
private HttpRequest request;
private ByteBuf byteBufContent = PooledByteBufAllocator.DEFAULT.heapBuffer();// Unpooled.buffer();
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
request = (HttpRequest) msg;
if (HttpHeaders.is100ContinueExpected(request))
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
}
if (msg instanceof HttpContent) {
HttpContent httpContent = (HttpContent) msg;
try {
byteBufContent = byteBufContent.writeBytes(httpContent.content());
} catch (IndexOutOfBoundsException e) {
logger.error(e.getMessage(), e);
}
if (httpContent instanceof LastHttpContent) {
FullHttpRequest fullHttpRequest = new DefaultFullHttpRequest(request.getProtocolVersion(), request.getMethod(), request.getUri(), byteBufContent);
fullHttpRequest.headers().set(request.headers());
FullHttpResponse fullHttpResponse = httpDispatcher.handleHttpRequestMessage(ctx, fullHttpRequest);
if (!writeResponse(ctx, fullHttpRequest, fullHttpResponse)) {
// If keep-alive is off, close the connection once the content is fully written.
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
request = null;
byteBufContent.clear();
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error("", cause);
ctx.close();
}
private boolean writeResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
// Decide whether to close the connection or not.
boolean keepAlive = HttpHeaders.isKeepAlive(request);
if (keepAlive) {
// Add 'Content-Length' header only for a keep-alive connection.
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
// Add keep alive header as per:
response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
// Write the response.
ctx.write(response);
return keepAlive;
}
}
확인 결과 client 에서 connection 종료시 FIN 패킷이 아닌 RST 패킷이 전달되고 있는데요, 이럴경우 위 에러가 발생할 수 있는지 궁금합니다.
( client 에서 보내는 요청의 http 헤더에는 Keep-Alive 설정이 되어 있습니다. )
코드상 잘 못 구현한 부분이 있다면 조언 부탁드리겠습니다.
감사합니다.