【问题标题】:Damaged data when gzippinggzip压缩时损坏的数据
【发布时间】:2010-04-20 14:42:06
【问题描述】:

这是我为在我的网站上压缩内容而编写的脚本,该脚本位于“gzip.php”中。我使用它的方式是,在我想要启用 gzipping 的页面上,我在顶部和底部包含文件,我这样调用输出函数:

print_gzipped_page('javascript')

如果文件是 css 文件,我使用 'css' 作为 $type-argument,如果它是 php 文件,我调用函数而不声明任何参数。该脚本在除 Opera 之外的所有浏览器中都能正常工作,它会给出一个错误,指出由于数据损坏而无法解码页面。谁能告诉我我做错了什么?

<?php
function print_gzipped_page($type = false) {
    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 ($type!=false) {
        $type_header_array = array("css" => "Content-Type: text/css", "javascript" => "Content-Type: application/x-javascript");
        $type_header = $type_header_array[$type];
    }

    $contents = ob_get_contents();
    ob_end_clean();
    $etag = '"' .  md5($contents) . '"';
    $etag_header = 'Etag: ' . $etag;
    header($etag_header);

    if ($type!=false) {
        header($type_header);
    }

    if (isset($_SERVER['HTTP_IF_NONE_MATCH']) and $_SERVER['HTTP_IF_NONE_MATCH']==$etag) {
        header("HTTP/1.1 304 Not Modified");
        exit();
    }

    if($encoding){
        header('Content-Encoding: '.$encoding);
        print("\x1f\x8b\x08\x00\x00\x00\x00\x00");
        $size = strlen($contents);
        $contents = gzcompress($contents, 9);
        $contents = substr($contents, 0, $size);
    }

    echo $contents;
    exit();
}

ob_start();
ob_implicit_flush(0);
?>

附加信息:如果要压缩的文档长度仅为 10-15 个字符,则该脚本有效。

感谢帮助,修正版:

<?php
function print_gzipped_page($type = false) {
    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 ($type!=false) {
        $type_header_array = array("css" => "Content-Type: text/css", "javascript" => "Content-Type: application/x-javascript");
        $type_header = $type_header_array[$type];
        header($type_header);
    }

    $contents = ob_get_contents();
    ob_end_clean();

    $etag = '"' .  md5($contents) . '"';
    $etag_header = 'Etag: ' . $etag;
    header($etag_header);

    if (isset($_SERVER['HTTP_IF_NONE_MATCH']) and $_SERVER['HTTP_IF_NONE_MATCH']==$etag) {
        header("HTTP/1.1 304 Not Modified");
        exit();
    }

    if($encoding){
        header('Content-Encoding: ' . $encoding);
        $contents = gzencode($contents, 9);
    }

    $length = strlen($contents);
    header('Content-Length: ' . $length);
    echo $contents;
    exit();
}

ob_start();
ob_implicit_flush(0);
?>

【问题讨论】:

    标签: php http compression gzip


    【解决方案1】:

    这种方法有点太笨拙了。而是使用ob_gzhandler。它会自动压缩客户端支持的内容并设置必要的标题。

    ob_start('ob_gzhandler');
    readfile($path);
    

    【讨论】:

    • 正是...为什么要重新发明*?
    • 话虽如此,你最后需要ob_end_flush(),还是自动调用?
    • @OMG:PHP 文档确实没有明确说明这一点,但在我的情况下,它在所有情况下都会执行刷新和关闭,所以我认为它会自动执行。
    • @BalusC:为什么笨拙?我不想使用 ob_gzhandler 的原因是我想使用 gzipping 同时仍然能够发送 304 - Not Modified 标头。我想缓冲内容,对其进行哈希处理,只有在它发生变化时才输出它。你不能用 ob_gzhandler 做到这一点,它会用 firefox 创建一个错误。阅读:php.net/manual/en/function.ob-gzhandler.php#97385。我问了一个关于我的代码有什么问题的问题,如果你有建议我会很感激。
    • 我会检查ETag(通常由文件名、大小和最后修改的时间戳组成)是否有 304。
    【解决方案2】:

    有两点很突出:

    1) 您似乎没有将 Content-Length 标头设置为压缩数据的大小。 (也许我忽略了它。)如果您不设置此选项,浏览器可能会认为您过早完成发送数据。

    2) 您正在使用未压缩的 $size 对压缩的 $content 进行 substr。当内部结构具有 EOF 标记时,某些浏览器将停止解压缩,但其他浏览器(Opera?)可能会尝试解压缩整个下载的缓冲区。那肯定会给你一个“损坏的数据”错误。您可能不会在使用小缓冲区时看到此问题,因为开销量和压缩量可能完全匹配。

    【讨论】:

    • 谢谢,问题已解决。我删除了压缩数据上的 substr() 并使用压缩文档的长度设置了 Content-Length 标头。