Working router that can target WSGI middleware or a standard controller+action

This commit is contained in:
Michael Gundlach
2010-08-11 14:46:43 -04:00
parent ec93c83fd0
commit cf2916a95e

View File

@@ -29,6 +29,8 @@ import eventlet.wsgi
eventlet.patcher.monkey_patch(all=False, socket=True)
import routes
import routes.middleware
import webob.dec
import webob.exc
logging.getLogger("routes.middleware").addHandler(logging.StreamHandler())
@@ -89,75 +91,80 @@ class Middleware(Application): # pylint: disable-msg=W0223
class Debug(Middleware):
"""Helper class that can be insertd into any WSGI application chain
"""Helper class that can be inserted into any WSGI application chain
to get information about the request and response."""
def __call__(self, environ, start_response):
for key, value in environ.items():
@webob.dec.wsgify
def __call__(self, req):
print ("*" * 40) + " REQUEST ENVIRON"
for key, value in req.environ.items():
print key, "=", value
print
wrapper = debug_start_response(start_response)
return debug_print_body(self.application(environ, wrapper))
resp = req.get_response(self.application)
def debug_start_response(start_response):
"""Wrap the start_response to capture when called."""
def wrapper(status, headers, exc_info=None):
"""Print out all headers when start_response is called."""
print status
for (key, value) in headers:
print ("*" * 40) + " RESPONSE HEADERS"
for (key, value) in resp.headers:
print key, "=", value
print
start_response(status, headers, exc_info)
return wrapper
resp.app_iter = self.print_generator(resp.app_iter)
return resp
@staticmethod
def print_generator(app_iter):
"""
Iterator that prints the contents of a wrapper string iterator
when iterated.
"""
print ("*" * 40) + "BODY"
for part in app_iter:
sys.stdout.write(part)
sys.stdout.flush()
yield part
print
def debug_print_body(body):
"""Print the body of the response as it is sent back."""
class Wrapper(object):
"""Iterate through all the body parts and print before returning."""
def __iter__(self):
for part in body:
sys.stdout.write(part)
sys.stdout.flush()
yield part
print
return Wrapper()
class ParsedRoutes(Middleware):
"""Processed parsed routes from routes.middleware.RoutesMiddleware
and call either the controller if found or the default application
otherwise."""
def __call__(self, environ, start_response):
if environ['routes.route'] is None:
return self.application(environ, start_response)
app = environ['wsgiorg.routing_args'][1]['controller']
return app(environ, start_response)
class MichaelRouterMiddleware(object):
class Router(object):
"""
Router that maps incoming requests to WSGI apps or to standard
controllers+actions. The response will be a WSGI response; standard
controllers+actions will by default have their results serialized
to the requested Content Type, or you can subclass and override
_to_webob_response to customize this.
WSGI middleware that maps incoming requests to targets.
Non-WSGI-app targets have their results converted to a WSGI response
automatically -- by default, they are serialized according to the Content
Type from the request. This behavior can be changed by overriding
_to_webob_response().
"""
def __init__(self, map):
def __init__(self, map, targets):
"""
Create a router for the given routes.Mapper. It may contain standard
routes (i.e. specifying controllers and actions), or may route to a
WSGI app by instead specifying a wsgi_app=SomeApp() parameter in
map.connect().
Create a router for the given routes.Mapper `map`.
Each route in `map` must contain either
- a 'wsgi_app' string or
- a 'controller' string and an 'action' string.
'wsgi_app' is a key into the `target` dictionary whose value
is a WSGI app. 'controller' is a key into `target' whose value is
a class instance containing the method specified by 'action'.
Examples:
map = routes.Mapper()
targets = { "servers": ServerController(), "blog": BlogWsgiApp() }
# Explicit mapping of one route to a controller+action
map.connect(None, "/serverlist", controller="servers", action="list")
# Controller string is implicitly equal to 2nd param here, and
# actions are all implicitly defined
map.resource("server", "servers")
# Pointing to a WSGI app. You'll need to specify the {path_info:.*}
# parameter so the target app can work with just his section of the
# URL.
map.connect(None, "/v1.0/{path_info:.*}", wsgi_app="blog")
"""
self.map = map
self.targets = targets
self._router = routes.middleware.RoutesMiddleware(self.__proceed, self.map)
@webob.dec.wsgify
@@ -169,23 +176,28 @@ class MichaelRouterMiddleware(object):
return self._router
@webob.dec.wsgify
@staticmethod
def __proceed(req):
def __proceed(self, req):
# Called by self._router after matching the incoming request to a route
# and putting the information into req.environ. Either returns 404, the
# routed WSGI app, or _to_webob_response(the action result).
if req.environ['routes.route'] is None:
return webob.exc.HTTPNotFound()
match = environ['wsgiorg.routing_args'][1]
match = req.environ['wsgiorg.routing_args'][1]
if 'wsgi_app' in match:
return match['wsgi_app']
app_name = match['wsgi_app']
app = self.targets[app_name]
return app
else:
kwargs = match.copy()
controller, action = match['controller'], match['action']
delete kwargs['controller']
delete kwargs['action']
return _to_webob_response(req, getattr(controller, action)(**kwargs))
controller_name, action = match['controller'], match['action']
del kwargs['controller']
del kwargs['action']
controller = self.targets[controller_name]
method = getattr(controller, action)
result = method(**kwargs)
return self._to_webob_response(req, result)
def _to_webob_response(self, req, result):
"""
@@ -194,7 +206,8 @@ class MichaelRouterMiddleware(object):
webob.Response before returning up the WSGI chain. By default it
serializes to the requested Content Type.
"""
return Serializer(req).serialize(result)
return Serializer(req.environ).serialize(result)
class Serializer(object):
"""
@@ -206,75 +219,53 @@ class Serializer(object):
self.environ = environ
def serialize(self, data):
req = webob.Request(environ)
req = webob.Request(self.environ)
# TODO(gundlach): temp
if 'applicatio/json' in req.accept):
if req.accept and 'application/json' in req.accept:
import json
return json.dumps(result)
return json.dumps(data)
else:
return '<xmlified_yeah_baby>' + repr(data) + '</xmlified_yeah_baby>'
class ApiVersionRouter(MichaelRouterMiddleware):
class ApiVersionRouter(Router):
def __init__(self):
map = routes.Mapper()
map.connect(None, "/v1.0/{path_info:.*}", wsgi_app=RsApiRouter())
map.connect(None, "/ec2/{path_info:.*}", wsgi_app=Ec2ApiRouter())
map.connect(None, "/v1.0/{path_info:.*}", wsgi_app="rs")
map.connect(None, "/ec2/{path_info:.*}", wsgi_app="ec2")
super(ApiVersionRouter, self).__init__(self, map)
targets = { "rs": RsApiRouter(), "ec2": Ec2ApiRouter() }
class RsApiRouter(MichaelRouterMiddleware):
super(ApiVersionRouter, self).__init__(map, targets)
class RsApiRouter(Router):
def __init__(self):
map = routes.Mapper()
map.resource("server", "servers", controller=ServerController())
map.resource("image", "images", controller=ImageController())
map.resource("flavor", "flavors", controller=FlavorController())
map.resource("sharedipgroup", "sharedipgroups",
controller=SharedIpGroupController())
map.resource("server", "servers")
map.resource("image", "images")
map.resource("flavor", "flavors")
map.resource("sharedipgroup", "sharedipgroups")
super(RsApiRouter, self).__init__(self, map)
targets = {
'servers': ServerController(),
'images': ImageController(),
'flavors': FlavorController(),
'sharedipgroups': SharedIpGroupController()
}
super(RsApiRouter, self).__init__(map, targets)
# TODO(gundlach): temp
class Ec2ApiRouter(object):
@webob.dec.wsgify
def __call__(self, req):
return 'dummy response'
# TODO(gundlach): temp
class ServerController(object):
def __getattr__(self, key):
return {'dummy': 'dummy response'}
return lambda **args: {key: 'dummy response for %s' % repr(args)}
# TODO(gundlach): temp
ImageController = FlavorController = SharedIpGroupController = ServerController
class Router(Middleware): # pylint: disable-msg=R0921
"""Wrapper to help setup routes.middleware.RoutesMiddleware."""
def __init__(self, application):
self.map = routes.Mapper()
self._build_map()
application = ParsedRoutes(application)
application = routes.middleware.RoutesMiddleware(application, self.map)
super(Router, self).__init__(application)
def __call__(self, environ, start_response):
return self.application(environ, start_response)
def _build_map(self):
"""Method to create new connections for the routing map."""
raise NotImplementedError("You must implement _build_map")
def _connect(self, *args, **kwargs):
"""Wrapper for the map.connect method."""
self.map.connect(*args, **kwargs)
def route_args(application):
"""Decorator to make grabbing routing args more convenient."""
def wrapper(self, req):
"""Call application with req and parsed routing args from."""
return application(self, req, req.environ['wsgiorg.routing_args'][1])
return wrapper