Python, FastCGI, WSGI, and lighttpd · Thursday, March 24, 2005

The Set Up

Right now, the lighttpd web server is all the rage with Ruby on Rails people, PHP people, and excellent hosting providers. Why is this? Because lighttpd is extremely lightweight, has a small footprint, scales like nobody’s business, and is fast fast fast. The internals of lighttpd are really interesting, using kqueue and other fancy asynchronous networking operating system support to really put Apache to shame. But, the internals of lighttpd are a topic for another day…

I decided that it was about time that I was able to take advantage of this great web server from Python. My criteria was simple:

After some serious looking, and a few false starts, I am happy to report that I have found a way to make this happen! This opens up all sorts of interesting opportunities for writing better, faster web applications and services using Python.

Installing and Configuring Lighttpd

The first step in this whole process is to download, compile, and install lighttpd. I will leave this step as an exercise for the reader, since lighttpd is a very simple app to compile and install.

Once you have lighttpd installed, you will need to configure your lighttpd instance to talk to your Python FastCGI socket. In our example, we will use UNIX Domain Sockets to talk to the external Python process, but you can also do this using stdout, or TCP sockets. What follows in the lighttpd.conf that I am using:

server.port = 8080
server.bind = "127.0.0.1"
server.event-handler = "freebsd-kqueue"
server.modules = ( "mod_rewrite", "mod_fastcgi" )
server.error-handler-404 = "/test.fcgi"
server.document-root = "/home/jonathan/dev/test"
server.errorlog      = "/tmp/error.log"
fastcgi.server =    ( ".fcgi" =>

( "localhost" => ( "min-procs" => 1 "socket" => "/tmp/fcgi.sock" ) ) )

Thats it. Start up your lighttpd instance with this configuration file, and it will proxy out all requests via FastCGI to the /tmp/fcgi.sock file socket.

Writing Your Python FCGI Handler

Now you need to write a simple Python FastCGI handler that complies with the WSGI. This is thankfully simple. Just grab fcgi.py from here and put it into your PYTHONPATH.

Now, create your python script, as follows, name it test.fcgi and put it into your server root:

from fcgi import WSGIServer

def myapp(environ, start_response):

print 'got request: %s' % environ start_response('200 OK', [('Content-Type', 'text/plain')]) return ['Hello World!\n']

WSGIServer(myapp, bindAddress = '/tmp/fcgi.sock').run()

Save this python file and run it. You are all set! I hope that at least someone finds this useful!

UPDATE: Comments closed thanks to idiot comment spammers. Email me if you have something you would like to add.

Comment

  1. Thanks heaps :)

    How stable is the fcgi.py module?

    The most recent/modern fastcgi module I’ve seen is the jonpy one, but its version is only 0.06 and its discussion list is very quiet.

    How stable is fcgi.py in medium production environments?
    jason    1803 days ago    #
  2. I am not entirely sure how stable the linked module is in production. I am working with it on a new project for my employer, and it seems extremely stable thus far. The author of the fcgi.py module may be more inclined to comment on this. It works great for me so far though. I am going to be using it largely to serve up many “service” requests (REST, XML-RPC), and thus far I have been impressed.

    The WSGI is very low level though. If you want to write an application, you will probably want to either write or find a third party toolkit that wraps WSGI in some higher-level objects (Quixote, CherryPy, Twisted.Web, WebWare, etc.). I am not really writing “web applications” so much as web services, so I am rolling my own in order to keep the list of dependencies low.

    The JonPy fastcgi implementation has issues, and will not work with lighttpd at this time. It works with Apache at this point, but the module hasn’t changed in a long time, and that always puts up a red flag.

    I assume that the mystery Jason in the above comment works for a recently popular hosting company? :)
    Jonathan LaCour    1801 days ago    #
  3. I tried out lighttpd with fcgi and the saddi wsgi fcgi server.

    The performance I got was lower than a mod_python-> baseHTTP xmlrpc server combination I had done. One reason could be because the sadi server uses threads, and is not async like the simpleserver I used. So I wanted to try the sadi server out using multiple processes.

    Unfortunately lighttpd fcgi with the saddi fcgi/wsgi server does not work with round robin load balancing :(

    I got lighttpd to work(without load balancing) with the config below(the last two servers on the sockets got no requests). So you do not need to use the error page like you did with your config.

    I needed to do a touch pp.fcgi in the document root for it to work.

    There are a few fixes for lighttpd for the next version which should be nice :) Some of them in the mod_fcgi module. Also for a static file bench mark my apache2 configuration performed the same number of requests as the lighttpd. Except apache used like 500MB of ram and lighttpd used about 9MB :)

    One other downside is that lighttpd currently does not support compression for dynamic processes. So you will need to do your compression by yourself.

    Luckily there is some gzip middleware floating around, including one here:
    http://www.saddi.com/software/py-lib/

    If anyone can get load balancing with this combination I would be quite happy :) Or maybe there is another way to speed this combination up? I really like lighttpd as a front end webserver :)

    However I may try out twisted too if I can figure out how to do wsgi with it.

    fastcgi.server = ( ”.fcgi” =>
    ( “server1” =>
    (
    “socket” => ”/tmp/fcgi1.sock”,
    “docroot” => ”/home/rene/mystuff/pretendpaper/pp/”,
    “min-procs” => 1
    )
    ),
    ( “server2” =>
    (
    “socket” => ”/tmp/fcgi2.sock”,
    “docroot” => ”/home/rene/mystuff/pretendpaper/pp/”,
    “min-procs” => 1
    )
    ),
    ( “server3” =>
    (
    “socket” => ”/tmp/fcgi3.sock”,
    “docroot” => ”/home/rene/mystuff/pretendpaper/pp/”,
    “min-procs” => 1
    )
    )

    )

    #fastcgi.debug = 1

    url.rewrite = (
    ”^/pp($|\?.+)” => ”/pp.fcgi$1”,
    )
    Rene Dudfield    1792 days ago    #
  4. An addition to my last post…

    I forgot that I had mod_cache enabled in apache2. Which makes things a lot faster if your dynamic content is cached ;) With my dynamic data I cache function calls and mark them dirty on updates, and inserts. So things can be a lot quicker.

    There are so many different variables to consider…

    This is where apache is good. So many different modules for it. mod_cache in addition to the compression of dynamic content is really nice.

    However I get almost double the performance with fcgi assuming no caching is going on with apache.

    For making sure lighttpd stays up, it is good to use daemon tools. Like in this explanation:
    http://bougyman.com/miscfiles/RailsonDebian.html

    Also changing the fcgi.py buffer size seemed to be 20% quicker for me.

    WSGIServer(app, bindAddress = ’/tmp/fcgi.sock’, maxwrite=8192 *10).run()

    So in the end I can get about 112 dynamic requests per second with lighttpd and saddi fcgi wsgi server. Quite nice indeed :)

    With mod_python -> simpleHTTP xmlrpc server and mod_cache I can get around 400 requests per second if the data does not change. But around 35 per seoncond if the data does change.
    Rene Dudfield    1792 days ago    #
  5. Hello and this is vital info. Hopefully lighttpd docs will have a link here cause at the moment they seem to support ruby too much.

    Anyways I’m running 1.3.13 and your config is not valid in this version:
    a) that freebsd stuff is not available in GNU/Linux at least
    b) You have a missing comma after 1 [in missing procs] and the conf is not parsable without that comma

    also the default conf says that 2 modules should be always on:
    # at least mod_access and mod_accesslog should be loaded

    I didn’t load them but I get to see Hello World anyways with this config:

    server.modules = ( “mod_rewrite”, “mod_fastcgi” )
    server.error-handler-404 = ”/test.fcgi”
    server.document-root = ”/www/pages”
    server.errorlog = ”/tmp/error.log”
    fastcgi.server = ( ”.fcgi” =>
    ( “localhost” =>
    ( “min-procs” => 1,
    “socket” => ”/tmp/fcgi.sock”
    )
    )
    )

    Thank you for the info.
    Nikos Kouremenos    1791 days ago    #
  6. I have played with the fcgi.py you mentioned, and although very complete, I didn’t like the philosophy of spawning a thread for every request ; besides a lot of it looks like C and not like Python.

    So I rewrote it entirely today using stackless python with an original twist.

    There is an asynchronous server using select/poll to accept new connections and dispatch the incoming data packets. All the FastCGI protocol data including stdin and stuff is read entirely in this stage. I have a special input stream class which stores stdin in a cStringIO and then switches to a temporary file when the data size grows to more than a fixed threshold. This way, any number of concurrent big slow file uploads can be processed concurrently without hogging any thread or server process. All this uses one thread only ; all the connections use stackless tasklets communicating with channels.

    Once all the request data has been collected, the request can be processed. The asynchronous server simply yields the object containing everything from the socket to the streams (the server is a generator, which is a very nice way to implement servers instead of overriding the handle() method or something.) When the objects get out of the asynchronous server, the sockets are set back to blocking mode.

    These objects are then stuffed into a queue which is read from by a thread pool, processing the requests and handling the data to send to the client with simple send() blocking calls.

    It’s not yet WSGI compliant but will be tomorrow. It’s rather easy to do.

    It’s still probably full of bugs and there is no elaborated error or signal handling like in fcgi.py

    Thanks to the asynchronous approach, scalability is awesome.

    I tried a simple benchmark. The generated page is a simple Hello World with a copy of the CGI’s stdin. The page length is 97 bytes, so this is just to measure the overhead.

    On my Pentium-M 1.6G laptop, untuned, with a thread pool of 10 threads… benchmark from an Athlon XP 2.5G over 100Mbps LAN :

    ab2 -n 10000 -c 10
    > 1200 pages/seconds

    ab2 -n 10000 -c 200
    > 1121 pages/second

    ab2 -n 10000 -c 1000
    (this one is local, the benchmarker machine ran out of file descriptors)
    > 1142.51 pages/second

    So I guess the throughput in pages/second is a flat line at about 1000 hits/second from 1 to 1000 simultaneously connected clients.

    Not that bad.

    Oh yeah. The webserver is lighttpd. It does 14000 hits/second on a small static file on this machine. Not that bad either.

    And it’s not even using fastcgi persistent connections.
    Peufeu    1773 days ago    #
  7. Oh yeah, I forgot, with 1000 simultaneous connections, it uses less than 100 megabytes of RAM.
    Peufeu    1773 days ago    #
  8. Peufeu, that sounds very interesting. Any reason in particular you chose to use stackless though? You are really limiting your audience, and you can get by with standard cPython and get close performance, I am betting.

    If you make it run on standard cPython, I will definitely use it, given that you make it available under some BSD-style license that is!

    Either way, I would love to see the implementation.
    Jonathan LaCour    1772 days ago    #
  9. Well, since I’ve tried Stackless, I don’t even want to hear about a language which doesn’t support coroutines. It’s just too powerful.

    I guess you could do the same in Python by twisting the sequential code which uses stackless coroutines inside out into ugly event handlers…

    I’m not finished with this project, more later.
    Peufeu    1769 days ago    #
  10. Fair enough. I am a fan of many of the concepts behind stackless — including coroutines. That being said, my point still stands: the vast majority of Python developers don’t use Stackless. A WSGI layer on top of FastCGI can be implemented asynchronously without Stackless. Like you said, it may be a little ugly, but its a framework that complies to the WSGI, which means that any applications that use it won’t care if its ugly — just that its fast! I for one would definitely be interested in using your framework, but I have a lot of code that is deployed on standard CPython, so I wouldn’t be able to take full advantage unless it would run on CPython.

    That being said, there is a very interesting discussion going on right now on the Python development mailing list about a PEP regarding anonymous blocks built on top of generators. A side-effect of the PEP, which seems to be on track for approval, would be the ability to build something like coroutines into Python itself. The original PEP was PEP 340, but I believe that they are trying to split it up into two PEPs: one for anonymous blocks, and one for the generator/coroutine stuff. You should check it out, and maybe even provide your framework as a decent example of why the PEP should be approved.

    I really look forward to hearing more about your project. Let me know the second you are ready to set it free.
    Jonathan LaCour    1769 days ago    #
  11. Stackless core dumped on me today.

    I’m reimplementing the thing with standard python. This is a major PAIN, but, well.

    Right now I have this framework running, an asynchronous server collecting request data, putting complete requests in a thread pool…

    Request processing is done by a thread in the pool (right now returning ‘x’ * 10000)

    The new thing is that once a request has its data to send, it is put back in asynchronous mode and the data is sent back to the client by the same poll() loop which handles the data reads.

    Hence I can have any number of receives and sends at the same time, while keeping a low number of running threads.

    This time I’m using HTTP purely to eliminate the overhead of parsing fastcgi protocol.

    With zero request parsing, just the send, receive and poll overhead, performance is a straight line at about 4000 requests/second upto 1000 simultaneous connections, then I run out of file descriptors.

    That’s with a small page. If I return a 120 kbyte page I can push about 200 MBytes/second of data.

    Right now the code is a mess but I like the concept.

    I wonder if I’m going to use fast-cgi or not, or using lighttpd as a proxy in front of my server which would then stay a HTTP server.

    Drawback would be that I’d have to implement a bit of the HTTP protocol. So in the end I think I’ll use FastCGI because the parsing overhead will likely be a lot less than the time it takes to actually render the page.

    I’m going to make a connection object able to jump back and forth between asynchronous server and thread pool to implement scalable file uploading too. Asynchronous socket reads, synchronous file write() calls.
    Peufeu    1768 days ago    #
  12. I am really glad to hear that you are working on an implementation for standard Python. This means that, if you choose to release it under some open license (BSD-style, please please please), that many people would benefit. Potentially including me.

    From your description it seems like you are implementing it in a very sound way, properly balancing asyncronous networking with a thread pool for processing. After some time, I have finally come to the conclusion that this is the best approach in Python — asyncore for the network and file IO, and thread pools for processing.

    I would strongly encourage you to go with FastCGI. You may lose some performance in some ways, but you can keep HTTP out of your code, and allow people to choose their own HTTP server (like lighttpd, apache, or IIS if they are stupid).

    Keep me informed, and if you want some help, let me know. Posting your work on a public SVN repository might inspire some other people to contribute.
    Jonathan LaCour    1767 days ago    #
  13. In the end it was possible (but less elegant) to do it in standard Python. I miss coroutines, though.

    I like the concept of Asynchronous + Thread pool a lot. It really works well. For instance if the queue is full I can send a Server Busy response in about 300 microseconds.

    Well, now it’s at 1000 lines of code total, can serve both HTTP and FCGI on two different ports… at the same time !...

    This is the test script, note how the two serving sockets are instanciated. This is the power of non-inherited OO classes.

    Note that I use multiple inheritance and mixins everywhere to maximize code reuse, except where it would be less powerful than passing classes and closures around…

    import socket, Queue, time

    from Core.Server.AsyncServer import Server, Handlers, FastCGI, HTTP
    from Core.Thread.Pool import ThreadPool
    from Core.Server.Context import Context

    def test():
    # open a server socket
    fcgi_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    fcgi_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    fcgi_sock.bind((‘localhost’,1028))
    fcgi_sock.listen(socket.SOMAXCONN)

    http_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    http_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    http_sock.bind((‘localhost’,1027))
    http_sock.listen(socket.SOMAXCONN)

    # define a default request executor
    def executor( req ):
    env = req.environ.items()
    env.sort()
    req.connection.sendErrorResponse( 200, “Request environment : %s” % ”\n”.join([”%s : %s” % kv for kv in env]) )

    # build a server context, assembling the various classes
    fcgi_context = Context()
    fcgi_context.connectHandler= FastCGI.Connection
    fcgi_context.requestHandler= FastCGI.Request
    fcgi_context.requestExecutor= executor

    http_context = Context()
    http_context.connectHandler= HTTP.Connection
    http_context.requestHandler= HTTP.Request
    http_context.requestExecutor= executor

    # Create a thread pool
    queue = Queue.Queue( )
    thread_pool = ThreadPool( queue, 10 )

    # Create an asynchronous socket poller, create a listener on the server socket, put it in the pool
    pool = Server.AsyncPoller( queue.full )
    server = Handlers.Listener( fcgi_sock, pool, fcgi_context )
    server = Handlers.Listener( http_sock, pool, http_context )

    try:
    for x in pool.run():
    try:
    thread_pool.put_nowait( x )
    except Queue.Full:
    x.serverOverload()
    del x# garbage collect this request dammit !
    finally:
    thread_pool.stopThreads()

    test()

    I will definitely put this thing on SourceForge when it’s finished, but it’s not going to be alone. I’ll build a Web Framework in the spirit of Wee and Seaside on top of it (it’s a huge project). However while I’m in full support of open source, I will licence it GNU completely free for non-commercial use, but commercial use will be of course non-free (I have to eat dammit).
    Peufeu    1765 days ago    #
  14. There are still bugs in the FCGI part, the HTTP part is quite good now (ie. request time about 350 microseconds). Of course these amazing times will be swamped by dynamic page generation times, but it’s good to know there is no waste of resources…

    Also, FCGI is a lot slower than HTTP (about 1.5 ms overhead per request), this is why I keep the HTTP option.

    What is the advantage of FCGI versus HTTP behind a proxy ? it looks more powerful, but I can’t really figure out in which case it would really be more useful.
    Peufeu    1765 days ago    #
  15. I know where FastCGI could be better option than httpd behind proxy.

    This is serving static files protected with authorization.

    Simple http basic authorization could be handled by webserver, but more complex one with various access right, and/or cookie+form based is out of scope.

    FastCGI application could be ran as AUTHORIZER, and this scenario should be manageable this way.

    However, it’s theory, I haven’t tried it in reality – I discovered this feature of FastCGI few days ago. :)
    kasou    1765 days ago    #
  16. With regard to the “why support FastCGI” question, I have a few reasons:

    # HTTPS
    # Authentication using Apache or lighttpd plugins
    # Keeping HTTP out of process
    # Having multiple processes exposed to HTTP via a single web server, regardless of what machine they are on

    Also, I know you have to eat, but I don’t think anyone will pay money for yet another Python web framework. I would love to use bits and pieces of the FCGI framework that you discuss at my work, but I am almost certain I couldn’t convince the higher ups to pay for such a thing. The GPL carries a huge penalty for frameworks. Even the LGPL isn’t optimal.

    A better approach, in my mind, would be to license it BSD-style and offer paid support and consulting. It’s much more the Python open source way, and you are much more likely to make money from the project in this way.

    Please don’t take the licensing comments the wrong way, I hate getting religious about things like this, but I see a potential opportunity that could be wasted!

    I would like to post a top-level article about this project the second that you release. Make sure to keep us informed :)
    Jonathan LaCour    1765 days ago    #
  17. # HTTPS
    # Authentication using Apache or lighttpd plugins
    # Keeping HTTP out of process
    # Having multiple processes exposed to HTTP via a single web server, regardless of what machine they are on

    This everything is possible with httpd behind reverse proxy. Zope is usually deployed behind Squid or apache with mod_proxy. You have all the good things of FastCGI (in responder mode).
    kasou    1765 days ago    #
  18. Great article, reminds me of how I need to shore up the description of our own python support.

    “I assume that the mystery Jason in the above comment works for a recently popular hosting company? :)”

    Nope, that wasn’t me, but a good question nonetheless.
    Jason Hoffman    1756 days ago    #
  19. Hey all,

    I’ve been trying to get lighttpd playing nicely with CherryPy for the past couple days using a combination of the above recipe and the code seen here.

    I’ve tried tweaking everything (even CherryPy’s wsgiapp module) but have been unsuccessful so far. I’m confident that it is possible, or at least will be when CherryPy 2.1 is released. If anyone can get it working before then, give it a shot!
    Brian Beck    1743 days ago    #
  20. Well, it looks like the live preview parses links but they’re stripped from the post. The code I referenced is here: http://groups-beta.google.com/group/cherrypy-users/msg/072a8b36361a0774
    Brian Beck    1743 days ago    #
  21. Snippet this!
    Basil Crow    1740 days ago    #
  22. http://textsnippets.com/
    Basil Crow    1740 days ago    #
  23. Anyone made any extra progress on this?
    ahhh    1716 days ago    #
  24. SCGI is a lot simpler than FastCGI, yet very similar, and lighttpd supports it now. I’d advise people to look at that as an alternative. Works nicely with CherryPy too.
    John Speno    1716 days ago    #
  25. Ahhh that sounds great. Not much discussion to be found on it in either the lighttpd or python camps however. Looking forward to seeing some SCGI apps and tutorials!!
    ahhh    1715 days ago    #
  26. Here’s a simple SCGI example using CherryPy:

    http://www.cherrypy.org/wiki/ScgiWsgi

    I used apache there, but that was because it was before lighttpd had SCGI support…
    John Speno    1703 days ago    #
  27. SCGI was written by Neil Schemenauer for use with the Quixote web app package. Here is its homepage.
    Nicola Larosa    1631 days ago    #
  28. Weird, the preview was correctly showing the link, but the final message does not have it. Here it is:

    http://www.mems-exchange.org/software/scgi/
    Nicola Larosa    1631 days ago    #
  29. Peufeu: “I will licence it GNU completely free for non-commercial use”

    You realise that this sentence contradicts itself – the GNU licences don’t impose commercial/non-commercial restrictions since this would undermine the very nature of those texts.

    Jonathan LaCour: “The GPL carries a huge penalty for frameworks. Even the LGPL isn’t optimal.”

    What? That people can’t extend the framework and then weld the code shut? The LGPL seems more optimal for the end-users and the community to me.

    Jonathan LaCour: “A better approach, in my mind, would be to license it BSD-style and offer paid support and consulting. It’s much more the Python open source way, and you are much more likely to make money from the project in this way.”

    Whilst there may be people with expertise around widely-used BSD-licensed code who can make money from those works, I would dispute the “more likely” qualification. If you license the framework under the GPL but offer commercial licensing, you’re more likely to get paranoid businesses paying for the privilege of hiding their code. Meanwhile, if you license it under the BSD, freeloaders (ie. the majority of businesses, given the chance) will only come to you if they really get stuck.

    And I definitely dispute the “Python open source way” assertion. Just because CPython itself has a “liberal” licence doesn’t mean that everyone has to sheepishly follow suit, especially since that licence doesn’t always serve the community.

    (P.S. Nice live preview!)
    Paul Boddie    1631 days ago    #

commenting closed for this article