在 Apache 中启用 gzip 压缩时未发送 Content-Length?

在 Apache 中启用 gzip 压缩时未发送 Content-Length?

我真的非常感谢有人能帮助我理解 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。

我的问题:

  1. 为什么 Apache 不发送较大请求的 Content-Length?
  2. 设置了“Contend-Encoding = gzip”是否意味着 gzip 压缩仍然适用于较大的请求,即使我无法验证大小差异?
  3. 有没有什么办法可以让 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,你给了我解决这个问题所需的灵感 :)

相关内容