On 06.10.2011 18:36, Xavier Noria wrote:
> Does anyone have a well-tested and idiomatic Apache configuration to serve
> pre-compressed content? Vary header, Content-Type header, browser gotchas,
> and everything robustly sorted out?

Yes, see below.

> The situation is that you have foo.css and foo.css.gz on disk, and want
> Apache to serve foo.css.gz directly if asked for foo.css and the clients
> accepts the compressed content.

Note, that because of mime type handling our convention for the filename
is foo.gz.css. Also my recipe expects you to have both files available,
foo.css and foo.gz.css.

> I guess that's going to be based on content negotiation with MultiViews, but
> a google search suggests there are practical issues to get this right that
> may not be obvious at first.

Not necessary.

> And also a curiosity: After all these years, why doesn't mod_deflate do this
> by itself? Compressing with max ratio to disk on a first request, and
> serving the cached .gz in subsequent requests?

Patches welcome!

Now to the config and how it works:

General description

1) mod_rewrite checks, whether the browser accepts gzipped content
(RewriteCond against Accept-Encoding request header)

2) If so, check whether the URL belongs to the range of URLs, for which
we provide pre compressed static content
(prefix and suffix matches, you might need to adjust)
You can also use "-f" to check for existance, but I prefer a more
tightly managed environment, were it is clear that for certain URL
pattern all files exist compressed and uncompressed and wedon't have to
do a "-f" for each request.

3) If 1) and 2) are true, insert ".gz" before the file name suffix,
adjust the URL accordingly and set a marker environment variable we can
later check.

4) Now if the env var in 3) is set, use mod_headers toset the
Content-Encoding response header to "gzip".

Why does it work? By changing the request URL we let Apache send the
compressed file. The content Type doesn't change, because the filesuffix
hasn't changes (foo.gz.css). Browsers who don't accept gzipped content
should not indicate gzip support in the Accept-Encoding request header
so we send uncompressed content.

Caches need to be made aware of whether the response is compressed or
not. For this one uses the Vary response header. Fortunately since we
make our response depending on the Accept-Encoding request header using
mod_rewrite, mod_rewrite automaticcaly adds it to Vary for us. Nice.

Note: mod_deflate is not involved.

Following is a commented example config snippet:

# This needs mod_rewrite and mod_headers
# loaded as modules.

# Make static content available.
# Not needed if already mounted elsewhere.

Alias /myapp/static /opt/myapp/static/

# Activate mod_rewrite and debug logging.
# Not needed if mod_rewrite is already
# activated for this VHost elsewhere.

RewriteEngine On
# Configure RewriteLog according to your needs
# RewriteLog ...
# RewriteLogLevel ...

# Flip in compressed content if allowed.
# Assumes all the compressed files are on disk
# having the correct names:
# something.css -> something.gz.css
# something.js -> something.gz.js

# 1) Check whether browser accepts "gzip" encoding
RewriteCond %{HTTP:Accept-Encoding} gzip
# 2) Check whether request belongs to our
#    static URLs and has the right suffix
#    If yes, add ".gz" to URL before existing suffix
#    and remember this in our custom environment variable.
RewriteRule (/myapp/static/.*)\.(css|js)$ $1.gz.$2 [E=gz:1]

# Fix returned encoding header if we use the gzippped file.
Header set Content-Encoding gzip env=gz

# Notes:
#
# - Be careful when introducing loops for rewrite rules:
#   The new .gz.js etc. file would again match the rule
#   leading to unterminated recursion.
#   Make regexp more precise in that case (not allowing the .gz.)
#   to match again.
#
# - Content-Type header works OK, because file suffix hasn't changed.
#   This would not work for files without suffix, because then
#   we end up with a ".gz" suffix!
#
# - Vary header is automatically extended with "Accept-Encoding"
#   by mod_rewrite because of using the "Accept-Encoding" header
#   in the RewriteCond
#
# - Old-style "Accept-Encoding: x-gzip" in request also works.
#   The "gzip" is a sub pattern match (not anchored).
#
# Open Questions:
#
# - Is there any interoperability issue when mod_deflate is
#   activated in addition (double compress or similar).
#   If so, try to set env var "no-gzip" to deactivate mod_deflate
#   for those requests.

Example shell script to generate the compressed content (in addition to
the uncompressed already existing files), here for *.css and *.js:

CONTENT_DIR=/opt/myapp/static/
for suffix in css js
do
    for file in \
      `find $CONTENT_DIR -type f -name "*.$suffix" -a ! -name "*.gz.*"`
    do
        gzfile=`echo $file | sed -e 's#\.'$suffix'#.gz.'$suffix'#'`
        gzip --best -c $file > $gzfile
        chmod 644 $gzfile
        echo === $file $gzfile ===
        ls -ld $file $gzfile
    done
done

Regards,

Rainer

---------------------------------------------------------------------
The official User-To-User support forum of the Apache HTTP Server Project.
See <URL:http://httpd.apache.org/userslist.html> for more info.
To unsubscribe, e-mail: users-unsubscr...@httpd.apache.org
   "   from the digest: users-digest-unsubscr...@httpd.apache.org
For additional commands, e-mail: users-h...@httpd.apache.org

Reply via email to