Существуют ли какие-либо ограничения на написание нескольких HTTP-ответов?

Я создаю HTTP-прокси с помощью netty, который поддерживает конвейерную обработку HTTP. Поэтому я получаю несколько объектов HttpRequest на одном канале и получаю соответствующие объекты HttpResponse. Порядок записи HttpResponse такой же, как у HttpRequest. Если было записано HttpResponse, следующее будет записано, когда HttpProxyHandler получит событие writeComplete.

Pipeline должен быть удобным:

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

Относительно этого вопроса важен только порядок вызовов записи, но для верности я создаю еще один обработчик (HttpResponseWriteDelayHandler), который подавляет событие writeComplete до тех пор, пока не будет записан весь ответ.

Чтобы проверить это, я включил network.http.proxy.pipelining в Firefox и посетил страницу с большим количеством изображений и ссылок (страница новостей). Проблема в том, что браузер не получает некоторые ответы, несмотря на то, что логи прокси считают их успешно отправленными.

У меня есть некоторые выводы:

  • Проблема возникает только в том случае, если соединение прокси-сервера с сервером быстрее, чем соединение прокси-сервера с браузером.
  • Проблема возникает чаще после отправки большего изображения по этому соединению, например. 20 КБ
  • Проблема не возникает, если было отправлено только 304 - Not Modified ответов (обновление страницы с учетом кеша браузера)
  • Установка bootstrap.setOption("sendBufferSize", 1048576); или выше не помогает
  • Задержка периода времени, зависящего от размера тела ответа, перед отправкой события writeComplete в HttpResponseWriteDelayHandler решает проблему, но это очень плохое решение.

person Julian    schedule 03.07.2012    source источник


Ответы (1)


Я нашел решение и хочу поделиться им, если у кого-то еще есть аналогичная проблема:

Содержимое 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() {}
}

Затем можно вызвать только channel.write(new ChunkedHttpResponse(response), и разбиение на фрагменты выполняется автоматически, если это необходимо.

person Julian    schedule 12.07.2012