Netty 的 HttpObjectAggregator 似乎错过了 HTTP 块
Netty's HttpObjectAggregator appears to miss HTTP chunks
我在 Java 中使用 Netty 框架开发异步 HTTP 客户端,遇到了一些与分块编码相关的问题。客户端正在连接到发出 JSON 响应的 REST 服务,可以通过长轮询访问这些响应。服务器使用分块编码进行响应,因此我在处理每个响应之前利用 Netty 的 HttpObjectAggregator
重新 assemble 分块。我遇到的问题是,对于大约 1/2 的长轮询请求,我的 HTTP 处理程序仅获得部分 JSON 响应。通常发出一次或两次相同的请求会导致提供完整的请求。
我解决问题的步骤:
- 使用
HttpContentDecompressor
- Netty 版本 5.0.0.Alpha2、4.1.0.Beta5、4.0.29.Final
- 消除了
HttpContentDecompressor
不够 'large' 的可能性,给它足够的空间来保存响应
我不知道的事情
如果 Netty 确实是问题所在:这可能只是一个糟糕的网络服务,但它是用 SSL 加密的,我不知道如何在 Netty 收到 assembled
为什么这只发生在某些请求中。通常同一个请求重试一次或两次即可解决问题
我的目标:可靠地 assemble 将块合并为一个整体。
如果有任何关于调试这个的建议,我将不胜感激!
编辑:正如Bayou.io指出的那样,我混淆了分块编码重组和gzip inflation 的顺序。但是,我也尝试过不使用 gzip 编码,但出现了同样的错误。
一些代码:
这是我配置 HTTP 客户端的地方
/**
* Establishes a connection, or throws an exception if one cannot be made
* @throws Exception If there is a problem connecting to {@link #mUri}
*/
private void connect() throws Exception {
mGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(mGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
/* all channel IO goes through the pipeline */
ChannelPipeline p = ch.pipeline();
/* handles read timeout */
p.addLast(new ReadTimeoutHandler(mTimeout));
/* handles SSL handshake if needed */
if (mUri.getScheme().equalsIgnoreCase("https"))
p.addLast(sslContext.newHandler(ch.alloc(), mUri.getHost(), mUri.getPort()));
/* converts to HTTP response */
p.addLast(new HttpClientCodec());
/* decompress GZIP if needed */
p.addLast(new HttpContentDecompressor());
/* aggregates chunked responses */
p.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
/* handles response for child class */
configureCustomPipelines(p, mCallback);
}
});
mChannel = b.connect(mUri.getHost(), mUri.getPort()).sync().channel();
}
configureCustomPipelines
中配置的处理程序如下class(省略不必要的细节):
public abstract class BaseHttpHandler extends SimpleChannelInboundHandler<HttpObject> {
...
/**
* Processes the response and ensures that the correct callback is invoked,
* and then that the HttpClient is shutdown
*/
@Override
public synchronized void messageReceived(ChannelHandlerContext ctx, HttpObject msg) {
if (!mHandled) {
if (msg instanceof FullHttpResponse) {
HttpResponse response = (FullHttpResponse) msg;
int status = response.status().code();
if (status < 200 || status > 299) {
handleBadResponse(response, status);
mHandled = true;
} else {
HttpContent content = (HttpContent) msg;
String body = content.content().toString(
0,
content.content().writerIndex(),
CharsetUtil.UTF_8);
if (body.length() > 0) {
handleMessageBody(body, status);
mHandled = true;
}
}
}
}
if (mHandled)
shutdown(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
shutdown(ctx);
}
private void shutdown(ChannelHandlerContext ctx) {
ctx.channel().close();
ctx.channel().eventLoop().shutdownGracefully();
}
}
我知道响应被缩短了,因为下面的处理函数无法解析 JSON 正文。经过进一步检查,似乎 JSON 字符串突然结束:
if (body.length() > 0) {
handleMessageBody(body, status);
mHandled = true;
}
我能够确定 Netty 不是问题所在。事实证明,HTTP 服务器定期无法发送某些响应。我能够使用管道中的自定义流转储处理程序来确定这一点,该处理程序将 Netty 接收到的(post-ssl 解密)内容写入文件。手动检查这些后,问题就很清楚了。
我在 Java 中使用 Netty 框架开发异步 HTTP 客户端,遇到了一些与分块编码相关的问题。客户端正在连接到发出 JSON 响应的 REST 服务,可以通过长轮询访问这些响应。服务器使用分块编码进行响应,因此我在处理每个响应之前利用 Netty 的 HttpObjectAggregator
重新 assemble 分块。我遇到的问题是,对于大约 1/2 的长轮询请求,我的 HTTP 处理程序仅获得部分 JSON 响应。通常发出一次或两次相同的请求会导致提供完整的请求。
我解决问题的步骤:
- 使用
HttpContentDecompressor
- Netty 版本 5.0.0.Alpha2、4.1.0.Beta5、4.0.29.Final
- 消除了
HttpContentDecompressor
不够 'large' 的可能性,给它足够的空间来保存响应
我不知道的事情
如果 Netty 确实是问题所在:这可能只是一个糟糕的网络服务,但它是用 SSL 加密的,我不知道如何在 Netty 收到 assembled
为什么这只发生在某些请求中。通常同一个请求重试一次或两次即可解决问题
我的目标:可靠地 assemble 将块合并为一个整体。
如果有任何关于调试这个的建议,我将不胜感激!
编辑:正如Bayou.io指出的那样,我混淆了分块编码重组和gzip inflation 的顺序。但是,我也尝试过不使用 gzip 编码,但出现了同样的错误。
一些代码:
这是我配置 HTTP 客户端的地方
/**
* Establishes a connection, or throws an exception if one cannot be made
* @throws Exception If there is a problem connecting to {@link #mUri}
*/
private void connect() throws Exception {
mGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(mGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
/* all channel IO goes through the pipeline */
ChannelPipeline p = ch.pipeline();
/* handles read timeout */
p.addLast(new ReadTimeoutHandler(mTimeout));
/* handles SSL handshake if needed */
if (mUri.getScheme().equalsIgnoreCase("https"))
p.addLast(sslContext.newHandler(ch.alloc(), mUri.getHost(), mUri.getPort()));
/* converts to HTTP response */
p.addLast(new HttpClientCodec());
/* decompress GZIP if needed */
p.addLast(new HttpContentDecompressor());
/* aggregates chunked responses */
p.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
/* handles response for child class */
configureCustomPipelines(p, mCallback);
}
});
mChannel = b.connect(mUri.getHost(), mUri.getPort()).sync().channel();
}
configureCustomPipelines
中配置的处理程序如下class(省略不必要的细节):
public abstract class BaseHttpHandler extends SimpleChannelInboundHandler<HttpObject> {
...
/**
* Processes the response and ensures that the correct callback is invoked,
* and then that the HttpClient is shutdown
*/
@Override
public synchronized void messageReceived(ChannelHandlerContext ctx, HttpObject msg) {
if (!mHandled) {
if (msg instanceof FullHttpResponse) {
HttpResponse response = (FullHttpResponse) msg;
int status = response.status().code();
if (status < 200 || status > 299) {
handleBadResponse(response, status);
mHandled = true;
} else {
HttpContent content = (HttpContent) msg;
String body = content.content().toString(
0,
content.content().writerIndex(),
CharsetUtil.UTF_8);
if (body.length() > 0) {
handleMessageBody(body, status);
mHandled = true;
}
}
}
}
if (mHandled)
shutdown(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
shutdown(ctx);
}
private void shutdown(ChannelHandlerContext ctx) {
ctx.channel().close();
ctx.channel().eventLoop().shutdownGracefully();
}
}
我知道响应被缩短了,因为下面的处理函数无法解析 JSON 正文。经过进一步检查,似乎 JSON 字符串突然结束:
if (body.length() > 0) {
handleMessageBody(body, status);
mHandled = true;
}
我能够确定 Netty 不是问题所在。事实证明,HTTP 服务器定期无法发送某些响应。我能够使用管道中的自定义流转储处理程序来确定这一点,该处理程序将 Netty 接收到的(post-ssl 解密)内容写入文件。手动检查这些后,问题就很清楚了。