I’ve run this site on Heroku for a while now but something has always bothered me, the default Apache install on the Cedar stack does not come with mod_deflate
forcing us to send everything sent to clients uncompressed. Now I get that bandwidth is cheap but CPU time with zlib is cheaper and there’s no reason to send any text over http uncompressed.
Luckily the Heroku repo that this site is running on has already been customized to dynamically inject new configs on boot. So now all we need it to load another module and configure it as part of the dyno bootstrap process.
Compiling via One-off Dynos
To build a mod_deflate
module that’s compatible with Heroku dynos we need to compile it on the dynos themselves. Luckily Heroku offers one-off dynos which will allow dynos run attached to your terminal. It’s as simple as issuing a single command.
$ heroku run bash
Running `bash` attached to terminal... up, run.2638
~ $ uname -a
Linux 8ce584d4-a480-4c88-85e0-2243d15a19a2 3.8.11-ec2 #1 SMP Fri May 3 09:11:15 UTC 2013 x86_64 GNU/Linux
With terminal access to the dynos compiling any custom Apache module becomes a simple & straight forward process. First get the source; in this case the module is included in the default Apache tarball.
~ $ curl -o httpd-2.2.24.tar.gz http://mirrors.ibiblio.org/apache/httpd/httpd-2.2.24.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 7212k 100 7212k 0 0 65851 0 0:01:52 0:01:52 --:--:-- 75675
~ $ tar -xzf httpd-2.2.24.tar.gz
Next we just need to use the Apache extension tool (apxs), already included with the dyno, to build the module.
~ $ cd httpd-2.2.24/modules/filters/
~/httpd-2.2.24/modules/filters $ /app/apache/bin/apxs -i -c -Wl,lz mod_deflate.c
/app/apache/build/libtool --silent --mode=compile gcc -prefer-pic -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -g -O2 -pthread -I/app/apache/include -I/app/apache/include -I/app/apache/include -c -o mod_deflate.lo mod_deflate.c && touch mod_deflate.slo
/app/apache/build/libtool --silent --mode=link gcc -o mod_deflate.la lz -rpath /app/apache/modules -module -avoid-version mod_deflate.lo
/app/apache/build/instdso.sh SH_LIBTOOL='/app/apache/build/libtool' mod_deflate.la /app/apache/modules
/app/apache/build/libtool --mode=install cp mod_deflate.la /app/apache/modules/
cp .libs/mod_deflate.so /app/apache/modules/mod_deflate.so
cp .libs/mod_deflate.lai /app/apache/modules/mod_deflate.la
cp .libs/mod_deflate.a /app/apache/modules/mod_deflate.a
chmod 644 /app/apache/modules/mod_deflate.a
ranlib /app/apache/modules/mod_deflate.a
PATH="$PATH:/sbin" ldconfig -n /app/apache/modules
----------------------------------------------------------------------
Libraries have been installed in:
/app/apache/modules
If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
- add LIBDIR to the `LD_LIBRARY_PATH' environment variable
during execution
- add LIBDIR to the `LD_RUN_PATH' environment variable
during linking
- use the `-Wl,--rpath -Wl,LIBDIR' linker flag
- have your system administrator add LIBDIR to `/etc/ld.so.conf'
See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.
----------------------------------------------------------------------
chmod 755 /app/apache/modules/mod_deflate.so
With the module built we’ll just SCP it somewhere else before we shutdown the one-off dyno so we can add it in our Heroku repo for our web dynos instances.
Enabling Compression
Enabling the module is a matter of adding to our custom Apache conf from before and enabling mod_deflate
filtering on textual content. Luckily here too, the hard work has already been done for us by the good folks that maintain HTML5 ★ BOILERPLATE. (Yay, no more copying and pasting that same configs from the internet that still tries to correct for Netscape 4 not handling gzip properly!)
#
# Enable compression with mod_deflate
#
LoadModule deflate_module /app/www/extensions/mod_deflate.so
<IfModule mod_deflate.c>
# Set compression level
DeflateCompressionLevel 6
# Force compression for mangled headers.
# http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)s*,?s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
</IfModule>
</IfModule>
# Compress all output labeled with one of the following MIME-types
AddOutputFilterByType DEFLATE application/atom+xml
application/javascript
application/json
application/rss+xml
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/svg+xml
image/x-icon
text/css
text/html
text/plain
text/x-component
text/xml
</IfModule>
I’ve merged these changes into my WordPress Heroku repository and I’m happy to report it’s now happily running and compressing content on this very site.