如何在Apache中托管静态内容预压缩?

22
我主持一个JavaScript游戏,基本上由一个.html和一个.data文件组成。如果我使用gzip进行压缩,它们的大小会缩小到25%。所以我想这样做。
我不是100%确定,但我认为使用mod_gzip或mod_deflate可以在运行时进行压缩,因为内容不改变而浪费cpu时间。
所以我想要预编译内容。因此,我在未压缩的文件旁边放了一个.gz,并在.htaccess中设置了重写规则:
RewriteEngine on 
# If client accepts compressed files 
RewriteCond %{HTTP:Accept-Encoding} gzip 
# and if compressed file exists 
RewriteCond %{REQUEST_FILENAME}.gz -f 
# send .html.gz instead of .html 
RewriteRule ^(.+)\.(html|css|js|data)$ $1.$2.gz [T=text/$2,E=GZIP:gzip,L] 
Header set Content-Encoding gzip env=GZIP 

重定向已经生效,我可以请求game.html并实际获取game.html.gz。但是,浏览器不会直接显示它,而是要求我选择保存文件的位置。我该如何解决这个问题?或者也许有其他方法可以实现我的目标?


似乎Apache不尊重T修饰符。服务器实际返回什么内容类型? - claustrofob
我很难找到答案。当Firefox想要保存文件时,Firebug没有报告Content-type - 或者我不知道如何查看它。 - marc40000
5个回答

20

这是我曾经解决过的相同问题的方法。

在 .htaccess 中添加新类型:

AddEncoding gzip .jsgz .cssgz .htmlgz .datagz
AddType application/javascript .jsgz
AddType text/css .cssgz
AddType text/html .htmlgz       
AddType text/plain .datagz

之所以这样做,是因为AddType指令不接受以 .html.gz 形式结尾的扩展名。

然后修改你的重写规则:

RewriteRule ^(.+)\.(html|css|js|data)$ $1.$2gz [L] 

最后重命名您的文件。从.html.gz、.js.gz等文件名中删除点号。

完整的 .htaccess 文件看起来像这样:

AddEncoding gzip .jsgz .cssgz .htmlgz .datagz
AddType application/x-javascript .jsgz
AddType text/css .cssgz
AddType text/html .htmlgz       
AddType text/plain .datagz

RewriteEngine on 
# If client accepts compressed files 
RewriteCond %{HTTP:Accept-Encoding} gzip 
# and if compressed file exists 
RewriteCond %{REQUEST_FILENAME}gz -f 
# send .html.gz instead of .html 
RewriteRule ^(.+)\.(html|css|js|data)$ $1.$2gz [L] 

2
这个解决方案在实际项目中已经运行了数年。如果AddType支持像.html.gz这样的复杂扩展名,你就不能重命名文件了。 - claustrofob
我也是这样做的。我的CSS和JS都被压缩/压缩,并且效果很好。谢谢。 - Wolf87
1
这个解决方案仅剩下的问题是它没有设置Vary:accept-encoding,这可能会破坏中间缓存。 - Matty K
1
@MattyK 当使用%{HTTP:Accept-Encoding}RewriteCond匹配时,Apache应该(根据文档)自动添加Vary头。 - Walf
1
这个有效:https://dev59.com/cWox5IYBdhLWcg3wpFv5?lq=1 - Kalpesh Soni
显示剩余4条评论

10
你应该先问自己一个问题,这么做有意义吗?你是否因此遇到了过高的CPU负载和/或性能差异?我的猜测是,你可能没有遇到这个问题 :)
不过,无论如何,有多种方法可以解决你的问题。
  1. 对于你来说,可能最好的选择是使用CDN。它们专为静态文件快速传输而设计,并且将使远离服务器的人以及靠近服务器的人都能够快速访问。此外,根据我的经验,CDN通常比你自己的带宽便宜得多。

  2. 使用Nginx。对于托管静态文件来说,它要快得多,并支持像你现在所做的预生成静态内容。当需要时,它会自动检测是否有.gz文件并提供服务。

  3. 使用Apache缓存机制之一,例如mod_mem_cachemod_disk_cache,以确保每个经常使用的文件都在缓存中。教程:http://webdirect.no/linux/apache-caching-with-gzip-enabled/

  4. 在其前面使用缓存代理,例如Varnish,这些类型的服务器具有更智能的缓存机制,并且实际上会缓存最重要的文件。

对于你当前的版本,以下内容(未经测试)应该可以解决问题:
RewriteEngine On    
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.(html|css|js|data) $1\.$2\.gz [QSA]

# Prevent double gzip and give the correct mime-type
RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1,E=FORCE_GZIP]
RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1,E=FORCE_GZIP]
RewriteRule \.html\.gz$ - [T=text/html,E=no-gzip:1,E=FORCE_GZIP]
RewriteRule \.data\.gz$ - [T=text/plain,E=no-gzip:1,E=FORCE_GZIP]

Header set Content-Encoding gzip env=FORCE_GZIP

你写的替代方案很有趣。虽然nginx和varnish可能不适合我,因为我已经安装了Apache,不想为两个文件再安装一遍。1或3似乎是我要调查的有趣选项。然而,现在对我来说,只是修复.htaccess似乎是最快的选择。我尝试了你的建议:它不再要求保存文件,但在显示之前它不会解压缩文件。我看到了压缩文件,有很多字符。我在Firefox和Chrome中进行了测试。 - marc40000
当包含浏览器时,它们可能会聪明地正确执行,但我确实忘记了传递编码。我会更新答案 :) - Wolph
我使用类似的方法,只是我将它用于在任何CGI之前运行的mod Perl脚本中。这使我也能够在需要时获取特定于引擎的文件。例如,/file.js -> file.jscript.js.gz适用于ie,file.gecko.js.gz适用于firefox,file.v8.js.gz适用于chrome或file.nitro.js.gz适用于safari。CSS也是如此,但是它是基于渲染器而不是JS引擎,对于IE是trident,对于Firefox是gecko,对于Chrome和Safari是webkit。如果找不到特定于浏览器的文件,则使用默认的file.js.gz。对于客户端,它还考虑软件版本。 - Rahly
这篇文章对我很有帮助。但是我遇到了一个问题。如果我直接请求css.gz文件,响应头会包含Content-Encoding。有没有其他方法来解决这个问题? - Arun kumar Kalaiarasan
它应该包含content-encoding=gzip,因为它是这样的。如果你真的不想要它,你可以删除Header行,但我不建议这样做。 - Wolph

7

被采纳的答案似乎相当繁琐。 Wolph的答案 更好一些,但仍需要针对每个文件扩展名进行单独配置,并且缺乏对更高级协商 (q-values, 状态406, TCN等) 的支持。与其使用mod_rewrite自己实现内容协商,您可能希望考虑使用mod_negotiation,如这个问题中所讨论的。从那里复制我的答案

Options +MultiViews
RemoveType .gz
AddEncoding gzip .gz
<FilesMatch ".+\.tar\.gz$">
    RemoveEncoding .gz
    # Note:  Can use application/x-gzip for backwards-compatibility
    AddType application/gzip .gz
</FilesMatch>

这样做的额外好处是它可适用于所有 .gz 文件,而不仅仅是明确配置的文件,并且可以轻松扩展为使用 brotli 或其他编码方式。

它确实有一个主要缺点,因为 只有不存在的文件才会进行协商请求,所以名称为foo.js 的文件将使得对于 /foo.js(但不是/foo)的请求返回未压缩的版本。可以通过使用François Marier's solution 将未压缩的文件重命名为双扩展名来避免此问题,因此将 foo.js 部署为 foo.js.js


3

这似乎比mod_rewrite规则更合理。 - Gareth Oakley

0

这花了我一些时间来弄清楚,但大多数人在重写请求到预压缩文件时遇到问题的原因是因为他们使用了REQUEST_FILENAME

看起来REQUEST_FILENAME取决于上下文和您放置重写代码的位置,确定您获取完整的文件系统路径还是REQUEST_URI的副本。

REQUEST_FILENAME
如果服务器在引用 REQUEST_FILENAME 时已经确定了与请求匹配的文件或脚本的完整本地文件系统路径,那么该值为该路径。否则,在虚拟主机上下文中使用时,其值与 REQUEST_URI 相同。根据 AcceptPathInfo 的值,服务器可能仅使用了 REQUEST_URI 的一些前导组件将请求映射到文件。

参考: https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html

要解决此问题,我找到了两个可能的选项。

其中一个是根据文档中的先行断言修饰符进行使用。

如果在每个服务器上下文中使用(即,在请求映射到文件系统之前),SCRIPT_FILENAME和REQUEST_FILENAME不能包含完整的本地文件系统路径,因为在处理的这个阶段路径是未知的。在这种情况下,这两个变量最初将包含REQUEST_URI的值。为了在每个服务器上下文中获取请求的完整本地文件系统路径,可以使用基于URL的前瞻 %{LA-U:REQUEST_FILENAME} 来确定REQUEST_FILENAME的最终值。
另一个不依赖于上下文或前瞻的选项是使用DOCUMENT_ROOT和REQUEST_URI。
所以把它们都放在一起,看起来像这样。

# ==============================================================================
# Serving pre-compressed content
# ==============================================================================
# Include in a vhost to enable Serving Gzip Files
# ------------------------------------------------------------------------------
# Ref:
# - https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#precompressed
#

<IfModule mod_headers.c>
    <IfModule mod_rewrite.c>
        RewriteEngine On

        # Serve gzip compressed CSS and JS files if they exist
        # and the client accepts gzip.
        RewriteCond "%{HTTP:Accept-encoding}" "gzip"

        # REQUEST_FILENAME is only available as a full path once the requests
        # has been resolved so we need to use a look ahead or
        # DOCUMENT_ROOT+REQUEST_URI
        # RewriteCond "%{LA-U:REQUEST_FILENAME}\.gz"    "-s"
        RewriteCond "%{DOCUMENT_ROOT}%{REQUEST_URI}.gz" "-s"

        # Rewrite to gzip file
        RewriteRule "^(.*)\.(css|js)"                   "$1\.js\.gz" [QSA]

        # Serve correct content types, and prevent mod_deflate double gzip.
        RewriteRule "\.css\.gz$" "-" [T=text/css,E=no-gzip:1]
        RewriteRule "\.js\.gz$"  "-" [T=text/javascript,E=no-gzip:1]

        <FilesMatch "(\.js|\.css)$">
            Header append Pre-Compressed 0
        </FilesMatch>

        <FilesMatch "(\.js\.gz|\.css\.gz)$">
            # Serve correct encoding type.
            Header append Content-Encoding gzip

            # Force proxies to cache gzipped &
            # non-gzipped css/js files separately.
            Header append Vary Accept-Encoding
            Header append Pre-Compressed 1
        </FilesMatch>
    </IfModule>
</IfModule>

如果Apache的官方文档能够明确指出这一点,或者使用这两个在所有情况下都有效的示例之一,那就太好了,因为多年来,这可能让很多人困惑。
参考链接:[https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#precompressed]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接