我已经研究了整个早上,决定最后一搏,也许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();
}
}
非常标准的事情,对吧?
问题
有10%到33%的PHP页面请求通过Firefox发送后成功返回并被压缩,只有Firefox显示压缩的ASCII而没有解压它。而且,最奇怪的部分是返回的内容大小总是比正确呈现的页面大小多30或31字节。例如,当脚本被正确显示时,Firebug显示内容大小为1044;当Firefox显示一大块二进制无意义字符时,Firebug显示的内容大小为1074。这种情况发生在一些运行着32位老版Fedora 12的用户上,他们使用Firefox 3.3。然后它发生在FF5、FF6以及一些新的7.1用户身上!我一直想要将他们全部升级到FF7.1,所以每当他们出现问题时,我就会将其升级,但是FF7.1仍然表现出相同的行为,只是更少发生。
诊断
我一直在一些电脑上安装Firebug以观察头文件,这也是我感到困惑的地方:正常工作的页面响应头:
- HTTP/1.1 200 OK
- Date: Fri, 21 Oct 2011 18:40:15 GMT
- Server: Apache/2.2.15 (Fedora)
- X-Powered-By: PHP/5.3.2
- Expires: Thu, 19 Nov 1981 08:52:00 GMT
- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
- Pragma: no-cache
- Content-Encoding: gzip
- Vary: Accept-Encoding
- Content-Length: 1045
- Keep-Alive: timeout=10, max=75
- Connection: Keep-Alive
- Content-Type: text/html; charset=UTF-8
(注意,内容长度是自动生成的)
当页面出现故障时:
- HTTP/1.1 200 OK
- (其他所有内容都相同)
- Content-Length: 1075
发送的头文件总是包括Accept-Encoding: gzip、deflate。
我尝试过以下方法来修复这个问题:
- 显式声明有无压缩的内容长度
- 不使用$contents的substr()
- 移除$contents末尾的校验和
我不想使用gzencode,因为我的测试结果显示它比gzcompress慢得多(9%),这可能是因为它会生成额外的校验和和其他Web浏览器不需要或使用的内容。
我无法在运行Firefox 7.1的64位Fedora 14框上复制此行为。在我将压缩代码发布之前的测试中,Chrome和Firefox都没有发生过这种情况。(编辑:在发布此帖子后不久,我留下的一个窗口每30秒发送一次meta刷新,在Firefox中刷新了约60次后终于崩溃了)我们手头的几台Windows XP电脑表现与Fedora 12相同。搜索Firefox的Bugzilla会出现一两个与这种情况有些类似的bug请求,但那是针对3.3之前的版本,并且是所有gzipped内容,而我们的Apache gzipped css和js文件每次下载和显示时都没有错误。事实上,每次返回的内容长度增加了30/31个字节,这让我想到我的脚本/gzcompress()内部出了问题,导致Firefox无法解析响应。当然,如果你尝试改变echo'd gzip header,Firefox会抛出“Content Encoding Error”,所以我真的倾向于问题在gzcompress()内部。
我注定要失败吗?我必须放弃这个实现并使用不受欢迎的ob_start("ob_gzhandler")方法吗?
我猜我的“适用于多种情况”的问题是:PHP的zlib压缩库中是否存在已知的bug,当接收到非常特定的输入时会发生一些奇怪的事情?
编辑:糟糕。我readgzfile()了其中一个Firefox下载的损坏的、未压缩的页面,结果每个东西都完美地回显了回来。=(这意味着这一定是...不,我什么都没有。