在禁用 mod_deflate 和 mod_gzip 的情况下压缩 HTML、CSS 和 JS 的最佳方法

Posted

技术标签:

【中文标题】在禁用 mod_deflate 和 mod_gzip 的情况下压缩 HTML、CSS 和 JS 的最佳方法【英文标题】:Best way to compress HTML, CSS & JS with mod_deflate and mod_gzip disabled 【发布时间】:2010-09-08 02:13:58 【问题描述】:

我在运行 Apache 2 的共享主机上有几个站点。我想压缩交付给浏览器的 html、CSS 和 javascript。主机已禁用 mod_deflate 和 mod_gzip,因此这些选项已失效。不过,我确实有 php 5 可供使用,所以我可以使用其中的 gzip 组件。

我目前将以下内容放在我的 .htaccess 文件中:

php_value output_handler ob_gzhandler

不过,这只会压缩 HTML,而忽略 CSS 和 JS。

有没有一种可靠的方法可以透明地压缩 CSS 和 JS 的输出而不必更改每个页面?我搜索了谷歌并提出了许多解决方案,但我还没有找到一个工作。如果有人能提出他们知道可行的解决方案,我们将不胜感激。

注意,The Definitive Post on Gzipping your CSS 中的方法 2 看起来是一个不错的解决方案,但我无法让它发挥作用。有其他人成功使用此方法吗?

【问题讨论】:

只是好奇 - 为什么主机公司禁用了 mod_deflate 和 mod_gzip?!实际上,他们感兴趣的是让它们打开! 【参考方案1】:

很抱歉耽搁了——这对我来说是忙碌的一周。

假设:

.htaccesscompress.php 在同一个文件中 要压缩的静态文件在static子目录下

我从在 .htaccess 中设置以下指令开始我的解决方案:

RewriteEngine on
RewriteRule ^static/.+\.(js|ico|gif|jpg|jpeg|png|css|swf)$ compress.php [NC]

要求您的提供程序允许您覆盖.htaccess 文件中的mod_rewrite 选项。 那么compress.php文件本身可以是这样的:

<?php

$basedir = realpath( dirname($_SERVER['SCRIPT_FILENAME']) );
$file = realpath( $basedir . $_SERVER["REQUEST_URI"] );

if( !file_exists($file) && strpos($file, $basedir) === 0 ) 
    header("HTTP/1.0 404 Not Found");
    print "File does not exist.";
    exit();


$components = split('\.', basename($file));
$extension = strtolower( array_pop($components) );

switch($extension)

    case 'css':
        $mime = "text/css";
        break;
    default:
        $mime = "text/plain";


header( "Content-Type: " . $mime );
readfile($file);

您当然应该在 switch 语句中添加更多的 mime 类型。我不想让解决方案依赖于 pecl fileinfo 扩展或任何其他神奇的 mime 类型检测库 - 这是最简单的方法。

至于保护脚本 - 我将文件系统中的真实路径转换为没有被破解的 '../../../etc/passwd' 或其他 shellscript 文件路径不会通过。

就是这样

$basedir = realpath( dirname($_SERVER['SCRIPT_FILENAME']) );
$file = realpath( $basedir . $_SERVER["REQUEST_URI"] );

sn-p。虽然我很确定在 $basedir 之外的其他层次结构中的大多数路径甚至在到达脚本之前都会被 Apache 处理。

我还检查生成的路径是否在脚本的目录树中。 按照 pilif 的建议添加缓存控制的标头,您应该有一个可行的解决方案来解决您的问题。

【讨论】:

谢谢,我会试一试。我当然希望 SOF 有电子邮件提醒,以便在问题更新时告诉您。 嘿,有了这个,我将如何使用它。我把它保存为compress.php 对吗?我使用包含吗? 这个脚本假定它是由 apache 重写规则运行的,并被赋予一个文件名作为 URI 参数。 我明白了。但是,如果我更新我的文件,它就不起作用。这是否意味着旧的被压缩了?我更新了我的 CSS,得到了一个包含内容但没有样式的页面。 更新文件是什么意思?您无需对要压缩的文件进行任何操作 - 您只需将此脚本放在服务器上并适当地设置重写规则。【参考方案2】:

我做什么:

我将脚本放在js 中,将样式表放在css 目录中。

在 Apache 配置中,我添加如下指令:

<Directory /data/www/path/to/some/site/js/>
    AddHandler application/x-httpd-php .js
    php_value auto_prepend_file gzip-js.php
    php_flag zlib.output_compression On
</Directory>
<Directory /data/www/path/to/some/site/css/>
    AddHandler application/x-httpd-php .css
    php_value auto_prepend_file gzip-css.php
    php_flag zlib.output_compression On
</Directory>

js 目录中的gzip-js.php 如下所示:

<?php
    header("Content-type: text/javascript; charset: UTF-8");
?>

css 目录中的 gzip-cs.php 如下所示:

<?php
    header("Content-type: text/css; charset: UTF-8");
?>

这可能不是最优雅的解决方案,但它肯定是一个简单的解决方案,只需要很少的更改并且运行良好。

【讨论】:

我刚刚尝试过,不幸的是我无法让它工作。当我按照建议添加指令时,出现内部服务器错误。【参考方案3】:

无论你做什么,都要小心客户端的缓存:

浏览器会使用各种技巧来尝试最小化带宽,并且 HTTP 协议中有很多方法可以做到这一点,所有这些都由 apache 处理 - 如果您只是提供本地文件。

如果你不是,那么这是你的责任

至少看一下当前所有浏览器都支持的 ETag 和 If-Modified-Since 机制,它们似乎是向服务器查询更新内容的最可靠方法。

使用 If-Modified-Since-Header 将 CSS 文件提供给浏览器的一种可能方式是这样的(空头以关闭 PHP 默认发送的任何非缓存头):

$p = 'path/to/css/file'
$i = stat($p);
if ($_SERVER['HTTP_IF_MODIFIED_SINCE'])
    $imd = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
    if ( ($imd > 0) && ($imd >= $i['mtime']))
        header('HTTP/1.0 304 Not Modified');
        header('Expires:');
        header('Cache-Control:');
        header('Last-Modified: '.date('r', $i['mtime']));
        exit;
    

header('Last-Modified: '.date('r', $i['mtime']));
header('Content-Type: text/css');
header('Content-Length: '.filesize($p));
header('Cache-Control:');
header('Pragma:');
header('Expires:');
readfile($p);

代码将使用浏览器发送的 if-modified-since-header 来检查服务器上的实际文件自浏览器给出的日期以来是否已更改。如果是,则发送文件,否则返回 304 Not Modified 并且浏览器不必重新下载整个内容(如果它足够智能,它也会将解析后的 CSS 保存在内存中)。

还有另一个机制涉及服务器为每条内容发送一个唯一的 ETag-Header。客户端将使用 If-None-Match 标头将其发回,从而使服务器不仅可以决定上次修改的日期,还可以决定内容本身。

这只是让代码更复杂,所以我把它省略了。 FF、IE 和 Opera(也可能是 Safari)在接收到附加了 Last-Modified 标头的内容时都会发送 If-Modified-Since 标头,因此可以正常工作。

另外请记住,某些版本的 IE(或它使用的 JScript-Runtime)仍然在 GZIP 传输的内容方面存在问题。

哦。我知道这不是问题的一部分,但 Acrobat 在某些版本中也是如此。在使用 gzip 传输编码提供 PDF 时,我遇到过白屏的案例。

【讨论】:

您是否有针对 GZIP 内容的 IE/JScript 问题的 src/url?我记得旧版本的 IE(例如 IE6 之前的版本)不支持 gzip,但这只是意味着它们提供了较慢的未压缩内容。如果 IE 仍然存在问题,我当然想知道! 我遇到过 IE 无法解析使用 gzip 传输的外部 JS 文件的情况。 Acrobat 插件的某些版本(甚至是当前版本)也无法处理压缩,尽管他们声称可以。【参考方案4】:

当用户请求 CSS 和 JavaScript 文件时,您可以提前对它们进行 gzip,而不是即时 gzip。只要 Apache 为它们提供正确的标头,您就是黄金。

例如,在 Mac OS X 上,在命令行上 gzip 文件非常简单:

gzip -c styles.css > styles-gzip.css

但可能不是适合您的工作流程。

【讨论】:

【参考方案5】:

您可以通过mod_rewrite 试试运气。

创建一个以本地静态文件名作为输入的脚本,例如通过$_SERVER['QUERY_STRING'] 并以压缩形式输出。许多提供商不允许使用 .htaccess 文件配置 mod_rewrite,或者将其完全禁用。

如果你之前没有使用过rewrite,我推荐一个很好的初学者指南,比如this one。 这样,您可以使 apache 将对静态文件的所有请求重定向到 php 脚本。例如 style.css 将被重定向到 compress.php?style.css

一如既往地非常谨慎对待你接受的输入,否则你手上有XSS漏洞利用!

【讨论】:

@macbirdie,非常感谢这个建议。您能否提供或链接到这种方法的任何示例?我也有兴趣了解一些有关保护此类脚本的一般技术。

以上是关于在禁用 mod_deflate 和 mod_gzip 的情况下压缩 HTML、CSS 和 JS 的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

如何为 PHP 文件启用 mod_deflate?

Apache 未发送 304 响应(如果启用了 mod_deflate 和 AddOutputFilterByType)

在没有 mod_deflate 的 Symfony 2 中使用 gzip / 压缩

Apache网页优化和安全优化

mod_deflate 与 mod_gzip

mod_deflate模块