Eventlet is an asynchronous networking library for Python which uses coroutines to allow you to write non-blocking code without needing to perform the normal mental gymnastics that usually go along with asynchronous programming. There are a bunch of async/event-driven networking framworks (eventmachine, node.js, tornado, and twisted to name a few), and their defining characteristic is that they use select/epolll/kqueue/etc. to do I/O asynchronously. Asynchronous I/O is cool because when done correctly it allows your server to handle a much greater number of concurrent clients than the one-OS-thread-per-connection approach. Furthermore, since you're using co-operative rather than preemptive multitasking, you can safely update shared data structures without locks, which makes it a lot easier to write correct code.
Anyway, eventlet is pretty cool. If you like Python and you're interested in async programming, you should check it out. After all, anything that reduces the incidence of Heisenbugs is worth a look ;)
Proxymachine in 55 Lines
Since they're so specialized, playing with this kind of library requires a specific kind of project. Consequently, I decided to put together a "Hello Word!" version of Proxymachine. Proxymachine is a proxy built on eventmachine that lets you configure it's routing logic using Ruby. Don't get me wrong, Proxymachine is awesome and way more production ready than this. That being said, it's still friggin' cool that I could put togther a pale imitation of Proxymachine in less than 100 lines thanks to eventlet:
importfunctoolsimporteventletCHUNK_SIZE=32384classRouter(object):defroute(self,addr,data):raiseNotImplementeddefmerge(*dicts):result={}fordindicts:result.update(d)returnresultdefforward(source,dest):whileTrue:d=source.recv(CHUNK_SIZE)ifd=='':breakdest.sendall(d)defroute(router,client,addr):blocks=[]whileTrue:block=client.recv(CHUNK_SIZE)ifblock=='':raiseException('Failed to route request: "{0}"'.format("".join(blocks)))blocks.append(block)route=router.route(addr,"".join(blocks))ifrouteisnotNone:print"Forwarded connection from {0} to {1}".format(addr,route)server=eventlet.connect(route)forblockinblocks:server.sendall(block)eventlet.spawn_n(forward,server,client)forward(client,server)returndefstart(router,**kwargs):defaults={"listen":('localhost',8080),}config=merge(defaults,kwargs)print"Listening on:",config['listen']eventlet.serve(eventlet.listen(config['listen']),functools.partial(route,router()))
Roulette: A router in ten lines
The logic here is laughable, route inbound connections to either localhost:9998 or localhost:9999 depending on whether the remote port is divisible by two, but the point is that the routing logic could be anything. We're writing Python here. We could look stuff up in a database, or check the phase of the moon or y'know, do something useful.
In my post about Grayvatar, I warned everybody away from actually using it for anything as it was a complete toy. The major reason I cited was that it did not make any attempt to respect cache headers. Enter Grayvatar 2.0, which is slightly less of a toy as it, at least, makes a half-hearted attempt to respect cache headers.
The key addition was httplib2, which maintains it's own cache, so that we're not hitting Gravatar's servers for every single request we receive. The other big change was to copy cache headers from the client to the server and vice-versa. This means that we'll pass a long the cache-control:max-age=300 that we get from Gravatar or the cache-control:max-age=0 we get from the client. All of this adds about 20 lines on top of the existing implementation. You still shouldn't use it for anything, it's still a toy, but at least it's slightly less naive one.
The week before last I got to spend some time working on a prototype as part of a sekrit project at work. In order to give the prototype the air of not-quite-done, I did the whole thing in black and white. This worked great up until I added in Identicons to identify users, which added a bunch of brightly colored triangles and ruined the whole black-and-white aesthetic. I eventually ended up pulling a bunch of Identicons from Gravatar with wget and desaturated them with ImageMagik. It worked, but it was kind of gross.
Teh Codez
It occurred to me that it should be pretty straightforward to build a web app that implemented the Gravatar API and desaturate real Gravatars, so I took a crack at it:
I think it's awesome that, thanks to CherrPy and PIL, I could put together a web app that does all that 30 odd lines (including imports), but it's less awesome that so many of those lines are shuffling data in and out of a StringIO.
The most important change was adding 'log.screen': True to the config. This sends errors to stderr, which eventually makes it's way to /var/log/apache2/error.log which makes it possible to figure out what's going on when things don't go right.
How It Works
Just like Gravatar. The base URL is http://aaron.maenpaa.ca/grayvatar/. You calculate the MD5 hash of your email address, append it to the base URL and then add any query parameters that Gravatar supports. That gets you a URL like this: http://aaron.maenpaa.ca/grayvatar/634329486bd326c395c8b3539c035139?s=256&d=identicon&r=PG
It fetchs the appropriate Gravatar, desaturates and returns it. Keep in mind, this is a toy: It doesn't actually respect the cache headers Gravatar sends and worse, it doesn't even pass them along to the client.
So far it has been pretty interesting and I've was plesently surprised with how much of the material I am familar with, despite the fact that I haven't studied math beyond the courses that are part of the engineering curriculum at my university.
The best part, though, has been the occasions when I find something that I can play with a little bit on my own. A good example is Ford Circles, which with a little time in NodeBox I had my own renderings: