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 @expose def delayed(self, request): def delayedResponse(): request.write("I was delayed 🙁 ") request.finish() reactor.callLater(5, delayedResponse) return server.NOT_DONE_YET class PageTwo(object): @expose def index(self, request): return "Hello From PageTwo index!" class Root(object): @expose def index(self, request): return "Hello From Index!" @expose 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) request.prepath.append(element) if root is None: break 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" break if callable(root) and hasattr(root, "exposed") and root.exposed == True: action = root request.postpath = path[i:] break else: 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 else: 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 site.resource """ dumb = OverrideSite(Root()) reactor.listenTCP(80, dumb ) reactor.run()
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.