禁用mod_deflate和mod_gzip压缩HTML,CSS和JS的最佳方法
我在运行Apache 2的共享主机上有一些站点。我想压缩交付给浏览器的HTML,CSS和Javascript。主机已禁用mod_deflate和mod_gzip,因此这些选项均已退出。不过,我确实可以使用PHP 5,因此可以使用其中的gzip组件。
我目前在.htaccess文件中放置以下内容:
php_value output_handler ob_gzhandler
但是,这只会压缩HTML,而忽略CSS和JS。
有没有一种可靠的方法可以透明地压缩CSS和JS的输出而不必更改每个页面?我已经搜索过Google,并提出了许多解决方案,但是我还没有找到一个可行的解决方案。如果有人能提出他们知道可以使用的解决方案,将不胜感激。
请注意,《关于在CSS上压缩的权威文章》中的方法2看起来是一个不错的解决方案,但我无法使其正常工作。还有其他人成功使用过此方法吗?
解决方案
回答
我们可以使用mod_rewrite来试试运气。
通过以下方式创建一个以本地静态文件名作为输入的脚本$ _SERVER ['QUERY_STRING']`并以压缩形式输出。许多提供程序不允许使用.htaccess文件配置mod_rewrite或者将其完全禁用。
如果我们以前没有使用过重写,那么我会推荐一本不错的初学者指南,就像这样的指南。
这样,我们可以使apache将对静态文件的所有请求重定向到php脚本。例如,style.css将被重定向到compress.php?style.css。
一如既往地谨慎对待我们接受的输入,或者我们手上有XSS
漏洞!
回答
我所做的:
- 我分别将脚本放在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"); ?>
这可能不是最优雅的解决方案,但可以肯定它是一个简单的解决方案,几乎不需要任何更改就可以很好地工作。
回答
无论我们做什么,都要注意在客户端缓存:
浏览器会采取各种技巧来尝试最小化带宽,并且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时,我遇到过白色屏幕的情况。
回答
对不起,对我来说这是一个忙碌的一周。
假设:
- .htaccess和compress.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类型。我不想让解决方案依赖于peclfileinfo
扩展名或者任何其他神奇的mime类型检测库,这是最简单的方法。
至于保护脚本,我将其转换为文件系统中的真实路径,因此不会破解" ../../../etc/passwd"或者其他shellscript文件路径。
那是
$basedir = realpath( dirname($_SERVER['SCRIPT_FILENAME']) ); $file = realpath( $basedir . $_SERVER["REQUEST_URI"] );
片段。尽管我很确定除$ basedir之外的其他层次结构中的大多数路径在到达脚本之前都会被Apache处理。
我还要检查结果路径是否在脚本的目录树中。
按照pilif的建议添加用于缓存控制的标头,我们应该可以解决该问题。
回答
当用户请求CSS和JavaScript文件时,我们可以立即对它们进行gzip压缩,而不是即时gzip压缩。只要Apache使用正确的标头为它们提供服务,就可以了。
例如,在Mac OS X上,在命令行上gzip压缩文件很容易:
gzip -c styles.css > styles-gzip.css
虽然可能不是适合工作流程。