编写多个http响应有什么限制吗?

时间:2012-07-03 10:31:05

标签: http proxy netty pipelining

我正在构建一个带有netty的HTTP proxy,它支持HTTP流水线操作。因此,我在单个通道上收到多个HttpRequest对象,并获得匹配的HttpResponse个对象。 HttpResponse写入的顺序与HttpRequest的顺序相同。如果写了HttpResponse,则在HttpProxyHandler收到writeComplete事件时会写下一个。{/ p>

管道应该很方便:

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());

关于this question,只有写调用的顺序应该是重要的,但是为了确保我构建另一个Handler(HttpResponseWriteDelayHandler)来抑制writeComplete事件,直到整个响应被写入

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

我有一些发现:

  • 如果从代理到服务器的连接速度快于从代理到浏览器的连接,则只会出现此问题。
  • 在该连接上发送更大的图像后,问题更频繁发生,例如: 20KB
  • 如果仅发送304 - Not Modified个响应(考虑浏览器缓存刷新页面),则不会出现此问题。
  • 设置bootstrap.setOption("sendBufferSize", 1048576);或更高版本无效
  • writeComplete中发送HttpResponseWriteDelayHandler事件之前,依赖于响应体大小的时间范围可以解决问题,但这是一个非常糟糕的解决方案。

1 个答案:

答案 0 :(得分:0)

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

HttpResponse的内容太大了。要分析整个HTML文档在缓冲区中的内容。这必须再次在Chunks中拆分才能正确发送。如果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() {}
}

然后可以只调用channel.write(new ChunkedHttpResponse(response),并在需要时自动完成分块。