Python FastCGI with Lighttpd

16 May 2013
I finally decided to bite the bullet and learn how to use Python rather than PHP for dynamic website content, and in the process I decided to do it via FastCGI as that is more in line with the Model-View-Controller paradigm that is in favour these days. The problem is that I could not find an all-in How-To, so I decided to write my own. I will assume a non-root install (doing a root install is just a case of changing the file paths) and that everything will be built from source tarballs, because in my experience fewer things screw up that way, and it results in a much more future-proof guide.

Building Python & Flup

These instructions were written using a Ubuntu 12.04 workstation, but should work with minimal changes on other distributions such as Slackware and the various BSDs. Here I used Python 2.7.5, Setup Tools v0.6c11, and Flup v1.0, but only trivial changes should be needed for more recent versions. Grab the tarballs from the above websites, and extract them:

tar -xzvf ~/Downloads/Python-2.7.5.tgz tar -xzvf ~/Downloads/setuptools-0.6c11.tar.gz tar -xzvf ~/Downloads/flup-1.0.tar.gz

Build & install Python:

cd Python-2.7.5 ./configure --prefix=/home/remy/WWW/Python make && make install

Once Python has been built, you want to rig the PATH enviornment variable so that the modules get installed into this build rather than the main system Python install (if any):

export PATH=/home/remy/WWW/Python/bin:$PATH

Use which python to make sure the right version of Python is being picked up. Once that is done you can build & install Setup Tools and Flup:

cd ../setuptools-0.6c11 python setup.py install cd ../flup-1.0 python setup.py install

Python is now ready to go.

Writing the Python handler

This is based on the handler given on the Python FastCGI docs, with the main change being the use of our locally installed Python rather than the system-wide one. The handler generates a web page that has all the FastCGI environment variables.

#!/home/remy/WWW/Python/bin/python # -*- coding: UTF-8 -*- from cgi import escape<br> import sys, os from flup.server.fcgi import WSGIServer def app(environ, start_response):    start_response('200 OK', [('Content-Type', 'text/html')])    yield '<h1>FastCGI Environment</h1>'    yield '<table>'    for k, v in sorted(environ.items()):        yield '<tr><th>%s</th><td>%s</td></tr>' % (escape(k), escape(v))    yield '</table>' if __name__ == '__main__':    WSGIServer(app).run()

Note that Lighty somehow tells this Flup which socket to use, so if you want to start it manually, you will need to specify the parameters:

WSGIServer(app,bindAddress=('127.0.0.1',8000)).run()

Setting up Lighttpd

Below is what needs to go into your Lighty configuration file, and it is rigged so that anything prefixed with the absolute URL /py/ is dispatched to your Python script. Lighty will fire up a copies of this script (in this example, /home/remy/WWW/fast.py) as needed. You will also need to make sure your server.modules contains mod_fastcgi.

fastcgi.debug = 1 fastcgi.server = (     "/py/" => ((         "socket" => "/tmp/fcgi.sock",         "check-local" => "disable",         "bin-path" => "/home/remy/WWW/fast.py",         "max-procs" => 1         ))     )

I'm not sure why Lighty wants double brackets around the FastCGI helper parameters.

Checking it all

If all goes well, you should see the following in the error log when you start Lighty:

2013-05-16 23:48:17: (log.c.166) server started 2013-05-16 23:48:17: (mod_fastcgi.c.1365) --- fastcgi spawning local        proc: /home/remy/WWW/fast.py        port: 0        socket /tmp/fcgi.sock        max-procs: 1 2013-05-16 23:48:17: (mod_fastcgi.c.1389) --- fastcgi spawning        port: 0        socket /tmp/fcgi.sock        current: 0 / 1

Point your web browser at http://127.0.0.1/py/ and you should get the output of the handler, which consists of an environment variable dump. SCRIPT_NAME, REQUEST_URI, and QUERY_STRING will be the interesting ones.