Working router that can target WSGI middleware or a standard controller+action
This commit is contained in:
199
nova/wsgi.py
199
nova/wsgi.py
@@ -29,6 +29,8 @@ import eventlet.wsgi
|
|||||||
eventlet.patcher.monkey_patch(all=False, socket=True)
|
eventlet.patcher.monkey_patch(all=False, socket=True)
|
||||||
import routes
|
import routes
|
||||||
import routes.middleware
|
import routes.middleware
|
||||||
|
import webob.dec
|
||||||
|
import webob.exc
|
||||||
|
|
||||||
|
|
||||||
logging.getLogger("routes.middleware").addHandler(logging.StreamHandler())
|
logging.getLogger("routes.middleware").addHandler(logging.StreamHandler())
|
||||||
@@ -89,75 +91,80 @@ class Middleware(Application): # pylint: disable-msg=W0223
|
|||||||
|
|
||||||
|
|
||||||
class Debug(Middleware):
|
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."""
|
to get information about the request and response."""
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
@webob.dec.wsgify
|
||||||
for key, value in environ.items():
|
def __call__(self, req):
|
||||||
|
print ("*" * 40) + " REQUEST ENVIRON"
|
||||||
|
for key, value in req.environ.items():
|
||||||
print key, "=", value
|
print key, "=", value
|
||||||
print
|
print
|
||||||
wrapper = debug_start_response(start_response)
|
resp = req.get_response(self.application)
|
||||||
return debug_print_body(self.application(environ, wrapper))
|
|
||||||
|
|
||||||
|
print ("*" * 40) + " RESPONSE HEADERS"
|
||||||
def debug_start_response(start_response):
|
for (key, value) in resp.headers:
|
||||||
"""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 key, "=", value
|
print key, "=", value
|
||||||
print
|
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):
|
@staticmethod
|
||||||
"""Print the body of the response as it is sent back."""
|
def print_generator(app_iter):
|
||||||
|
"""
|
||||||
class Wrapper(object):
|
Iterator that prints the contents of a wrapper string iterator
|
||||||
"""Iterate through all the body parts and print before returning."""
|
when iterated.
|
||||||
|
"""
|
||||||
def __iter__(self):
|
print ("*" * 40) + "BODY"
|
||||||
for part in body:
|
for part in app_iter:
|
||||||
sys.stdout.write(part)
|
sys.stdout.write(part)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
yield part
|
yield part
|
||||||
print
|
print
|
||||||
|
|
||||||
return Wrapper()
|
|
||||||
|
|
||||||
|
class Router(object):
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Router that maps incoming requests to WSGI apps or to standard
|
WSGI middleware that maps incoming requests to targets.
|
||||||
controllers+actions. The response will be a WSGI response; standard
|
|
||||||
controllers+actions will by default have their results serialized
|
Non-WSGI-app targets have their results converted to a WSGI response
|
||||||
to the requested Content Type, or you can subclass and override
|
automatically -- by default, they are serialized according to the Content
|
||||||
_to_webob_response to customize this.
|
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
|
Create a router for the given routes.Mapper `map`.
|
||||||
routes (i.e. specifying controllers and actions), or may route to a
|
|
||||||
WSGI app by instead specifying a wsgi_app=SomeApp() parameter in
|
Each route in `map` must contain either
|
||||||
map.connect().
|
- 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.map = map
|
||||||
|
self.targets = targets
|
||||||
self._router = routes.middleware.RoutesMiddleware(self.__proceed, self.map)
|
self._router = routes.middleware.RoutesMiddleware(self.__proceed, self.map)
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify
|
||||||
@@ -169,23 +176,28 @@ class MichaelRouterMiddleware(object):
|
|||||||
return self._router
|
return self._router
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify
|
||||||
@staticmethod
|
def __proceed(self, req):
|
||||||
def __proceed(req):
|
|
||||||
# Called by self._router after matching the incoming request to a route
|
# Called by self._router after matching the incoming request to a route
|
||||||
# and putting the information into req.environ. Either returns 404, the
|
# and putting the information into req.environ. Either returns 404, the
|
||||||
# routed WSGI app, or _to_webob_response(the action result).
|
# routed WSGI app, or _to_webob_response(the action result).
|
||||||
|
|
||||||
if req.environ['routes.route'] is None:
|
if req.environ['routes.route'] is None:
|
||||||
return webob.exc.HTTPNotFound()
|
return webob.exc.HTTPNotFound()
|
||||||
match = environ['wsgiorg.routing_args'][1]
|
match = req.environ['wsgiorg.routing_args'][1]
|
||||||
if 'wsgi_app' in match:
|
if 'wsgi_app' in match:
|
||||||
return match['wsgi_app']
|
app_name = match['wsgi_app']
|
||||||
|
app = self.targets[app_name]
|
||||||
|
return app
|
||||||
else:
|
else:
|
||||||
kwargs = match.copy()
|
kwargs = match.copy()
|
||||||
controller, action = match['controller'], match['action']
|
controller_name, action = match['controller'], match['action']
|
||||||
delete kwargs['controller']
|
del kwargs['controller']
|
||||||
delete kwargs['action']
|
del kwargs['action']
|
||||||
return _to_webob_response(req, getattr(controller, action)(**kwargs))
|
|
||||||
|
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):
|
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
|
webob.Response before returning up the WSGI chain. By default it
|
||||||
serializes to the requested Content Type.
|
serializes to the requested Content Type.
|
||||||
"""
|
"""
|
||||||
return Serializer(req).serialize(result)
|
return Serializer(req.environ).serialize(result)
|
||||||
|
|
||||||
|
|
||||||
class Serializer(object):
|
class Serializer(object):
|
||||||
"""
|
"""
|
||||||
@@ -206,75 +219,53 @@ class Serializer(object):
|
|||||||
self.environ = environ
|
self.environ = environ
|
||||||
|
|
||||||
def serialize(self, data):
|
def serialize(self, data):
|
||||||
req = webob.Request(environ)
|
req = webob.Request(self.environ)
|
||||||
# TODO(gundlach): temp
|
# TODO(gundlach): temp
|
||||||
if 'applicatio/json' in req.accept):
|
if req.accept and 'application/json' in req.accept:
|
||||||
import json
|
import json
|
||||||
return json.dumps(result)
|
return json.dumps(data)
|
||||||
else:
|
else:
|
||||||
return '<xmlified_yeah_baby>' + repr(data) + '</xmlified_yeah_baby>'
|
return '<xmlified_yeah_baby>' + repr(data) + '</xmlified_yeah_baby>'
|
||||||
|
|
||||||
|
|
||||||
class ApiVersionRouter(MichaelRouterMiddleware):
|
class ApiVersionRouter(Router):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
map = routes.Mapper()
|
map = routes.Mapper()
|
||||||
|
|
||||||
map.connect(None, "/v1.0/{path_info:.*}", wsgi_app=RsApiRouter())
|
map.connect(None, "/v1.0/{path_info:.*}", wsgi_app="rs")
|
||||||
map.connect(None, "/ec2/{path_info:.*}", wsgi_app=Ec2ApiRouter())
|
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):
|
def __init__(self):
|
||||||
map = routes.Mapper()
|
map = routes.Mapper()
|
||||||
|
|
||||||
map.resource("server", "servers", controller=ServerController())
|
map.resource("server", "servers")
|
||||||
map.resource("image", "images", controller=ImageController())
|
map.resource("image", "images")
|
||||||
map.resource("flavor", "flavors", controller=FlavorController())
|
map.resource("flavor", "flavors")
|
||||||
map.resource("sharedipgroup", "sharedipgroups",
|
map.resource("sharedipgroup", "sharedipgroups")
|
||||||
controller=SharedIpGroupController())
|
|
||||||
|
|
||||||
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):
|
class Ec2ApiRouter(object):
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify
|
||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
return 'dummy response'
|
return 'dummy response'
|
||||||
|
# TODO(gundlach): temp
|
||||||
class ServerController(object):
|
class ServerController(object):
|
||||||
def __getattr__(self, key):
|
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
|
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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user