Serving files if they exist or fallback to wsgi app

Fri 14 June 2013

We want to achieve this routing scheme [2]:

  • /anything => static file, if it exists!
  • / => the wsgi app

For some reason this isn't as straightforward as in Nginx. Example [1].

With Apache you have to employ mod_rewrite, which is not straightforward if you're not writing rewrite rules and conditions all day long :)

The first this is to alias the wsgi app to a different location than the root:

WSGIScriptAlias /wsgi /path/to/the/app.wsgi

We need to make the location of the static files the DocumentRoot:

DocumentRoot /path/to/static/files
<Directory /path/to/static/files>
    Order deny,allow
    Allow from all
</Directory>

And now the mod_rewrite dance:

RewriteEngine on

Set some conditions: if the request path is not a filename:

RewriteCond /path/to/static/files%{REQUEST_FILENAME} !-f

And not a directory:

RewriteCond /path/to/static/files%{REQUEST_FILENAME} !-d

Optional, exempt any other special locations (I have an Alias /media):

RewriteCond %{REQUEST_URI} !^/media

And finally the rewrite rule:

RewriteRule ^(.*)$ /wsgi$1 [PT,L]
  • PT - Take the request to go through the URL mapping machinery (so Alias, WSGIScriptAlias etc are applied again for the resulting URL)
  • L - Stops processing any other RewriteRule for this request. This is implied by PT but it put it there in case I forget this :).

The full configuration:

WSGIScriptAlias /wsgi /path/to/the/app.wsgi

DocumentRoot /path/to/static/files
<Directory /path/to/static/files>
    Order deny,allow
    Allow from all
</Directory>

Alias /media /path/to/media/files
<Directory /path/to/media/files>
    Order deny,allow
    Allow from all
</Directory>

RewriteEngine on
RewriteCond /path/to/static/files%{REQUEST_FILENAME} !-f
RewriteCond /path/to/static/files%{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/media
RewriteRule ^(.*)$ /wsgi$1 [PT,L]
[1]

In Nginx you would simply do something like:

location / {
    root /path/to/static/files;
    try_files $uri $uri/ @wsgiapp;
}
location @wsgiapp {

    #  ...

}

... which is very straightforward.

[2]

The given rewrite example will probably only work with Django. Explanation:

For a given request to /some/url/bla, the request environ will have:

  • PATH_INFO = '/some/url/bla'
  • SCRIPT_NAME = '/wsgi'
  • SCRIPT_URL = '/some/url/bla'

If SCRIPT_URL is set, Django will remove what's in PATH_INFO from SCRIPT_URL and then use that as the SCRIPT_NAME. So reversing will work as expected. No need to use FORCE_SCRIPT_NAME.

If you use a different framework then you probably need to patch the SCRIPT_NAME as noted by Graham in the comments.

This entry was tagged as apache django linux mysql nginx python wsgi