Pages tagged as ‘mod_wsgi’

Multi Trac / Django Hosting with mod_wsgi

September 12th, 2007

As you might now we switched the pocoo trac to mercurial, mod_wsgi and splitted it in the same go. The new structure is can be found at dev.pocoo.org. What you cannot see there is how all that is implemented. And I tell you. It’s dead simple.

Basically we use mod_wsgi for hosting the tracs. There are many reasons for that but the most important one is that you can host multiple trac instances without much configuration. Basically the configuration binds a wsgi application to a URL match rule. One important thing is the maximum-requests setting. To understand this parameter you have to know that mod_wsgi does not only keep a pool of running python interpreters, but also your application with all data in the memory. Now that’s a big difference to mod_php where your application is sourced on request and removed from the memory after the reqest. So basically you cannot create memory holes, which you can do in python. If your application leaks memory (and trac tends to do so) you can tell mod_wsgi to restart one python interpreter after 500 requests for example. That setting of course depends on your trac version, the number of plugins etc. And especially how many memory you have in your application. Here the Apache config:

<VirtualHost *:80>
    ServerName dev.example.org
    RewriteEngine On

    WSGIDaemonProcess tracs threads=10 maximum-requests=500

    RewriteCond %{REQUEST_URI} ^/([a-z_]+)
    RewriteRule . - [E=TRAC_ID:%1]
    WSGIScriptAliasMatch ^/([a-z_]+) /var/trac/trac.wsgi

    <Directory /var/trac>
        WSGIProcessGroup tracs
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>

    <LocationMatch /([a-z_]+)/login>
        AuthType "Basic"
        AuthName "Trac Instances Login"
        AuthUserFile /var/trac/users
        Require valid-user
    </LocationMatch>
</VirtualHost>

Then we need a trac.wsgi file which is basically just a minimal WSGI application that dispatches our key. Say we have our trac instances in /var/trac/instances, every trac in it’s own folder. Then we can use this code:

#!/usr/bin/python
from os import environ, path
from trac.web.main import dispatch_request

def application(environ, start_response):
    trac_path = path.join('/var/trac/instances', environ['TRAC_ID'])
    if path.exists(trac_path):
        environ['trac.env_path'] = trac_path
        return dispatch_request(environ, start_response)
    start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
    return ['Not Found']

You can of course modify that not found message, maybe render a fancy HTML page or just redirect to the index of that domain or whatever. The important thing is that you set the path to the trac instance before calling the dispatch_request function. In theory you can do that from the apache config too, but in that situation you cannot check if the trac really exists.

And now about the django hosting part. Basically you can do the same for django. Django basically has a environment key called the DJANGO_SETTINGS_MODULE key. This key basically controls what settings module django will import. Unfortunately the whole django core is not process safe, so you cannot run two different django powered applications in the same python interpreter. This however is not that much of an issue with mod_wsgi, because you can tell mod_wsgi to not share the interpreter. (In the trac config above we shared the interpreter to save some memory)

Your config could look like this:

<VirtualHost *:80>
    ServerName www.example.org
    WSGIDaemonProcess django_app1 threads=10 maximum-requests=5000
    WSGIScriptAlias /app1 /var/www/django_app1.wsgi
    WSGIDaemonProcess django_app2 threads=10 maximum-requests=5000
    WSGIScriptAlias /app2 /var/www/django_app2.wsgi

    <Location /app1>
        WSGIProcessGroup django_app1
        WSGIApplicationGroup %{GLOBAL}
    </Location>
    <Location /app2>
        WSGIProcessGroup django_app2
        WSGIApplicationGroup %{GLOBAL}
    </Location>
</VirtualHost>

The actual “django_appX.wsgi” file is very, very simple. It just adds the folder and instanciates the django wsgi app:

#!/usr/bin/python
import sys, os
sys.path.insert(0, '/path/to/django_appX')
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_appX.settings'

from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()

Hope I could help a little bit, if you have some questions to our server setup just send me a mail or write a comment. Finally, we first encountered some problems with mod_wsgi two months ago, but at the moment everything is working well, a lot better than any other server setup we used. You can even put python applications into the context of another user which basically replaces fastcgi + suexec.
And btw, the support we got from Graham is really, really good :)

Update: removed WSGIPassAuthorization like Graham suggested in the comments below.

cogitations driven by wordpress