在 Apache 中启用 gzip 压缩时未发送内容长度?

Wil*_*iss 14 php compression gzip apache-2.2

我真的很感激能帮助理解这种 Apache 行为。

我正在从应用程序/json 中的 iPhone Objective-C 应用程序与 PHP 通信。Gzip 压缩在服务器上启用,并由客户端请求。

从我的.htaccess:

AddOutputFilterByType DEFLATE text/html text/plain text/xml application/x-httpd-php application/json
Run Code Online (Sandbox Code Playgroud)

对于小型请求,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;
Run Code Online (Sandbox Code Playgroud)

X-Uncompressed-Content-Length是我添加的标头,设置为未压缩 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;
Run Code Online (Sandbox Code Playgroud)

请注意,没有给出 Content-Length。

我的问题:

  1. 为什么 Apache 不为较大的请求发送 Content-Length?
  2. 设置了“Contend-Encoding=gzip”这一事实是否意味着 gzip 压缩仍在处理更大的请求,即使我无法验证大小差异?
  3. 有没有办法让 Apache 包含这些较大请求的实际内容长度,以便更准确地向用户报告数据使用情况?

此应用程序可用于昂贵的数据计划,因此我希望向用户报告实际使用情况,而不是 30-70% 的夸大使用情况(额外的几百 KB 听起来可能不多——但这些计划的成本可能在 1 美元之间每 MB 10 美元!)。

提前致谢。

小智 14

除了 Martin Fjordvalds 答案:

Apache 仅在压缩文件大小大于 DeflateBufferSize 时才使用分块编码。因此,增加此缓冲区大小将阻止服务器对较大的文件也使用分块编码,从而导致即使对于压缩数据也发送 Content-Length。

更多信息可在此处获得:http : //httpd.apache.org/docs/2.2/mod/mod_deflate.html#deflatebuffersize


Mar*_*ald 7

听起来 Apache 正在执行分块编码,这意味着它可以在 gzip 压缩时发送数据,而不是等待 gzip 压缩完整响应。这是相当标准的做法,但我对 Apache 不够熟悉,无法说是否可以禁用它。


Wil*_*iss 5

好的,我设法解决了这个问题。正如 Martin F 正确指出的那样,Apache 正在对回复进行分块,因此内容大小未知。对于许多人来说,这是可取的(页面加载速度更快)。这是以无法报告下载进度为代价的。

对于像我这样真正想要报告下载进度的人来说,如果您使用 Apache 或 PHP 的自动 gzip 支持,则无能为力。解决方法是手动完成。这比听起来容易:

如果您要发送整个文件,那么这是 PHP 中强制单个块(带有内容长度)的一个很好的示例: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;
Run Code Online (Sandbox Code Playgroud)

瞧!

自己做的另一个好处是您可以设置压缩级别。这对我的移动应用程序非常有用,因为我可以设置为最高压缩级别(因此我的用户为数据支付的费用更少!)——而服务器可能只使用中等压缩级别以获得更好的 CPU/大小权衡。我相信压缩级别只有在您可以编辑 httpd.conf 时才能更改(在共享主机上,我不能)。

所以我保留了我的 DEFLATE .htaccess 指令,但我现在以上述方式编码的应用程序/json 回复除外。

再次感谢 Martin F,你给了我解决这个问题所需的火花:)