A friendlier asynchronous twisted web, the ghetto monkey patch way

UPDATE to the UPDATE – A cleaned up and more coherent example of txweb is here
UPDATE – Github repo here

I like twisted, and I like Cherrypy, unfortunately just like my militant atheist friends and my more spiritual friends neither seems to get along with the other.

What to do? MONKEY PATCH + GHETTO HACKING to the rescue!

Note, this is just a mockup of CherryPy’s routing system and not a bridge or interface to CherryPy. There is no CherryPy to be had here, just ghetto py.

from twisted.web import server, resource
from twisted.internet import reactor
def expose(func):
    func.exposed = True
    return func
class PageOne(object):
    def foo(self, request):
        return "Hello From PageOne Foo!"        
    foo.exposed = True
    def delayed(self, request):
        def delayedResponse():
            request.write("I was delayed :( ")
        reactor.callLater(5, delayedResponse)
        return server.NOT_DONE_YET
class PageTwo(object):
    def index(self, request):
        return "Hello From PageTwo index!"
class Root(object):
    def index(self, request):
        return "Hello From Index!"
    def __default__(self, request):
        return "I Caught %s " % request.path
    pageone = PageOne()
    pagetwo = PageTwo()
class OneTimeResource(resource.Resource):
        Monkey patch to avoid rewriting more of twisted's lower web
        layer which does a fantastic job dealing with the minute details
        of receiving and sending HTTP traffic.
        func is a callable and exposed property in the Root OO tree
    def __init__(self, func):
        self.func = func
    def render(self, request):
        #Here would be a fantastic place for a pre-filter
        return self.func(request)
        #ditto here for a post filter
class OverrideSite(server.Site):
        A monkey patch that short circuits the normal
        resource resolution logic @ the getResourceFor point
    def checkAction(self, controller, name):
            On success, returns a bound method from the provided controller instance
            else it return None
        action = None
        if hasattr(controller, name):
                action = getattr(controller, name)
                if not callable(action) or not hasattr(action, "exposed"):
                    action = None
        return action
    def routeRequest(self, request):
        action = None
        response = None
        root = parent = self.resource
        defaultAction = self.checkAction(root, "__default__")
        path = request.path.strip("/").split("/")
        for i in range(len(path)):
            element = path[i]
            parent = root
            root = getattr(root, element, None)
            if root is None:                
            if self.checkAction(root, "__default__"):
                #Check for a catchall default action
                defaultAction = self.checkAction(root, "__default__")
            if element.startswith("_"):
                #500 simplistic security check
                action = lambda request: "500 URI segments cannot start with an underscore"
            if callable(root) and hasattr(root, "exposed") and root.exposed == True:
                action = root
                request.postpath = path[i:] 
            if action is None:
                if root is not None and self.checkAction(root, "index"):
                    action = self.checkAction(root, "index")
        #action = OneTimeResource(action) if action is not None else OneTimeResource(lambda request:"500 Routing error :(")
        if action is None:
            if defaultAction:
                action = defaultAction
                action = lambda request:"404 :("
        return OneTimeResource(action)         
    def getResourceFor(self, request):
        return self.routeRequest(request)
    Twisted thankfully doesn't do any type checking, so a
    dumb OO graph is A-Okay here.  It will be assigned to
dumb = OverrideSite(Root())
reactor.listenTCP(80, dumb )

Slapped this together in about 30 minutes… so there is a HIGH probability that it is almost entirely edge cased! Still it does work ( for me ) and it doesn’t hijack too much of twisted’s core, so it could be viable with a lot of unit-testing love, some additional sanity checking logics, and maybe some well thought out refactoring.