Helping Speed Up Websites With django-cachebuster

I released a cache-busting application for Django in March called django-cachebuster.  It hasn't seen the uptake I would have expected, given the static/media changes introduced with the release of Django 1.3.  After all, existing solutions seemed to be simple snippets supporting only Django 1.2 and lower.  I'm writing this article to provide some exposure to django-cachebuster, plain and simple.  If you try it out, please provide feedback, enter issues and/or submit pull requests to improve the app.

Seriously, what's this for?

Why would you want django-cachebuster added to your Django project?  Let's start by understanding how web browsers ask servers for files:

  1. A user tells their web browser to go to www.example.com
  2. The browser gets the page's HTML markup from the server and parses it to discover additional elements required for the page to look and function correctly.  Mostly, these are images, style sheets and javascript files.
  3. For each additional element, the browser checks its local file cache to see if it already has a file of that name
    • If it doesn't exist locally, it gets the file from the server
    • If it exists locally and there is no expiration date on the file, the web browser will request the file from the server (this is the default behaviour for most web servers):
      • the server may respond with a status code (304) indicating that there have been no modifications since its last retrieval, or
      • it will send back the file.
    • If it exists locally and there is an expiration date on the file that has expired, it will fetch the file from the server again
    • If it exists locally and there is an expiration date on the file that has not expired, it will do nothing. (this is the best scenario for high capacity server configurations)

Point #7 simply indicates that you want to cut out any unnecessary traffic between the multitude of users viewing your website content.  The technology to enable this functionality is available to server admins regardless of the web server they use - if it isn't, you should switch!  It is enabled by configuring the server to allow what I'll call 'asset far-future expiration'.  Once enabled, the web servers simply tell the browser to not bother coming to get affected files again until some ridiculously distant date comes to pass such as the year 2050.  Translated, it instructs the web browser software to keep the file in its cache until the expiration date.  This makes your users' web browsing experience much faster and responsive in addition to reducing the number of incoming web browser requests to your servers - enabling higher capacity within existing infrastructure and reducing costs...

... at a cost.

What's the catch?

You've decided to change the color of your website logo, update Javascript/CSS files or add new functionality.  Without far-future expiration, you would just roll out all of the new files and web browsers would find them immediately.  With far-future expiration enabled, the web browser will not bother to check its local copy of a file against the server - it will never get the new content unless the user manually refreshes the page.

What's the fix?

django-cachebuster, or something similar.  Web browser file cache-busting is typically done by appending a unique string as a query parameter at the end of the asset's URL.  (ie. instead of www.example.com/styles.css, we would have www.example.com/styles.css?3141548702).  Typically, the unique value used is simply the file's timestamp.  This ensures that the server uses the same unique value for each file request until the file is updated, which causes its timestamp to be changed. django-cachebuster has some efficiency improvements to allow for reducing file I/O (timestamp reads) by using the current git commit; this also helps to ensure cross-system compatible unique ids.  Outside contributors have also submitted compatibility changes for cloud-based (such as Amazon's CloudFront) asset serving.

Find out how to install and configure django-cachebuster on GitHub here: https://github.com/jaddison/django-cachebuster.

Using JetBrains IntelliJ IDEA 10 libGDX Android Game Development

Updated May 15, 2011: Streamlining the project setup steps.

As I spend a fair amount of my working/hobby time using the excellent JetBrains' PyCharm Python IDE for my Django projects, I thought it only made sense to stick with the common interface when exploring Android mobile phone application development.  JetBrains provides another freakishly good IDE for Java called IntelliJ IDEA 10 that has a built in module for Android development, so getting up and running for basic Android app development is really quite easy - and the free version does everything you'd need!

Here entered my desire to experiment in Android game development.  I knew I would need an 2D/3D game engine or framework to work with; after exploring a couple of options, I settled on libGDX.  Its friendly licensing and available functionality seemed to match up with my grand expectations of experimentation and revenue generation.  

At the time of writing this entry, I'm quite new when it comes to game development in general, and rusty at best when it comes to Java development as it has been some years since I've touched it.  I will say, however, that the Android demos and tutorials can get an experienced non-Java developer up and running quite quickly.  libGDX has its own set of tutorials and demos to work from as well - overall, a great starting point!

My problems with libGDX started right off the bat when I read the project setup instructions - they're great if you're using Eclipse as your IDE, but not so much if you're using IDEA 10.  What follows is the steps I went through to get a running Desktop/Android hybrid libGDX project configured with JetBrains' IntelliJ IDEA 10 IDE.  I have tried to be as explicit as possible - I apologise if there appears to be repetition.

For reference's sake, I used the Eclipse instructions for setting up a libGDX project located here: http://code.google.com/p/libgdx/wiki/ProjectSetup

Initial Project Setup

These 3 subsections identify how to create the required modules: the Base module containing the bulk of the game logic/assets as well as the Android and Desktop modules which are simply entry points for those platforms.

Base

  • Click File/New Project...
  • Select "Create project from scratch" and click Next
  • Enter a Name for your project (eg. "libgdx-example"), select "Java Module" type and click Next
  • Select "Create source directory" (eg. "src") and click Next
  • Click Finish

Desktop

  • Click File/New Module...
  • Select "Create module from scratch" and click Next
  • Enter a Name for your module (eg. "Desktop"), select "Java Module" type and click Next
  • Select "Create source directory" (eg. "src") and click Next
  • Click Finish

Android

  • Click File/New Module...
  • Select "Create module from scratch" and click Next
  • Enter a Name for your module (eg. "Android"), select "Android Module" type and click Next
  • Select "Create source directory" (eg. "src") and click Next
  • Select the Android Platform to target (eg. "Android 1.6"), select the Application option, enter a Package Name (eg. "com.example.libgdxexample"), enter an Activity Name (eg. "ExampleActivity")
  • Click Finish
  • If asked to create an Android Run Configuration, click Yes

Library Directory Creation

Android modules automatically have a "libs" directory created, but the Base and Desktop modules need manual intervention:

  • Right click on the "libgdx-example" module and select New/Directory, enter "libs" and click OK.
  • Right click on the "Desktop" module and select New/Directory, enter "libs" and click OK.

libGDX Library Copying

After downloading the libGDX nightlies, follow the copy instructions below for each modules' "libs" directory:

  • libgdx-example/libs: gdx.jar
  • libgdx-example/Desktop/libs: gdx-natives.jar, gdx-backend-lwjgl.jar, gdx-backend-lwjgl-natives.jar
  • libgdx-example/Android/libs: gdx-backend-android.jar, gdx-backend-android-sources.jar, armeabi & armeabi-v7a directories

libGDX Library Setup

  • Right-click on the base "libgdx-example" base project and select Open Module Settings
  • Select Libraries in the left dialog pane
  • Click the "+" button above the second dialog pane, select "libgdx-example" and click OK
  • Enter a pertinent Name (eg. "libgdx-example-libs"), click Attach Jar Directories, select libgdx-example/libs and click OK
  • Click the "+" button above the second dialog pane, select "Desktop" and click OK
  • Enter a pertinent Name (eg. "Desktop-libs"), click Attach Jar Directories, select libgdx-example/Desktop/libs and click OK
  • Click the "+" button above the second dialog pane, select "Android" and click OK
  • Enter a pertinent Name (eg. "Android-libs"), click Attach Jar Directories, select libgdx-example/Android/libs and click OK
  • Select Modules in the left dialog pane
  • Select libgdx-example in the second dialog pane, click the Dependencies tab and check the Export checkbox to the left of "libgdx-example-libs"
  • Select Desktop in the second dialog pane, click the Dependencies tab, click Add.../Module Dependency, select libgdx-example and click OK
  • Select Android in the second dialog pane, click the Dependencies tab, click Add.../Module Dependency, select libgdx-example and click OK

Android Module Assets Directory

We want the Desktop and Android modules to share the same images, sounds, etc without copying the files between directories.  The easiest way that I've found to do this is to create a symlink between a "data" directory containing the file assets in the "libgdx-example" directory into the default libgdx-example/Android/assets directory.  After this change, when referring to internal assets, use "data/" prepended to the asset location (eg. "data/sounds/gunshot.ogg"):

  • Right-click on the base "libgdx-example base module and select New/Directory, enter "data" as the new directory's name and click OK
  • Open a Terminal Console so you can issue commands
  • Type "cd <base project path>/libgdx-example/Android/assets" (use the appropriate <base project path> for your system) and press Enter
  • Type "ln -s ../../data" and press Enter.
  • Place any new asset items such as sounds, music, background, tile graphics, sprites, etc. into the libgdx-example/data directory, and both the Desktop and Android projects will pick them up automatically.

Note: This may be somewhat of an advanced step to some users - and it is only applicable to Linux/OSX operating systems.  Feel free to provide Windows-related steps, and I'll update this guide.

Base Module Initial Application

  • Right-click on the "src" directory under libgdx-example, select New/Package, enter your chosen package Name  (eg. "com.example.libgdxexample") and click OK
  • Right-click on this new package, select New/Java Class, enter "Game" and click OK
  • Edit the new file to contain the following:

Desktop Initial Application

  • Right-click on the "src" directory under Desktop, select New/Package, enter your chosen package Name  (eg. "com.example.libgdxexample") and click OK
  • Right-click on this new package, select New/Java Class, enter "DesktopStarter" and click OK
  • Edit the new file to contain the following:

Android Initial Application

  • Edit the existing libgdx-example/Android/src/com.example.libgdxexample/ExampleActivity class to contain the following:

Desktop Run Configurations

At this point here, you can run the Android Run Configuration, but you'll need to configure a Run Configuration for the Desktop module:

  • Click Run/Edit Configurations
  • Click the "+" button above the left dialog pane, select Application, enter a descriptive Name, select Desktop in the "Use classpath and JDK of module" dropdown, click "..." beside the Main Class, select "DesktopStarter" and click OK.