【问题标题】:gzcompress() randomly inserting extra data?gzcompress() 随机插入额外数据?
【发布时间】:2011-10-21 19:08:17
【问题描述】:

我整个上午都在研究这个问题,并决定作为最后的努力,也许 Stack Overflow 上的某个人对我有一个“一直在那里,完成那个”类型的答案。

背景

最近,我在我们的(面向内联网的)Apache (2.2) 服务器上使用过滤器实现了压缩,以便通过 mod_deflate 压缩所有基于文本的文件(css、js、txt、html 等) ,只字未提php脚本。在对如何最好地压缩 PHP 输出进行了大量研究之后,我决定使用 gzcompress() 风格,因为 PHP 文档建议使用 zlib 库和 gzip(使用 deflate 算法,等等等等)优于 ob_gzipwhatever()。

所以我像这样复制了别人的方法:

<?php # start each page by enabling output buffering and disabling automatic flushes
ob_start();ob_implicit_flush(0);

(program logic)

print_gzipped_page();

function print_gzipped_page() {
 if (headers_sent())
    $encoding = false;
 elseif(strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'x-gzip') !== false )
    $encoding = 'x-gzip';
 elseif(strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false )
    $encoding = 'gzip';
 else
    $encoding = false;

 if($encoding){
    $contents = ob_get_contents(); # get contents of buffer
    ob_end_clean(); # turn off OB and flush buffer
    $size = strlen($contents);
    if ($size < 512) { # too small to be worth a compression
        echo $contents;
        exit();
    } else {
        header("Content-Encoding: $encoding");
        header('Vary: Accept-Encoding');
        # 8-byte file header: g-zip file (1f 8b) compression type deflate (08), next 5 bytes are padding
        echo "\x1f\x8b\x08\x00\x00\x00\x00\x00"; 
        $contents = gzcompress($contents, 9);
        $contents = substr($contents, 0,$size); # faster than not using a substr, oddly
        echo $contents;
        exit();
    }
} else {
    ob_end_flush();
    exit();
 }
}

很标准的东西,对吧?

问题

在我们通过 Firefox 发送的所有 PHP 页面请求中,有 10% 到 33% 可以正常执行并以 g-zip 格式返回,只有 Firefox 会显示压缩的 ASCII 而不是解压缩它。而且,最奇怪的部分是,发回的内容大小总是比正确呈现的页面大小大 30 或 31 个字节。如,当脚本正确显示时,Firebug 显示内容大小为 1044;当 Firefox 显示一个巨大的二进制乱码屏幕时,Firebug 显示的内容大小为 1074。

这发生在我们的一些运行 Firefox 3.3s 的旧版 32 位 Fedora 12s 上的用户身上......然后它发生在一个使用 FF5 的用户身上,一个使用 FF6 的用户,还有一些使用新的 7.1 的用户!无论如何,我一直打算将它们全部升级到 FF7.1,所以我一直在更新它们,因为它们有问题,但 FF7.1 仍然表现出相同的行为,只是频率降低了。

诊断

我一直在各种计算机上安装 Firebug 来查看标题,这就是我感到困惑的地方: 正常、正常运行的页面响应标头:
  • HTTP/1.1 200 正常
  • 日期:格林威治标准时间 2011 年 10 月 21 日星期五 18:40:15
  • 服务器:Apache/2.2.15 (Fedora)
  • X-Powered-By:PHP/5.3.2
  • 到期:1981 年 11 月 19 日星期四 08:52:00 GMT
  • Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
  • 编译指示:无缓存
  • 内容编码:gzip
  • 变化:接受编码
  • 内容长度:1045
  • 保持活动状态:超时=10,最大值=75
  • 连接:保持活动状态
  • 内容类型:文本/html; charset=UTF-8

(注意 content-length 是自动生成的)

损坏时相同的页面:

  • HTTP/1.1 200 正常
  • (其他一切都相同)
  • 内容长度:1075

发送的标头总是包含 Accept-Encoding: gzip, deflate

我尝试解决的问题:

  • 使用未压缩和压缩长度显式声明内容长度
  • 不使用 $contents 的 substr()
  • 删除 $contents 末尾的校验和

我真的不想使用 gzencode,因为我的测试表明它比 gzcompress 慢得多(9%),大概是因为它会生成额外的校验和以及我(假设)网络浏览器不需要或使用的东西.

我无法在运行 Firefox 7.1 的 64 位 Fedora 14 机器上复制该行为。 在实时推出压缩代码之前,我的测试中没有一次发生这种情况,无论是在 Chrome 还是 Firefox 中。 (编辑:在发布此消息后,我打开的一个每 30 秒发送一次元刷新的窗口最终在 Firefox 中刷新约 60 次后中断)我们的少数 Windows XP 机器的行为与 Fedora 12s 相同。通过 Firefox 的 Bugzilla 搜索引发了与这种情况有些相似的一两个错误请求,但这是针对 3.3 之前的版本并且包含所有 gzip 内容,而我们的 Apache gzip 压缩 css 和 js 文件正在下载和显示而没有错误每次。

内容长度每次返回大 30/31 字节的事实让我认为我的 script/gzcompress() 内部出现了问题,这在 Firefox 阻塞的响应中破坏了某些内容。自然,如果您更改 echo 的 gzip 标头,Firefox 会抛出“内容编码错误”,所以我真的倾向于 gzcompress() 内部的问题。

我注定要失败吗?我是否必须放弃这个实现并使用不喜欢的 ob_start("ob_gzhandler") 方法?

我想我的“适用于多种情况”的问题是:PHP 中的 zlib 压缩库中是否存在已知错误,在接收非常具体的输入时会做一些时髦的事情?

编辑:坚果。我读取了 Firefox 下载的损坏的、未压缩的页面之一,你瞧!它完美地回显了所有内容。 =(这意味着这一定是……不,我什么都没有。

【问题讨论】:

  • 自然,我一发布,我机器上的 Firefox 终于从服务器检索到损坏的数据。 ‹xÚµ™moÛ6ÇßûS

标签: php http firefox compression zlib


【解决方案1】:

好吧,首先,您似乎没有设置内容长度标头,这会导致问题,相反,您正在使 gzip 内容更长,以便它与您在第一个地方收到的内容长度大小相匹配.这将变得丑陋。我的建议是你替换这些行

# 8-byte file header: g-zip file (1f 8b) compression type deflate (08), next 5 bytes are padding
echo "\x1f\x8b\x08\x00\x00\x00\x00\x00"; 
$contents = gzcompress($contents, 9);
$contents = substr($contents, 0,$size); # faster than not using a substr, oddly
echo $contents;

$compressed = gzcompress($contents, 9);
$compressed_length = strlen($compressed); /* contains no nulls i believe */
header("Content-length: $compressed_length");
echo "\x1f\x8b\x08\x00\x00\x00\x00\x00", $compressed; 

看看是否有帮助。

【讨论】:

  • 好吧,我已经尝试过明确声明内容长度,但它并没有改变任何东西。在压缩内容之前回显 gzip 标头应该没问题,因为调用 ob_end_clean() 会擦除缓冲区的内容并停止输出缓冲,因此标头不会与其余内容一起编码。我已经重新启用了明确声明内容长度,但这并没有更早解决问题,所以我的希望并不高。
  • 您所说的“相反,您正在使 gzip 内容更长,以使其与您最初收到的内容长度大小相匹配”是什么意思?
  • 是的,这仍然不能阻止错误(如果它甚至是错误)的发生。不过,Firefox 似乎需要再刷新几次才能跌倒。
【解决方案2】:

叮!叮!叮!在整个周末都在考虑这个问题之后,在无数次重新阅读 PHP 手册页之后,我终于偶然发现了答案……来自 zlib PHP 文档,“是否要透明地压缩页面。”透明!如中所示,一旦 zlib.output_compression 设置为“On”,就不需要其他任何东西来让 PHP 压缩其输出。是啊,尴尬。

由于未知的原因,从 PHP 脚本中显式调用的代码正在压缩已经压缩的内容,而浏览器只是简单地解开一层压缩并显示结果。奇怪的是,当 output_compression 开启或关闭时,内容的 strlen() 并没有变化,所以透明压缩必须发生在显式压缩之后,但它偶尔决定不压缩已经压缩的内容?

无论如何,只需将 PHP 留给自己的设备即可解决所有问题。 zlib 不需要输出缓冲或任何压缩输出的东西。

希望这可以帮助其他在 HTTP 压缩的美妙世界中苦苦挣扎的人。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-29
    • 1970-01-01
    • 2016-02-24
    • 1970-01-01
    • 1970-01-01
    • 2011-06-03
    • 1970-01-01
    • 2011-01-30
    相关资源
    最近更新 更多