9

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

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

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

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

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

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

4

5 回答 5

9

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

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

<FilesMatch "\.swf$">
  SetEnv no-gzip 1
</FilesMatch>
于 2014-02-18T04:24:26.823 回答
0

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

RFC2616 第 4 节

4.4 消息长度

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

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

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

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

 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”,并且没有另外指定传输长度,则该自定界媒体类型定义传输长度。除非发送者知道接收者可以解析它,否则不得使用此媒体类型;来自 1.1 客户端的具有多个字节范围说明符的 Range 标头的存在意味着客户端可以解析 multipart/byteranges 响应。

   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),强制“Chunked”的一个好方法是设置 Transfer-Encoding 和 to flush。也许这两个不是严格需要的。在开始缓冲之前,是否会有任何多余的刷新?

于 2013-02-01T04:57:39.667 回答
0

我最好的猜测是你有一些变化,modules/http/http_filters因为 Apache 默认发送Content-Length.

于 2013-01-30T04:03:34.647 回答
0

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()缓冲区被刷新(或丢弃)之前不会调用回调,此时使用header(). 我猜你没有打开错误日志,我在我的错误日志中看到了这个:

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

您可以将它们放入<Location><LocationMatch>例如,或使用mod_rewriteenv”标志进行更多控制。

于 2013-02-01T12:54:28.080 回答
0

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

于 2020-01-03T22:26:45.063 回答