Python FastCGI with Lighttpd
16 May 2013I 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.