【问题标题】:Netty's HttpObjectAggregator appears to miss HTTP chunksNetty 的 HttpObjectAggregator 似乎错过了 HTTP 块
【发布时间】:2015-09-16 14:33:56
【问题描述】:

我正在使用 Java 中的 Netty 框架开发一个异步 HTTP 客户端,并且遇到了一些与分块编码相关的问题。客户端连接到一个 REST 服务,该服务发出 JSON 响应,可以通过长轮询访问。服务器使用分块编码进行响应,因此我使用 Netty 的 HttpObjectAggregator 在处理每个响应之前重新组装块。我遇到的问题是,对于大约 1/2 的长轮询请求,我的 HTTP 处理程序仅获得部分 JSON 响应。通常发出一次或两次相同的请求会导致提供完整的请求。

我为解决问题所采取的步骤:

  1. 使用HttpContentDecompressor
  2. Netty 版本 5.0.0.Alpha2、4.1.0.Beta5、4.0.29.Final
  3. 消除了 HttpContentDecompressor 不够“大”的可能性,给它足够的空间来容纳响应

我不知道的事情

  1. 如果 Netty 真的是问题所在:这可能只是一个糟糕的 Web 服务,但它是用 SSL 加密的,我不知道如何在它们组装之前记录来自 Netty 的原始响应

  2. 为什么这只发生在某些请求上。通常对同一个请求重试一次或两次即可解决问题

我的目标:可靠地将这些块组装成一个整体。

非常感谢任何有关调试此问题的建议!

编辑:正如 Bayou.io 所指出的,我将分块编码重组和 gzip 膨胀的顺序混淆了。但是,我也尝试过没有 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 中配置的处理程序是以下类(不必要的细节省略):

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;
}

【问题讨论】:

  • 不确定但是否应该在 gzip 和 chunked 之间切换顺序?
  • @bayou.io,感谢您指出这一点。我肯定会切换分块编码重组和 gzip 膨胀的顺序。我在这里找到了更多信息en.wikipedia.org/wiki/Chunked_transfer_encoding“...如果同时启用了压缩和分块编码,则块编码本身不会被压缩,并且每个块中的数据不应单独压缩。远程端点可以解码传入流首先使用 Transfer-Encoding 解码,然后是指定的 Content-Encoding。"

标签: java json http netty chunked-encoding


【解决方案1】:

我能够确定 Netty 不是问题所在。事实证明,HTTP 服务器周期性地无法发送一些响应。我能够使用管道中的自定义流转储处理程序来确定这一点,该处理程序将 Netty 接收到的(ssl 解密后)内容写入文件。在手动检查这些之后,问题就很清楚了。

【讨论】:

    猜你喜欢
    • 2017-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多