Thursday, December 29, 2011

Web Services in Python (Part 1) - You Only Need 3 Lines Of Python Code

I cringe whenever I see someone meddling with Apache or nginx config files to do something simple these days.

And worse - the usual LAMP stack is terrible at handling anything that's actually asynchronous in nature, like WebSocket or Comet. It can also be a pretty big security hole as well, see Slowloris.

And yet, you have to read lengthy manuals and fiddle with lengthy config files to get anything done with the traditional web servers - instead of getting shit done, like, right now. Seriously, this is 2011. You're doing more for less if you're sticking to the 2000 ways.

Let's try another way.

So, let's say you have a usual Ubuntu, or Debian, or the more trendy Mint machine. Try this in your home directory:
$ sudo apt-get install libevent-2.0 libevent-dev python-dev python-virtualenv
$ virtualenv webservice
$ . webservice/bin/activate
$ pip install gevent gunicorn
Well, congratulations! You've already got a self-contained web server package installed with the above four lines of shell commands! If your Linux machine has libevent and virtualenv installed already, you can even cut out the first line.

So, 3 lines of shell code to get the infrastructure ready.

The Apache guy is.. eh.. still looking through the manual pages for how to set up virtual hosts. We'll get back to him later, after the late-night news, supper, and.. watching grass grow.

Now, for the web server's content, we'll try a Hello World first. It'll be a Python script for now - we'll make it more like a real web server (i.e. serves your .html, .js, .css and runs scripts and frameworks just like your Apache, nginx, etc.) in another post.
$ cd webservice
$ cat >
#!/usr/bin/env python
def application(environ, start_response):
 start_response("200 OK", [("Content-type", "text/plain")])
 return [ "Hello World!" ]
Just in case you're not familiar with UNIX, press Ctrl-D after the last line to save the file.

So, 3 lines of Python code to make the server serve.. something.

Finally.. we start the web server:
$ gunicorn -w8 -k gevent --keep-alive 60 application:application
And.. it's alive! It's running at port 8000 by default. You can see it in http://localhost:8000/

The performance isn't shabby as well, considering the web server part of it (i.e. gevent.pywsgi, which handles the underlying HTTP protocol) is written in 100% Python:

$ ab -c 32 -n 12000 http://localhost:8000/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd,
Licensed to The Apache Software Foundation,

Benchmarking localhost (be patient)
Completed 1200 requests
Completed 2400 requests
Completed 3600 requests
Completed 4800 requests
Completed 6000 requests
Completed 7200 requests
Completed 8400 requests
Completed 9600 requests
Completed 10800 requests
Completed 12000 requests
Finished 12000 requests

Server Software:        gunicorn/0.13.4
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      32
Time taken for tests:   1.182 seconds
Complete requests:      12000
Failed requests:        0
Write errors:           0
Total transferred:      1644000 bytes
HTML transferred:       144000 bytes
Requests per second:    10148.61 [#/sec] (mean)
Time per request:       3.153 [ms] (mean)
Time per request:       0.099 [ms] (mean, across all concurrent requests)
Transfer rate:          1357.77 [Kbytes/sec] received

Connection Times (ms)
        min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       4
Processing:     0    3   1.9      2      22
Waiting:        0    3   1.9      2      22
Total:          1    3   2.0      3      23

Percentage of the requests served within a certain time (ms)
50%      3
66%      3
75%      4
80%      4
90%      5
95%      6
98%      8
99%     11
100%     23 (longest request)
For comparison, Apache 2.2 running on the same machine, serving a static file does 12k requests per second. And you can pretty much forget about playing with Comets in it without a lot of configurations and modifications.

1 comment:

b19a said...

Does setting up gunicorn with gevent based workers solve the whole slowloris attack problem? As far as I know, gunicorn needs a buffering proxy to prevent slowloris attacks.