Building Modules on Heroku

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.