Pylons deployment with Lighttpd and Flup

What's fast, easy to set up, and good for running web applications?

Wait, did that sound too much like a joke? Anyway, I'm talking about the combination of Lighttpd (a web server and alternative to Apache) and Pylons (a Python web framework).

Lighttpd (as well as the similar nginx) has been getting an increasing amount of attention in the past few years, especially for use with web applications in combination with dynamic languages such as Python and Ruby. It has a relatively small memory footprint and, due to its asynchronous network layer, can easily handle both dynamic web applications and lots of static files at the same time.

The "catch" is that it never runs apps in embedded interpreters, as Apache modules such as mod_php, mod_perl and mod_wsgi do. Instead, it farms requests out to apps running in separate processes, using protocols such as FastCGI or SCGI, or acting as an HTTP proxy.

This article will show you a basic setup for Pylons and Lighttpd, using the FastCGI protocol. I'm going to assume you already have a basic Pylons app set up and know how to run it; if not, hop over to the Pylons Getting Started documentation. Lighttpd installation instructions can be found on the Lighttpd documentation page.

Let's go.

Pylons configuration

I've started off with the FastCGI protocol here for reasons discussed below. SCGI, for those who prefer, is a drop-in replacement (you can actually just change all instances of fcgi and fastcgi to scgi in the Pylons and Lighttpd configuration examples.) HTTP proxying is only slightly different.

You will need the flup package installed. Use easy_install flup or pip install flup, or visit the Flup home page.

Only a small change needs to be made to the INI file for your Pylons application (e.g.: development.ini) to use Flup instead of the default Paste HTTP server.

development.ini (partial)
[server:main]
use = Flup#fcgi
host = localhost
port = 5000

You can then run the app with paster serve --reload development.ini, as usual.

Lighttpd configuration

Below is a minimal lighttpd.conf containing configuration that you can adapt to your needs. It's missing a lot of what you would actually want, such as logging and MIME types, so cut and paste liberally.

lighttpd.conf
server.modules = (
    "mod_fastcgi"
)

server.port = 80
server.document-root = "/dev/null"

$HTTP["host"] =~ "^example.com|localhost$" {
    server.document-root = "/path/to/files/"
    $HTTP["url"] !~ "^/static/" {
        fastcgi.server = ("/" => ((
            "host" => "127.0.0.1",
            "port" => 5000,
            "check-local" => "disable",
            "disable-time" => 1,
            "fix-root-scriptname" => "enable"
        )))
    }
}

This configuration uses Lighttpd's regular-expression selectors to create a host that can be accessed either as example.com or as localhost on port 80. This is suitable if you are sharing one configuration file between your local development machine and the example.com server, but unnecessary if you are using separate configuration files, or remapping example.com to your local machine for development purposes.

The positioning of server.document-root and the second regular-expression selector means that URLs that start with /static/ will be served by Lighttpd from /path/to/files/static/, while everything else will be served by the application.

Disabling check-local means that for those URLs handled by the application, Lighttpd will not attempt to look at the filesystem at all. disable-time, on the other hand, is the amount of time Lighttpd will wait between tries if it fails to connect to the application server. Setting this to a small value makes restarts or redeployments of your app go faster, if you have not implemented a seamless restarting process for your app.

The fix-root-scriptname argument is necessary for any WSGI app, Pylons or otherwise, that is mounted on /. Otherwise SCRIPT_NAME and PATH_INFO will not be set properly.

Why FastCGI?

Some perceive FastCGI as outdated, but it works well, and given that Flup and Lighttpd handle the protocol for you there's no real need to worry about it. Still, there are options.

If you would like to use SCGI instead of FastCGI, simply load mod_scgi in server.modules and change fastcgi.server to scgi.server in the config above. Lighttpd 1.4.15 and previous have a segfaulting bug in mod_scgi, and it should not be used. Since SCGI and FastCGI are otherwise drop-in replacements and their performance seems equivalent, I simply used FastCGI.

You could also use Paste's HTTP server (the same one used for development) behind mod_proxy. This looks like the above, but load mod_proxy in server.modules, and put following in place of the fastcgi.server block:

proxy.server = ("" => ((
    "host" => "127.0.0.1",
    "port" => 5000
)))

Unfortunately, in my tests Paste's HTTP server (based on the standard library's BaseHTTPServer) did not hold up under load, whereas Flup did spectacularly. You could also try another Python HTTP server that speaks WSGI.

Load balancing

All three options (FastCGI, SCGI, and proxying) in Lighttpd come with load-balancing capability. See the "extra" set of parentheses around the host and port definition in the configuration examples above? Simply add multiple such blocks, like this:

proxy.server = ("" => (
    ("host" => "10.0.0.10",
    "port" => 5000),
    ("host" => "10.0.0.11",
    "port" => 5000)
))

If using mod_proxy, you can then alter the load-balancing algorithm using the proxy.balance parameter (see the mod_proxy docs).

What about mod_wsgi?

Apache (not Lighttpd) has mod_wsgi, which runs one or more embedded Python processes in each Apache process, passing requests directly to WSGI applications. This is a solid way to deploy Pylons apps.

See the Integration With Pylons document on the mod_wsgi site for information on how to use paste.deploy.loadapp to make Pylons compatible with mod_wsgi.

An advantage of mod_wsgi is that you don't have to keep track of as many processes. However, if you use Supervisor to manage your Pylons apps and Lighttpd, it's no trouble at all.

A disadvantage of mod_wsgi is that you don't easily have the ability to split your HTTP server and app servers across multiple machines, which is easy with Lighttpd and a separate Python process. You can do it, of course, by running Apache on each machine and using mod_proxy or a load balancer.

Also, if you are building your own Apache and Python from source, you need to be very careful about library versions. If Apache and Python are built against different versions of the Expat library, for instance, mod_wsgi will segfault. This is not a concern when Python is running as a separate process, rather than embedded.

(See Graham Dumpleton's comment, below, for some updates -- mod_wsgi can run daemon processes, although they're managed by mod_wsgi, and apparently the Expat library hasn't been a problem for a while.)

This isn't too farfetched -- if you want to use a more recent version of Python than is available in your package manager (some stable Linux distributions seem to habitually lag behind Python point releases), or if you want to use the exact same version on different platforms, you'll probably build your own.

One final note on mod_wsgi: writing to STDOUT (e.g. using the print statement) will cause serious errors, so make sure to excise all print statements and use logging instead.

Addendum

Lighttpd is my personal favorite way to deploy Python web applications. (Some people prefer nginx, also a solid option.)

Not only does separating the HTTP process from the Python process allow for easy and flexible deployment, but Lighttpd has an amazingly useful configuration system. The basic syntax is easy to work with, and for advanced uses you can generate configuration files dynamically (see: include_shell) or run Lua scripts in the server process to do advanced URL rewriting/redirecting, maintenance notices, application firewalls, etc. (see: mod_magnet).

Prev: Show your IP address using a Lua/Lighttpd script

Next: YouTube video embedding with XSLT

Share this content

Comments

Avatar picture for Graham Dumpleton A few corrections. The mod_wsgi module has an out of process daemon mode that is similar in some ways to FASTCGI/SCGI, albeit that mod_wsgi still controls the management of those processes directly. The expat issue was only an issue back with Python 2.4 or earlier. Printing to stdout does not cause errors if you are using most recent mod_wsgi major version. That you get an error when using 'print' is strictly speaking an error in your code anyway and not a problem with mod_wsgi. A portable WSGI application should never write to stdout. See '[Link to blog.dscpl.com.au]'.
Avatar picture for Jason Stitt Thanks for the corrections, Graham.

It is indeed better to log than print, but print does not cause errors in various other WSGI servers, so it's possible for print statements to slip in. It's worth a "grep" to make sure there aren't any before using a server that doesn't allow them.

I could swear I had a compilation issue with a more recent version than 2.4, which I haven't used in a while, but I don't have everything handy to try it out. I'll add a note.
Avatar picture for suresh i am using lighttpd1.4.24 server in ubuntu.i have a python script as index.py in my server.document-root so that when i start my lightty server the script gets run..inside the script i need to redirect the server to a particular url(ex=[Link to www.gmail.com])..can anyone give me the entire python script..i have tried with some but nothing works for me but server running successfully without redirection..
Avatar picture for kosiara Pylons deployment on Apache server. (mod_wsgi)

[Link to kosiara87.blogspot.com]

Post Comment

All comments are personally reviewed and must be:

  • On-topic
  • Courteous
  • Not self-linking or spam
(Optional. This is your one self-link.)