Eventlet is Awesome!

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:

import functools
import eventlet

CHUNK_SIZE = 32384

class Router(object):
    def route(self, addr, data):
        raise NotImplemented

def merge(*dicts):
    result = {}
    for d in dicts:
        result.update(d)
    return result

def forward(source, dest):
    while True:
        d = source.recv(CHUNK_SIZE)
        if d == '':
            break
        dest.sendall(d)

def route(router, client, addr):
    blocks = []
    while True:
        block = client.recv(CHUNK_SIZE)
        if block == '':
            raise Exception('Failed to route request: "{0}"'.format("".join(blocks)))

        blocks.append(block)

        route = router.route(addr, "".join(blocks))
        if route is not None:
            print "Forwarded connection from {0} to {1}".format(addr, route)

            server = eventlet.connect(route)
            for block in blocks:
                server.sendall(block)

            eventlet.spawn_n(forward, server, client)
            forward(client, server)
            return


def start(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.

import roulette

class Router(roulette.Router):
    def route(self, addr, data):
        if addr[1] % 2 == 0:
            return ("localhost", 9999)
        return ("localhost", 9998)

roulette.start(Router,
    listen = ("localhost", 80)
)