Изменение примера файлового сервера Netty 4 HTTP для использования ChunkedStream вместо ChunkedFile

Я пытаюсь понять, как Netty 4 реализует HTTP-сервер, который обслуживает тела HttpResponses, используя кодирование передачи по частям, когда общий размер данных неизвестен.

В качестве отправной точки я просто изменил HttpStaticFileServerHandler (находится в https://github.com/netty/netty/tree/netty-4.0.0.CR1/example/src/main/java/io/netty/example/http/file), чтобы использовать ChunkedStream вместо ChunkedFile (они оба являются ChunkedByteInputs).

Я понимаю, что в исходном примере использовать FileInputStream не идеально, но я думаю, что это хороший пример повторного использования уже известного кода.

Итак, вот сравнение с классом HttpStaticFileServerHandler из пакета io.netty.example.http.file (по сравнению с 4.0.0.CR1):

diff --git a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java
index 904579b..0d3592f 100644
--- a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java
+++ b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java
@@ -27,13 +27,14 @@ import io.netty.handler.codec.http.FullHttpResponse;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.HttpResponse;
 import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.stream.ChunkedFile;
+import io.netty.handler.stream.ChunkedStream;
 import io.netty.util.CharsetUtil;

 import javax.activation.MimetypesFileTypeMap;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.RandomAccessFile;
+import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.text.SimpleDateFormat;
@@ -159,17 +160,15 @@ public class HttpStaticFileServerHandler extends ChannelInboundMessageHandlerAda
             }
         }

-        RandomAccessFile raf;
+        InputStream raf; // Use an InputStream instead of a RandomAccessFile
         try {
-            raf = new RandomAccessFile(file, "r");
+            raf = new FileInputStream(file);
         } catch (FileNotFoundException fnfe) {
             sendError(ctx, NOT_FOUND);
             return;
         }
-        long fileLength = raf.length();

         HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
-        setContentLength(response, fileLength);
         setContentTypeHeader(response, file);
         setDateAndCacheHeaders(response, file);
         if (isKeepAlive(request)) {
@@ -180,7 +179,7 @@ public class HttpStaticFileServerHandler extends ChannelInboundMessageHandlerAda
         ctx.write(response);

         // Write the content.
-        ChannelFuture writeFuture = ctx.write(new ChunkedFile(raf, 0, fileLength, 8192));
+        ChannelFuture writeFuture = ctx.write(new ChunkedStream(raf)); // Use a ChunkedStream instead of a ChunkedFile

         // Decide whether to close the connection or not.
         if (!isKeepAlive(request)) {

А вот и полностью измененный файл: https://gist.github.com/eskatos/5311587

Изменения минимальны: используйте FileInputStream вместо RandomAccessFile и ChunkedStream вместо ChunkedFile. Трубопровод не тронут.

Чтобы воспроизвести, просто примените изменения к примеру Netty, запустите его и попробуйте загрузить любой файл.

После этого изменения список каталогов, очевидно, работает, потому что ответы не разбиваются на части, а загрузки файлов - нет. Клиент загружает файл, но никогда не заканчивает загрузку, удерживает соединение и ждет вечно. Я пробовал несколько браузеров для curl, wget и т. Д. Я также пытался добавить ByteLoggingHandler в конвейер, и я вижу пустой завершающий фрагмент, поэтому я не понимаю, почему браузер все еще ждет данных.

Любая подсказка?


person eskatos    schedule 05.04.2013    source источник


Ответы (2)


Чтобы прервать передачу фрагментов неизвестного размера (Content-Length неизвестен и, следовательно, не указан), вы просто отправляете пустой фрагмент в качестве последнего фрагмента. Это позволяет держать соединение открытым для поддержки поддержки активности:

        ctx.write(new ChunkedInputAdapter(new ChunkedStream(raf, 8192)));
        ChannelFuture writeFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);

Вот полный пример: https://github.com/scireum/sirius/blob/develop/web/src/sirius/web/http/Response.java#L649

person Andreas Haufler    schedule 07.01.2014
comment
Недавно мы добавили реализацию ChunkedInput, посвященную этому: https://github.com/netty/netty/blob/master/codec-http/src/main/java/io/netty/handler/codec/http/HttpChunkedInput.java - person trustin; 19.01.2014
comment
Приятно видеть: как оказалось, я уже сделал то же самое для решения проблем со сжатием контента: andreas.haufler.info/2014/01/. Может быть, вы, ребята, могли бы также улучшить HttpContentCompressor (без сжатия jpeg, zip и т. д. или крошечных файлов...). Я буду рад предоставить запрос на включение... - person Andreas Haufler; 22.01.2014
comment
Конечно! Позвольте мне с нетерпением ждать вашего запроса на вытягивание / предложения функции и т. Д. - person trustin; 22.01.2014

Если вы не укажете заголовок Content-Length, клиент не имеет представления о длине отправляемого вами контента и, таким образом, ждет, пока сервер не закроет соединение, и все полученное до момента отключения считается контентом.

Поэтому необходимо выполнить одно из следующих действий:

  1. Добавить заголовок Content-Length
  2. Закройте соединение после отправки содержимого
  3. Отправьте контент, используя кодирование по частям с заголовком Transfer-Encoding: chunked
person trustin    schedule 08.04.2013
comment
HTTP 1.1 допускает отсутствие заголовка Content-Length и использование фрагментированного кодирования. w3.org/Protocols/rfc2616/rfc2616-sec4.html# сек.4.4 - person Mike Pone; 04.12.2013
comment
@MikePone: я думаю, вы, должно быть, неправильно прочитали ответ доверия. Ваш комментарий можно перефразировать, поскольку HTTP 1.1 допускает ваш вариант № 3, что верно, но как-то странно. - person ruakh; 21.08.2016