One of the many things we do to help optimize our website load times is optimize the frontend by setting far-futures headers. This simply means that media has an Expires tag of sometime in the distant future (maybe a year), and when we make changes, the filename needs to change. The easiest way to do this, is by adding a simple GET parameter.
To handle this, we had been using a simple revision system, where we just increment a number in a list of files, and it appends that number to the filename. After realizing how lazy we should have been, I quickly rewrote it to grab the file modification time and use that as the revision number.
Anyways, we did this by using a simple template tag (or a global object as it’s known in Jinja), to output the URL to the file, as well as the revision.
import os, os.path from django.conf import settings from jinja.contrib.djangosupport import register def mediaurl(value): fname = os.path.abspath(os.path.join(settings.MEDIA_ROOT, value)) if not fname.startswith(settings.BASE_PATH): raise ValueError("Media must be located within MEDIA_ROOT.") return '%s%s?%s' % (settings.MEDIA_URL, value, unicode(int(os.stat(fname).st_mtime))) register.object(mediaurl)
Now to link our media files it’s quick and painless:
<!-- iBegin Stylesheets -->
<link rel="stylesheet" type="text/css" media="screen" href="{{ mediaurl('styles/screen.css') }}" />
<link rel="stylesheet" type="text/css" media="print" href="{{ mediaurl('styles/print.css') }}" />
<!-- JavaScript -->
<script type="text/javascript">BASE_URL = '{{ BASE_URL|escape }}';</script>
<script type="text/javascript" src="{{ mediaurl('common/scripts/mootools-1.2.js') }}"></script>
<script type="text/javascript" src="{{ mediaurl('common/scripts/global.js') }}"></script>
10 Responses to "Scaling Your Frontend: Far-Futures Headers and Template Tags"
Thanks for this, I’ve been meaning to do it forever and this is just the kick in the pants needed to implement it. I was thinking of using the svn version number, but file modification time is even easier.
That’s the same thing I wanted to do at first, but then I realized that it didn’t really matter. File modification time is MUCH easier
I can’t understand reason for that. Testing ?
Changing file means that you’ve updated it’s timestamp. So, next time you’ll have new (non-cached) file delivered to client.
Why go to all this trouble which requires:
– access to the files from your frontend (that should not be in high traffic site)
– a filesystem access for every URL rendered (with hundreds of thousands of files, this gets ugly pretty quickly)
– requires you to use template tag for every single occurence of such URL (no URL’s in static files then)
instead you can just set MEDIA_URL = ’some_url/%s’ % VERSION and have your deployment system handle the directories on the media server (simple symlink is usually enough). Settings.py is just a python file, why not use that and compute the VERSION once and for all.
VERSION can be taken from svn’s revision and/or any arbitrary string (we, for example, take it from the package version that we deploy, same version we use to identify DB schema version and resolve dependencies). Plus your deployment process can go into your static files (js and css) and add the version there so that you won’t have to generate those dynamically.
Very useful.. Thanks
Honza you are quite correct about a lot of this. The MEDIA_URL solution would be a very useful way to do it, but it isn’t much different than what this is doing (and I think mediaurl() actually simplifies things).
As for the filesystem access, this is entirely up to what you are doing and how you’re doing it. This is meant to be used as a simple example, but if you have a heavy traffic site you’re probably not serving media from your system so this wouldn’t work. On the off chance you are, you may want to run some kind of in-memory caching mechanism on it (wouldn’t be hard to build).
Are you aware that if you do this, your static files won’t be cached?
http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
Because of the caching-issue tabo mentioned I’m using this approach for quite some time now (docstring is in english):
http://www.django-hosting.de/wiki/MediaFileExpire/
It’s very similar to your solution but it doesn’t append the mtime as a query string. Instead it appends the mtime in the filename just before the fileextension and is used in conjunction with some mod_rewrite rule to strip the mtime before reading the file from disk.
Check out django-compress. It versions, combines, and minifies your js/css files.
I use a patched version to bypass using settings.py (current trunk requires this).
That’s very interesting tabo. I hadn’t paid very close attention, as it appeared it was working fine. Anyways, you could use the same basic code to do something similar.
Leave A Reply