feb
12
2012

Serving mobile version of your Django app when using Varnish

You are building a Django news app, deadline is closing and you need to satisfy the X percentage of your users now accesing the site through their smartphones without getting a low Varnish cache hit rate.

In the ideal world a fully responsive design might be a solution – catering for all devices and looking good across all screen sizes.

But in my world we still have a mobile version of our website (served by our CMS), the ad systems are completely different on the mobile and full versions, and so are the video players, page headers, footers etc.

«Adaptive design light»

My approach is «adaptive design light». The aim is to make the content of the news app itself as adaptive or responsive as possible to save time and be able to update often. That means using media queries on the main app content.

The headers, footers and ad system is included via ESI in the Varnish boxes caching up my Django content. But that also means I need different ESI-tags for mobile and full version. In the Django world I need separate base.html templates with ESI-tags in.

Meet django-mobile

The original django-mobile app adds device detection to your Django project and lets you create separate templates for mobile devices (in template subfolder mobile, i.e. /mobile/base.html). It falls back to the original template if no mobile template is found.

So far so good, using django-mobile I can serve up different base templates for my mobile and full versions of the site.

Device detection

In our CMS and mobile version of the main site, there is (or has been) a full device detection using wurfl and adaption of serverside code to different devices ranging from smallest screens/models to iPhone and Android.

In my world that is to much server side code, and in a rapid changing news app world it would take to much time and code to maintain. And caching gets extremely difficult, so I only care about mobile or not.

But since I need to cache to versions of every page (mobile or not) the device detection has to happen before the request hits the Django app servers.

You could do that in Varnish (there is a good writeup here), but we have a load balancer/gateway that can do that.

Caching different versions

What troubled me the most was how to cache different versions of the site in Varnish and still be able to invalidate (purge) single pages and by that purge both the mobile and full version.

The solution I found was this (there are possibly a lot more nifty solutions, but I am a journalist and not a http header specialist)..

  1. Our gateway/load balancer checks if the device is some kind of mobile device. If so, it sets an http header value of
    X-PLATTFORM="mobile"
  2. Varnish detects this header value, and using the VCL we tell Varnish to «vary» on that header. That means it will cache different versions of the page depending on the value of the X-PLATTFORM header.
    sub vcl_fetch {
            ....
    	set beresp.http.Vary = "X-PLATTFORM";
            ....
    }
  3. By default the django-mobile app detects device and sets a variable («flavour») that is used to serve the right templates and/or content for your device. Since I don’t need that, but instead need it to take into account the X-Plattform header, I wrote a small middleware (PlattformHeaderDetectionMiddleware) to do that in my fork of django-mobile. Add it to your middlewares:
    MIDDLEWARE_CLASSES = (
        ....
        'django_mobile.middleware.PlattformHeaderDetectionMiddleware',
        'django_mobile.middleware.SetFlavourMiddleware',
        ....
    )

    The SetFlavourMiddleware lets you override/debug using flavour=mobile or flavour=full url parameter.

  4. You can now use the functionality of django-mobile to serve up different content/templates based on the X-Plattform header.

Notes on template caching

If you use template caching in Django (you should) you would see that Django only caches one version of your templates and disregarding mobile/full version. To avoid that, use the CachedLoader  template loader of django-mobile like this in your settings.py:

#cache the templates
TEMPLATE_LOADERS = (
    ('django_mobile.loader.CachedLoader', (
        'django.template.loaders.filesystem.Loader',
        'django.template.loaders.app_directories.Loader',
        'django.template.loaders.eggs.Loader',
    )),
)

Puring/invalidating content for all platforms

When purging your articles/pages you want it to happen for all cached versions of the page regardless of device.

That is simple if you use django-varnish as it purges via the management port – that means purging all versions regardless of Vary etc.

Useful links

 

 

About the Author: Anders Eriksen

Leave a comment