October 8th, 2007

Libevent has pure http API

It's now long time since my last post.

This surely does not means that I'm stopping FAPWS, at contrary I'm more and more convinced that one of the beauty of Python reside in his gluing capabilities.
This observation is probably not new to lot of people, but for my personal case it's better now than never ;-), no ?

After that small philosophical intro, please go back to our main interest: integrate libevent within the python world.
What's great with libevent is that since several release, they have introduced several HTTP low level APIs: send_reply, send_error, ...
I've tried to use them thanks to pyrex, ctype and swig.

With every of those tools I'm facing issues, problems, ... to get a webserver working correctly.
Because I want FAPWS to be light, efficient and stable, I don't like the idea to add layers on which I don't have too much control. Hoooooo, I'm already hearing some voices ;-). The point is not linked with those tools, but more to my competencies to be able to have them working correctly. Indeed, when you are new to C (which is my case) and try to understand how to use swig, trust me, this is not piece of cake.
Thus I'll use the simple, low level Python API for this project. This will be a way for me to learn C and C API of Python.

Will it be easy to learn C from Python ?

We will see.
Any how, any helps are welcome ;-).

Here after, you can see a minimalist webserver serving the famous "Hello World!":
import ctypes
import sys


def root_handler(req, *arg):
    #print "got request:", req
    #Build a buffer and put page content into it
    libevent.evbuffer_add_printf.argtypes=[ctypes.c_void_p, ctypes.c_char_p]
    libevent.evbuffer_add_printf(buf,"Hello world!\n\n")
    #send the buffer with a return code of 200
    libevent.evhttp_send_reply.argtypes=[ctypes.c_void_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p]
    libevent.evhttp_send_reply(req, 200, "OK", buf)
    return 1

def main():
    #We just initialise and give the server address and port
    libevent.evhttp_start.argtypes=[ctypes.c_char_p, ctypes.c_short]
    #we lik the call to root_handler at a request of "/"
    FUNC=ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p)
    libevent.evhttp_set_cb.argtypes=[ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p]
    libevent.evhttp_set_cb(http,"/",FUNC(root_handler), None)
    #we loop and wait events ;-)
    #we close the loop
    print "end loop main" 

if __name__=="__main__":

Note: I don't know exsactly why, but the server does not stop when you hit "-c". You must kill it ;-(.

Amazingly, I'm able to get 1700#/sec on my 1.2Ghz Athlon.


[myhost ~]$ ab -n2000 -c10
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking (be patient)

Server Software:
Server Hostname:
Server Port:            8080

Document Path:          /
Document Length:        14 bytes

Concurrency Level:      10
Time taken for tests:   1.170227 seconds
Complete requests:      2000
Failed requests:        0
Write errors:           0
Total transferred:      196000 bytes
HTML transferred:       28000 bytes
Requests per second:    1709.07 [#/sec] (mean)
Time per request:       5.851 [ms] (mean)
Time per request:       0.585 [ms] (mean, across all concurrent requests)
Transfer rate:          163.22 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       3
Processing:     2    5   2.5      4      15
Waiting:        1    5   2.6      4      15
Total:          2    5   2.5      4      15

Percentage of the requests served within a certain time (ms)
  50%      4
  66%      4
  75%      7
  80%      7
  90%      9
  95%     11
  98%     13
  99%     14
 100%     15 (longest request)
[myhost ~]$