2

当总数据大小未知时,我试图围绕 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 并不理想,但我认为它是重用已知代码的一个很好的示例。

因此,这里是与 io.netty.example.http.file 包中的 HttpStaticFileServerHandler 类的差异(与 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 添加到管道中,我可以看到一个空的尾随块,所以我不明白为什么浏览器仍在等待数据。

有什么线索吗?

4

2 回答 2

1

如果你不指定Content-Lengthheader,客户端不知道你发送的内容的长度,因此它会一直等到服务器关闭连接,直到断开连接之前收到的所有内容都被认为是内容。

因此,您必须执行以下操作之一:

  1. 添加Content-Length标题
  2. 发送内容后关闭连接
  3. Transfer-Encoding: chunked使用带有标头的分块编码发送内容
于 2013-04-08T02:49:08.957 回答
1

为了终止未知大小的分块传输(内容长度未知,因此未指定),您只需发送一个空块作为最后一个块。这允许保持连接打开以支持keepalive:

        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

于 2014-01-07T22:08:37.003 回答