This took me a while to figure out but the reason most people are having issues with rewriting requests to pre-compressed files is because they using REQUEST_FILENAME
.
It seem REQUEST_FILENAME
depending on the context and where you put you rewrite code, determines if you getting the full file-system path or a copy of REQUEST_URI
.
REQUEST_FILENAME
The full local filesystem path to the file or script matching the request, if this has already been determined by the server at the time REQUEST_FILENAME is referenced. Otherwise, such as when used in virtual host context, the same value as REQUEST_URI. Depending on the value of AcceptPathInfo, the server may have only used some leading components of the REQUEST_URI to map the request to a file.
Ref: https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html
To fix this I found there are 2 possible options.
One is to use the look-ahead modifier as per the docs.
If used in per-server context (i.e., before the request is mapped to the filesystem) SCRIPT_FILENAME and REQUEST_FILENAME cannot contain the full local filesystem path since the path is unknown at this stage of processing. Both variables will initially contain the value of REQUEST_URI in that case. In order to obtain the full local filesystem path of the request in per-server context, use an URL-based look-ahead %{LA-U:REQUEST_FILENAME} to determine the final value of REQUEST_FILENAME.
The other option that is not reliant on context or look-ahead I found to be using a DOCUMENT_ROOT
with REQUEST_URI
.
So putting it all together looks something like this.
# ==============================================================================
# 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>
It would be nice if Apache's official docs actually point this out or used one of these 2 examples that work in all contexts as this has probably tripped up a lot of people over the years.
Ref: https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#precompressed