![在 Apache 中启用 gzip 压缩时未发送 Content-Length?](https://linux22.com/image/525327/%E5%9C%A8%20Apache%20%E4%B8%AD%E5%90%AF%E7%94%A8%20gzip%20%E5%8E%8B%E7%BC%A9%E6%97%B6%E6%9C%AA%E5%8F%91%E9%80%81%20Content-Length%EF%BC%9F.png)
我真的非常感谢有人能帮助我理解 Apache 的行为。
我正在从 application/json 中的 iPhone Objective-C 应用程序与 PHP 进行通信。服务器上启用了 Gzip 压缩,并由客户端请求。
来自我的.htaccess:
AddOutputFilterByType DEFLATE text/html text/plain text/xml application/x-httpd-php application/json
对于较小的请求,Apache 会设置“Content-Length”标头。例如(这些值在 Objective-C 中从标头输出):
Connection = "Keep-Alive";
"Content-Encoding" = gzip;
"Content-Length" = 185; <-------------
"Content-Type" = "application/json";
Date = "Wed, 22 Sep 2010 12:20:27 GMT";
"Keep-Alive" = "timeout=3, max=149";
Server = Apache;
Vary = "Accept-Encoding";
"X-Powered-By" = "PHP/5.2.13";
"X-Uncompressed-Content-Length" = 217;
X-未压缩内容长度是我添加的标头,设置为未压缩的 JSON 字符串的大小。
如您所见,这个请求非常小(217字节)。
以下是较大请求的标头(282888 字节):
Connection = "Keep-Alive";
"Content-Encoding" = gzip;
"Content-Type" = "application/json";
Date = "Wed, 22 Sep 2010 12:20:29 GMT";
"Keep-Alive" = "timeout=3, max=148";
Server = Apache;
"Transfer-Encoding" = Identity;
Vary = "Accept-Encoding";
"X-Powered-By" = "PHP/5.2.13";
"X-Uncompressed-Content-Length" = 282888;
请注意,没有给出 Content-Length。
我的问题:
- 为什么 Apache 不发送较大请求的 Content-Length?
- 设置了“Contend-Encoding = gzip”是否意味着 gzip 压缩仍然适用于较大的请求,即使我无法验证大小差异?
- 有没有什么办法可以让 Apache 为这些较大的请求包含实际的内容长度,以便更准确地向用户报告数据使用情况?
这个应用程序可以在昂贵的数据计划上使用,因此我希望向用户报告实际使用情况,而不是 30-70% 的虚增使用情况(额外的几百 KB 可能听起来并不多 - 但这些计划的费用为每 MB 1 到 10 美元!)。
提前致谢。
答案1
对 Martin Fjordvalds 的回答补充:
仅当压缩文件大小大于 DeflateBufferSize 时,Apache 才会使用分块编码。因此,增加此缓冲区大小将阻止服务器对较大的文件使用分块编码,从而导致即使对于压缩数据也会发送 Content-Length。
更多信息请点击这里:http://httpd.apache.org/docs/2.2/mod/mod_deflate.html#deflatebuffersize
答案2
听起来 Apache 正在执行分块编码,这意味着它可以在数据被 gzip 压缩时发送数据,而不是等待完整响应被 gzip 压缩。这是相当标准的做法,但我对 Apache 不太熟悉,无法判断是否可以禁用它。
答案3
好的,我设法解决了这个问题。正如 Martin F 正确指出的那样,Apache 正在对回复进行分块,因此内容大小是未知的。对于许多人来说,这是可取的(页面加载速度更快)。但代价是无法报告下载进度。
对于像我这样真正想报告下载进度的人来说,如果你使用 Apache 或 PHP 的自动 gzip 支持,那么你几乎无能为力。解决方案是手动执行。这比听起来容易:
如果要发送整个文件,那么这是一个很好的示例,在 PHP 中强制发送单个块(使用 Content-Length): http://www.php.net/manual/en/function.ob-start.php#94741
如果您要发送生成的数据,请使用 gzencode 对数据进行编码,如上例所示。先决条件是所有输出数据都存储在变量中(如果您需要缓冲,则可以使用 ob_start 来帮助实现这一点,然后获取缓冲区的内容)。
// $replyBody is the entire contents of your reply
header("Content-Type: application/json"); // or whatever yours is
// checks if gzip is supported by client
$pack = true;
if(empty($_SERVER["HTTP_ACCEPT_ENCODING"]) || strpos($_SERVER["HTTP_ACCEPT_ENCODING"], 'gzip') === false)
{
$pack = false;
}
// if supported, gzips data
if($pack) {
header("Content-Encoding: gzip");
$replyBody = gzencode($replyBody, 9, FORCE_GZIP);
}
// compressed or not, sets the Content-Length
header("Content-Length: " . mb_strlen($replyBody, 'latin1'));
// outputs reply & exits
echo $replyBody;
exit;
瞧!
自己做的另一个好处是可以设置压缩级别。这对我的移动应用程序来说很棒,因为我可以设置为最高压缩级别(这样我的用户的数据费用就更少了!)——而服务器可能只使用中等压缩级别以获得更好的 CPU/大小权衡。我认为只有编辑 httpd.conf 才能更改压缩级别(在共享主机上,我不能编辑)。
因此,我保留了 DEFLATE .htaccess 指令,但不包括我现在以上述方式编码的 application/json 回复。
再次感谢 Martin F,你给了我解决这个问题所需的灵感 :)