10

I have a few sites on a shared host that is running Apache 2. I would like to compress the HTML, CSS and Javascript that is delivered to the browser. The host has disabled mod_deflate and mod_gzip, so these options are out. I do have PHP 5 at my disposal, though, so I could use the gzip component of that.

I am currently placing the following in my .htaccess file:

php_value output_handler ob_gzhandler

However, this only compresses the HTML and leaves out the CSS and JS.

Is there a reliable way of transparently compressing the output of the CSS and JS without having to change every page? I have searched Google and a number of solutions are presented, but I've yet to get one to work. If anyone could suggest a solution that they know to work, that would be very gratefully received.

Note, Method 2 in The Definitive Post on Gzipping your CSS looks like a good solution, but I couldn't get it working. Has anyone else succeeded using this method?

4

5 回答 5

6

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

假设:

  • .htaccess与在同一个文件中compress.php
  • 要压缩的静态文件在static子目录中

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

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

您的提供者必须允许您覆盖文件中的mod_rewrite选项.htaccess。那么 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 类型。我不想让解决方案依赖于 peclfileinfo扩展或任何其他神奇的 mime 类型检测库——这是最简单的方法。

至于保护脚本 - 我会翻译到文件系统中的真实路径,因此不会通过被黑客入侵的 '../../../etc/passwd' 或其他 shellscript 文件路径。

那是

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

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

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

于 2008-09-10T19:26:36.880 回答
4

What I do:

  • I place scripts in a js and stylesheets in a css dir, respectively.
  • In the Apache configuration, I add directives like so:

    <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>
    
  • gzip-js.php in the js directory looks like this:

    <?php
        header("Content-type: text/javascript; charset: UTF-8");
    ?>
    
  • …and gzip-cs.php in the css directory looks like this:

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

This may not be the most elegant solution, but it most certainly is a simple one that requires few changes and works well.

于 2008-09-07T17:14:51.717 回答
1

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

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

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

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

于 2009-12-20T22:22:56.413 回答
1

You can try your luck with mod_rewrite.

Create a script that takes a local static file name as input, through e.g. $_SERVER['QUERY_STRING'] and outputs it in compressed form. Many providers don't allow configuring mod_rewrite with .htaccess files or have it completely disabled though.

If you haven't used rewrite before, I recommend a good beginner's guide, like probably this one. This way you can make the apache redirect all requests for a static file to a php script. style.css will be redirected to compress.php?style.css for instance.

As always be extremely cautious on the input you accept or you have an XSS exploit on your hands!

于 2008-09-07T17:01:20.843 回答
1

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

浏览器会使用各种技巧来尝试最小化带宽,并且 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 时,我遇到过白屏的案例和案例。

于 2008-09-07T20:34:03.563 回答