1

我正在用 netty 构建一个HTTP 代理,它支持 HTTP 流水线。因此,我HttpRequest在单个通道上接收到多个对象并获得了匹配的HttpResponse对象。写入的顺序HttpResponse与我得到的相同HttpRequest。如果 aHttpResponse被写入,下一个将在HttpProxyHandler收到writeComplete事件时写入。

管道应该很方便:

final ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("writer", new HttpResponseWriteDelayHandler());
pipeline.addLast("deflater", new HttpContentCompressor(9));
pipeline.addLast("handler", new HttpProxyHandler());

关于这个问题,只有写入调用的顺序很重要,但要确保我构建了另一个 Handler ( HttpResponseWriteDelayHandler),它会抑制writeComplete事件,直到写入整个响应。

为了测试这一点,我在 Firefox 中启用network.http.proxy.pipelining并访问了一个包含许多图像和连接的页面(一个新闻页面)。问题是,浏览器没有收到一些响应,尽管代理的日志认为它们已成功发送。

我有一些发现:

  • 仅当从代理到服务器的连接比从代理到浏览器的连接快时才会出现此问题。
  • 在该连接上发送更大的图像(例如 20kB)后,问题更常发生
  • 如果仅发送响应,则不会发生此问题304 - Not Modified(刷新页面考虑浏览器缓存)
  • 设置bootstrap.setOption("sendBufferSize", 1048576);或以上没有帮助
  • writeComplete在发送事件之前休眠一个取决于响应正文大小的时间帧HttpResponseWriteDelayHandler可以解决问题,但这是一个非常糟糕的解决方案。
4

1 回答 1

0

我找到了解决方案并想分享它,如果其他人有类似的问题:

的内容HttpResponse太大了。为了分析整个 HTML 文档在缓冲区中的内容。这必须再次拆分为块才能正确发送。如果HttpResponse没有分块,我写了一个简单的解决方案来做到这一点。需要在逻辑处理程序旁边放置一个ChunkedWriteHandler并编写此类而不是响应本身:

public class ChunkedHttpResponse implements ChunkedInput {

    private final static int       CHUNK_SIZE = 8196;
    private final HttpResponse     response;
    private final Queue<HttpChunk> chunks;
    private boolean                isResponseWritten;

    public ChunkedHttpResponse(final HttpResponse response) {
        if (response.isChunked())
            throw new IllegalArgumentException("response must not be chunked");

        this.chunks = new LinkedList<HttpChunk>();
        this.response = response;
        this.isResponseWritten = false;

        if (response.getContent().readableBytes() > CHUNK_SIZE) {
            while (CHUNK_SIZE < response.getContent().readableBytes()) {
                chunks.add(new DefaultHttpChunk(response.getContent().readSlice(CHUNK_SIZE)));
            }
            chunks.add(new DefaultHttpChunk(response.getContent().readSlice(response.getContent().readableBytes())));
            chunks.add(HttpChunk.LAST_CHUNK);

            response.setContent(ChannelBuffers.EMPTY_BUFFER);
            response.setChunked(true);
            response.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
        }
    }

    @Override
    public boolean hasNextChunk() throws Exception {
        return !isResponseWritten || !chunks.isEmpty();
    }

    @Override
    public Object nextChunk() throws Exception {
        if (!isResponseWritten) {
            isResponseWritten = true;
            return response;
        } else {
            HttpChunk chunk = chunks.poll();
            return chunk;
        }
    }

    @Override
    public boolean isEndOfInput() throws Exception {
        return isResponseWritten && chunks.isEmpty();
    }

    @Override
    public void close() {}
}

然后可以调用just channel.write(new ChunkedHttpResponse(response),如果需要,分块会自动完成。

于 2012-07-12T05:31:45.690 回答