Content-Length 标头与分块编码

Posted

技术标签:

【中文标题】Content-Length 标头与分块编码【英文标题】:Content-Length header versus chunked encoding 【发布时间】:2011-01-26 00:45:41 【问题描述】:

我正在权衡设置Content-Length HTTP 标头与使用分块编码从我的服务器返回 [可能] 大文件的利弊。需要一种或另一种来符合使用持久连接的 HTTP 1.1 规范。我看到Content-Length 标头的优势是:

下载对话框可以显示准确的进度条 客户预先知道文件是否可能/可能不会太大而无法摄取

缺点是必须在返回对象之前计算大小,这并不总是实用的,并且可能会增加服务器/数据库的利用率。分块编码的缺点是在每个块和下载进度条之前添加块大小的开销很小。有什么想法吗?我可能没有想到的这两种方法的任何其他 HTTP 考虑事项?

【问题讨论】:

是否假定您的内容是静态的并且其长度是先验已知的?如果没有,对于大文件,分块会更快。 【参考方案1】:

内容长度

Content-Length 标头确定请求/响应正文的字节长度。如果您忽略指定 Content-Length 标头,HTTP 服务器将隐式添加 Transfer-Encoding: chunked 标头。 Content-LengthTransfer-Encoding 标头不应一起使用。接收方将不知道正文的长度,也无法估计下载完成时间。如果您确实添加了Content-Length 标头,请确保它以字节为单位匹配整个正文,如果不正确,则接收者的行为未定义。

Content-Length 标头不允许流式传输,但它对于希望支持部分内容服务的大型二进制文件很有用。这基本上意味着可恢复下载、暂停下载、部分下载和多宿主下载。这需要使用名为Range 的附加标头。这种技术称为Byte serving。

传输编码

Transfer-Encoding: chunked 的使用允许在单个请求或响应中进行流式传输。这意味着数据以分块的方式传输,不会影响内容的表示。

HTTP 客户端的正式用途是发送带有TE 标头字段的请求,该字段指定客户端愿意接受的传输编码类型。这并不总是发送,但大多数服务器假定客户端可以处理 chunked 编码。

chunked 传输编码更好地利用了持久的 TCP 连接,HTTP 1.1 默认假定为真。

内容编码

还可以压缩分块或非分块数据。这实际上是通过 Content-Encoding 标头完成的。

注意Content-Length 等于Content-Encoding 之后的正文长度。这意味着如果您对响应进行了 gzip 压缩,则长度计算将在压缩后进行。如果您想计算长度,则需要能够将整个正文加载到内存中(除非您在其他地方有该信息)。

使用分块编码进行流式传输时,压缩算法还必须支持在线处理。幸运的是,gzip 支持流压缩。我相信内容会先被压缩,然后再切成块。这样,块被接收,然后解压缩以获取真实内容。如果反过来,你会得到压缩流,然后解压缩会给我们块。这没有意义。

一个典型的压缩流响应可能有这些头:

Content-Type: text/html
Content-Encoding: gzip
Transfer-Encoding: chunked

从语义上讲,Content-Encoding 的用法表示“端到端”编码方案,这意味着只有最终客户端或最终服务器应该对内容进行解码。中间的代理不应该解码内容。

如果你想让中间的代理解码内容,正确使用的标头实际上是Transfer-Encoding 标头。如果 HTTP 请求具有 TE: gzip chunked 标头,则以 Transfer-Encoding: gzip chunked 响应是合法的。

但是这很少被支持。所以你现在应该只使用Content-Encoding进行压缩。

Chunked vs Store & Forward

【讨论】:

【参考方案2】:

如果事先知道内容长度,那么我当然更喜欢它而不是分块发送。如果在本地磁盘文件系统或数据库中有静态文件的方式,那么任何自尊的编程语言和 RDBMS 都提供了预先获取内容长度的方法。你应该好好利用它。

另一方面,如果内容长度事先确实无法预测(例如,当您的意图是将多个文件压缩在一起并将其作为一个发送时),那么以块的形式发送可能比在服务器内存中缓冲或写入要快首先到本地磁盘文件系统。但这确实会对用户体验产生负面影响,因为下载进度是未知的。然后,不耐烦的人可能会中止下载并继续前进。

预先知道内容长度的另一个好处是能够恢复下载。我在您的帖子历史中看到您的主要编程语言是 Java;您可以找到 here 一篇包含更多技术背景信息的文章和一个 Java Servlet 示例。

【讨论】:

【参考方案3】:

绝对使用 Content-Length。这样的服务器利用率将几乎不存在,并且对您的用户的好处将是巨大的。

对于动态内容,添加压缩响应支持 (gzip) 也非常简单。这需要输出缓冲,从而为您提供内容长度。 (不适用于文件下载或已压缩的内容(声音、图像))。

考虑同时添加对部分内容/字节范围服务的支持 - 即重新启动下载的功能。 See here for a byte-range example(该示例使用 php,但适用于任何语言)。提供部分内容时需要 Content-Length。

当然,这些都不是灵丹妙药:对于流媒体,使用输出缓冲或响应大小毫无意义;对于大文件,输出缓冲没有意义,但 Content-Length 和字节服务很有意义(重新启动失败的下载是可能的)。

就我个人而言,只要我知道,我就会提供 Content-Length;对于文件下载,检查文件大小在资源方面是微不足道的。结果:用户有一个确定的进度条(由于 gzip,动态页面下载速度更快)。

【讨论】:

我看不出字节范围服务(基本上:“恢复下载”)在这种特殊情况下有什么好处。这即要求事先知道内容长度。然后,您可以设置内容长度。 @BalusC: Content-Length 是字节服务的先决条件。典型用例:用户通过她的 WiFi 连接下载一个 10MB 的文件,下载时信号下降了 7MB。没有简历,她又要重新下载整个10MB,这对她来说是相当烦人的;有了简历,只剩下 3 MB 的空间了。大多数现代浏览器都支持这一点。 是的,我知道。也许你没有理解我?我只是说我不明白这与“内容长度”与“内容长度”有什么关系。 “传输编码:分块”问题。顺便说一句,OP 的帖子历史告诉我,他的主要语言是 Java,在这种情况下,这个 FileServlet 示例可能更有用:balusc.blogspot.com/2009/02/… @BalusC:问题的最后一句话:“我可能没有想到的这两种方法的任何其他 HTTP 考虑因素?”使用 Content-Length 时,可以添加此功能;而使用 Transfer-Encoding: chunked,这是不可能的。 是的,这是真的。顺便说一句:GZIP 不需要输出缓冲。默认情况下以分块编码发送。至少,在 Java servletcontainers 中。

以上是关于Content-Length 标头与分块编码的主要内容,如果未能解决你的问题,请参考以下文章

分块编码和内容长度标头

HTTP之实体和编码

用于分块编码 POST 的 HTTP 标头 - 错误 411

防止 Apache 对 gzip 内容进行分块

标头传输编码:分块并请求为空

HTTP协议之chunk编码(分块传输编码)