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
@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.