【问题标题】:Always send Content-Length in Apache?总是在 Apache 中发送 Content-Length?
【发布时间】:2013-01-22 17:28:46
【问题描述】:

我正在加载一个由 PHP 动态生成的特别大的 JSON 字符串。为了向用户提供一些反馈,我想显示下载进度。

我已经搞定了代码,它适用于静态内容,如图像、JS 文件等。但是,它似乎不适用于动态文件。

这是有道理的,因为动态文件没有可预测的内容长度,但即使我在 PHP 中添加它:

ob_start(function($c) {
    header("Content-Length: ".strlen($c));
    return $c;
});

它仍然不发送标头(但如果我添加任何其他标头,它工作正常)。

有没有办法强制 Apache 发送 Content-Length 标头?目前我唯一的选择是将输出保存到一个临时文件并重定向到它。这会起作用,但它有点乱,所以如果可能的话我宁愿避免它。

【问题讨论】:

  • ...具体来说就是Answer中的传输编码说明。
  • 感谢您的指点。它看起来不像使用分块编码(至少,Transfer-Encoding 没有出现在响应标头中)而且我似乎无法让它强制 HTTP/1.0...
  • 你试过简单的ob_start(); /* output json */ $content = ob_get_clean(); header('Content-Length: '.strlen($content)); print($content);吗?虽然您的ob_start 回调没有被执行是出乎意料的,但您当然可以期待 PHP 会发生任何意外。
  • 你能发布curl -I <url>的输出吗?

标签: apache http-content-length


【解决方案1】:

我遇到了类似的问题,但在我的情况下,Apache 没有发送 Content-Length 标头,因为响应是 gzip 压缩的。当我禁用压缩时,会正确计算并发送 Content-Length。

以下是 htaccess 设置,用于仅对 swf 文件禁用 gzip 压缩

<FilesMatch "\.swf$">
  SetEnv no-gzip 1
</FilesMatch>

【讨论】:

    【解决方案2】:

    我的最佳猜测是您在 modules/http/http_filters 中进行了一些更改,因为 Apache 默认发送 Content-Length

    【讨论】:

    • 有趣的是,我刚刚编写了一个快速的 PHP 脚本来检索文件,Content-Length 标头就在那里。但是在浏览器中xhr.getResponseHeader("Content-Length") 返回null 并且它也不会显示在控制台中。
    • @Kolink 谷歌浏览器的网络标签中有哪些标题? (当您检查该呼叫时)。并确保查看响应而不是请求标头。
    • 果然有header。好的,那么为什么 IE10 在某些调用中没有列出或确认 Content-Length 标头,而在其他调用中却没有?
    • @Kolink 现在看起来像另一个问题。你用什么来处理 XHR 调用,jQuery?顺便说一句,我认为 IE 仍然是真正不可预测的浏览器(就像 Microsoft 的所有东西一样),PHP 很危险。还有 Apache,嗯……几年前还不错,也许是时候尝试一些更快的东西了,比如基于事件循环的 Nginx?
    • 没有发生。对不起。即使它确实有效,它会有什么帮助?
    【解决方案3】:

    HTTP 协议需要一种方法来确定响应何时结束。根据

    RFC2616 Section 4

    4.4 消息长度

    消息的传输长度是消息体的长度,如 它出现在消息中;也就是说,在任何传输编码具有 被应用。当消息中包含消息正文时, 该主体的传输长度由以下之一确定(在 优先顺序):

    1.任何“不得”包含消息体的响应消息(例如 1xx、204 和 304 响应以及对 HEAD 请求的任何响应) 总是由标题字段后的第一个空行终止, 与消息中存在的实体头字段无关。

    2.如果存在 Transfer-Encoding 标头字段(第 14.41 节)并且具有除“identity”以外的任何值,则传输长度为 通过使用“分块”传输编码(第 3.6 节)定义,除非 通过关闭连接来终止消息。

    3.如果存在 Content-Length 头字段(第 14.13 节),则其在 OCTET 中的十进制值表示实体长度和 传输长度。如果出现以下情况,则不得发送 Content-Length 标头字段 这两个长度是不同的(即,如果传输编码

     header field is present). If a message is received with both a
     Transfer-Encoding header field and a Content-Length header field,
     the latter MUST be ignored.
    

    4.如果消息使用媒体类型“multipart/byteranges”,并且没有另外指定传输长度,那么这个自定界 媒体类型定义传输长度。此媒体类型不得为 除非发件人知道收件人可以解析它,否则使用它;这 存在于具有多个字节范围的 Range 标头的请求中 来自 1.1 客户端的说明符意味着客户端可以解析 多部分/字节范围响应。

       A range header might be forwarded by a 1.0 proxy that does not
       understand multipart/byteranges; in this case the server MUST
       delimit the message using methods defined in items 1,3 or 5 of
       this section.
    

    5.通过服务器关闭连接。 (关闭连接不能用于指示请求正文的结束,因为那样会离开 服务器不可能发回响应。)

    为了与 HTTP/1.0 应用程序兼容,HTTP/1.1 请求 包含消息体必须包含有效的 Content-Length 标头 字段,除非已知服务器符合 HTTP/1.1。如果一个 请求包含消息体并且没有给出 Content-Length,则 如果服务器无法确定,则应以 400(错误请求)响应 消息的长度,或者如果愿意,可以使用 411(需要长度) 坚持接收有效的内容长度。

    所有接收实体的 HTTP/1.1 应用程序必须接受 “分块”传输编码(第 3.6 节),因此允许这种机制 用于无法确定消息长度时的消息 提前。

    消息不得同时包含 Content-Length 标头字段和 非身份传输编码。如果消息确实包含非 身份传输编码,必须忽略 Content-Length。

    当在消息正文中给出 Content-Length 时 允许,其字段值必须与 消息体。 HTTP/1.1 用户代理必须通知用户 接收并检测到无效长度。

    您不能只是随机添加标题并期望它们被遵守(其他标题可以覆盖)。您首先需要控制所有可能覆盖的标头。

    根据这个问题 (How to make PHP generate Chunked response),强制“分块”的一个好方法是设置 Transfer-Encoding 和 flush。也许这两个不是严格需要的。在您开始缓冲之前,是否会有任何无关的刷新?

    【讨论】:

      【解决方案4】:

      来自ob_start 文档:

      This function will turn output buffering on. While output buffering is active no 
      output is sent from the script (other than headers), instead the output is stored 
      in an internal buffer.
      

      注意“除了标题”位——直到缓冲区被刷新(或丢弃),ob_start() 回调才会被调用,此时使用@987654324 为时已晚@。我猜你没有打开错误日志,我在我的错误日志中看到了这个:

      PHP Warning:  Cannot modify header information - headers already sent 
        in /usr/local/apache2/apps/testing/test2.php on line 6
      

      回调允许您在发送之前修改缓冲区,但由于它仅包含正文数据,因此您也不能预先添加新标头(如果您尝试,它将出现在内容中,对于分块传输的每个块一次)。

      一旦您知道大小,您的代码应该调用 header("Content-Length: ...")在任何其他输出之前。当找到Content-Length: 标头时,不会进行分块传输。

      如果你真的需要强制 HTTP/1.0(你不需要),你可以在 httpd.conf 中使用特殊变量:

      SetEnv downgrade-1.0 1
      SetEnv force-response-1.0 1
      

      例如,您可以将它们放在&lt;Location&gt;&lt;LocationMatch&gt; 中,或者使用mod_rewrite 的“env”标志进行更多控制。

      【讨论】:

        【解决方案5】:

        我有一些 1MB 左右的 JS 文件没有获得 Content-Length 标头(与其他较小的 JS 文件不同)。 Content-Length 标头对于 HTTP/2 不是强制性的,并且 Internet 连接有点不稳定,这导致了一个非常难以调试的错误,其中文件有时仅部分交付。解决方法是增加 DeflateBufferSize https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#DeflateBufferSize

        【讨论】:

          猜你喜欢
          • 2019-10-06
          • 2022-06-10
          • 2015-07-07
          • 2012-01-05
          • 1970-01-01
          • 1970-01-01
          • 2020-07-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多