Continuing on in my series on gevent and Python, this article discusses how to use gevent to power your Python WSGI web applications. If you're just getting started with Gevent, you might want to read the previous articles in this series first:
- Introduction to Gevent
- Gevent, Threads, and Benchmarks
- Gevent and Greenlets
- Greening the Python Standard Library with Gevent
- Building TCP Servers with Gevent
For those not familiar with the Python web server gateway interface (WSGI), I'll
provide a very abbreviated intro here. In WSGI, your application consists of a
single function that takes
start_response arguments. That
function will be called once for each web request received by your server. The
environ argument is a Python dictionary that holds information about the
request and about the server software itself. The
start_response argument is a
function that your application should call to set status and headers on the HTTP
Once your application has called
start_response, you can either return an
iterable such as a list, or simply begin yielding strings to send back as the
body of the response. A simple "hello world" WSGI application might look like the
def hello_world(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [ '<b>Hello world!</b>\n' ]
If you prefer to use the
yield statement instead, your application might look
more like this:
def hello_world(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) yield '<b>Hello world!</b>\n'
The difference between the two approaches is that if you yield strings a little at a time, you can send a large amount of data back to the client without needing to buffer it all in memory at once if the WSGI server you're using supports it.
Running your WSGI app inside gevent
Gevent actually includes two separate servers capable of calling Python WSGI web
applications, located in the
- gevent.pywsgi has a WSGI server implemented natively in gevent, and it supports streaming responses, HTTP pipelining, and SSL.
- gevent.wsgi has a WSGI server based the HTTP server in
libevent, so it's quite a bit faster than
pywsgi, but it doesn't support streaming responses, HTTP pipelining, nor SSL.
If we want to take our WSGI application above and wrap it in the
the approach is quite similar to a "regular" TCP
StreamServer discussed in the
previous article, except that our
WSGI application serves the purpose of the
handler in the
case. Our entire application, then is the following:
from gevent import pywsgi def hello_world(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) yield '<b>Hello world!</b>\n' server = pywsgi.WSGIServer( ('', 8080), hello_world) server.serve_forever()
Now we can request a page and see that everything's working just fine.
$ curl http://localhost:8080 <b>Hello world!</b>
wsgi versus pywsgi
If we run
curl with the
-v argument, we can see the headers sent back by the
server. We can observe what the WSGI server's doing by paying attention to the
lines starting with
$ curl -v http://localhost:8080 ... < HTTP/1.1 200 OK < Content-Type: text/html < Date: Sat, 04 Aug 2012 19:34:51 GMT < Transfer-Encoding: chunked < * Connection #0 to host localhost left intact * Closing connection #0
There are a couple of things of interest here:
Transfer-Encoding: chunkedindicates that we can stream out data a little bit at a time, without having to buffer everything in server memory.
- The next-to-last line indicates that the connection is left intact. This means that the server supports HTTP pipelining.
If we change our code just a bit to use the
wsgi module instead (leaving
everything else the same), we'll see a different set of headers:
$ curl -v http://localhost:8080 ... < HTTP/1.1 200 OK < Content-Type: text/html < Content-Length: 19 < Server: gevent/0.13 Python/2.7 < Connection: close < Date: Sat, 04 Aug 2012 19:41:49 GMT < * Closing connection #0
The points important to note here include:
Content-Length: 19header is present, and the
Transfer-Encoding: chunkedis missing. This is our clue that the server has buffered the entire response in memory before beginning to respond.
wsgiserver is quite forthcoming about its identity with the
Serverline, going so far as to say which version of gevent and Python are being used. This is a potential security problem, as it allows an attacker to target vulnerabilities to the particular server software used.
Connection: closeheader and the lack of anything indicating that the connection was left intact indicates that HTTP pipelining is not supported.
Given the functionality differences between the servers illustrated by the
headers they return, you might wonder why you'd ever use
wsgi instead of
pywsgi. The answer lies in the performance difference between the two
servers. In my testing,
wsgi could handle 3800 requests per second, while
pywsgi could handle around 2400 requests per second, for a speedup of
1.59. (For those interested, that's using
ab -n 10000 -c 1000
http://localhost:8080/ as a micro-benchmark.)
So if you have an application where the performance is limited by the WSGI
server overhead, you might want to consider using the
wsgi server. In my
experience, however, even fairly trivial Python WSGI applications have
performance that is measured (at best) in the hundreds of requests per
second. Put another way, the difference in overhead introduced by
pywsgi is less than 160 microseconds, and you give up quite a bit of
functionality in the process.
Using Python web frameworks with gevent
In most cases, you'll be using a web framework to build your larger Python web applications. In most if not all cases, those frameworks provide a WSGI application that you can plug directly into gevent. Some of the relevant links are below:
- Django - In particular, note that
projectname/wsgi.pycontains a WSGI application that you can use with gevent's
- Pyramid - Pyramid provides a
make_servercommand which is used in the documentation, but you can as easily use the
- Flask actually has
geventinstructions right on the web page.
- TurboGears - Since TurboGears uses PasteDeploy, you can use
paste-geventto wrap your application.
In most cases, you don't need to make any significant changes to your web applications to make it work correctly with gevent, particularly if you use the gevent monkey-patching module.
Using gevent as a Python WSGI server has one great side-effect: you can spin off greenlets to do background processing whenever you want. Of course, it's also nice to be able to handle thousands of simultaneous connections without seriously taxing your server. One place where this is useful is in web socket or long-polling applications where you need to support lots of simultaneous connections.
There are a couple of modules,
make this type of application work well in a gevent WSGI wrapper. I've mentioned
them in previously, but I'll be going into some more depth in upcoming articles,
so watch out for them.
So what do you think? Do you already use the gevent server to host your Python WSGI applications? Is it something that you'd consider doing? Anyone using gevent for long-lived web clients and not using websockets or socketio? I'd love to hear about it in the comments below!