summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorMichael Gundlach <michael.gundlach@rackspace.com>2010-08-11 14:46:43 -0400
committerMichael Gundlach <michael.gundlach@rackspace.com>2010-08-11 14:46:43 -0400
commita0fb0fdf1e899488f0717bea6ee2cad58120070b (patch)
tree4038f8668c30c397f486f1cb123e3926caff86f0 /nova
parent1637c33927672a6edc9ad7a994787669ea47f602 (diff)
Working router that can target WSGI middleware or a standard controller+action
Diffstat (limited to 'nova')
-rw-r--r--nova/wsgi.py205
1 files changed, 98 insertions, 107 deletions
diff --git a/nova/wsgi.py b/nova/wsgi.py
index c511a3f06..81890499e 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -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))
-
-
-def debug_start_response(start_response):
- """Wrap the start_response to capture when called."""
+ resp = req.get_response(self.application)
- 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
-def debug_print_body(body):
- """Print the body of the response as it is sent back."""
+ @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
- 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
+class Router(object):
+ """
+ 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, targets):
+ """
+ Create a router for the given routes.Mapper `map`.
- return Wrapper()
+ 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'.
-class ParsedRoutes(Middleware):
- """Processed parsed routes from routes.middleware.RoutesMiddleware
- and call either the controller if found or the default application
- otherwise."""
+ Examples:
+ map = routes.Mapper()
+ targets = { "servers": ServerController(), "blog": BlogWsgiApp() }
- 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)
+ # Explicit mapping of one route to a controller+action
+ map.connect(None, "/serverlist", controller="servers", action="list")
-class MichaelRouterMiddleware(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.
- """
-
- def __init__(self, map):
- """
- 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().
+ # 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")
+
+ targets = { "rs": RsApiRouter(), "ec2": Ec2ApiRouter() }
- super(ApiVersionRouter, self).__init__(self, map)
+ super(ApiVersionRouter, self).__init__(map, targets)
-class RsApiRouter(MichaelRouterMiddleware):
+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