Working router that can target WSGI middleware or a standard controller+action
This commit is contained in:
		
							
								
								
									
										211
									
								
								nova/wsgi.py
									
									
									
									
									
								
							
							
						
						
									
										211
									
								
								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))
 | 
			
		||||
        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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user