`

Glance源码架构探秘(三)

阅读更多

      上一篇分析了OpenStack中如何用eventlet库创建一个“绿化”的协程式web server,以及WSGI相关概念,本篇将具体讲解WSGI程序内部的框架。

      Glance及其他OpenStack的组件和大多数的web service一样,也是实现了MVC的框架,这个框架称之为RackSpace框架。包括消息路由,控制器Controller、action等等。

      这个框架并非完全自己实现,而是借用了许多开源的组件,比如消息路由就用到了Routes这个组件。Routes是借鉴了ruby on rails的某些思想后在Python上的一个实现,用于路由URL消息至对应的控制器和动作action,也可以用来生成URLs。

# Setup a mapper 
from routes import Mapper 
map = Mapper() 
map.connect(None, "/error/{action}/{id}, controller="error") 
map.connect("home", "/", controller="main", action="index") 
# Match a URL, returns a dict or None 
if no match 
    result = map.match('/error/myapp/4') 
# result == {'controller': 'main', 'action': 'myapp', 'id': '4'}

 导入routes,创建一个Mapper实例。之后,我们就可以用Mapper.connect()绑定路由。最后用Mapper.match()方法可以将给定的URL转换为路由信息,包括controller,action或其他用户自定义的路由类型。

      正式讲解Glance的代码之前,先介绍其WSGI中用到的另外一个开源组件webob。使用webob可以大幅简化编写框架的复杂度和代码量,webob用来包装WSGI应用程序的请求以及响应的类库。

from webob import Request 
environ = {'wsgi.url_scheme': 'http', ...} 
req = Request(environ)
req = Request.blank('/article id=1')
from pprint import pprint
pprint(req.environ) {
                     ' HTTP_HOST': 'localhost:80', 
                     'PATH_INFO': '/article', 
                     'QUERY_STRING': 'id=1', 
                     'REQUEST_METHOD': 'GET', 
                     'SCRIPT_NAME': '', 
                     'SERVER_NAME': 'localhost', 
                     'SERVER_PORT': '80', 
                     ' SERVER_PROTOCOL': 'HTTP/1.0', 
                     'wsgi.errors': <open file '<stderr$amp;>apos;$, mode 'w' at ...>, 
                     'wsgi.input': <...IO... object at ...>, 
                     'wsgi.multiprocess': False, 
                     'wsgi.multithread': False, 
                     'wsgi.run_once': False, 
                     'wsgi.url_scheme': 'http', 
                     'wsgi.version': (1, 0)}       

 上面的示例程序就简单说明了如何用webob的Request生成一个request请求和解析一个request请求的环境信息。然而,webob的功能不止这些,还有一个功能是将一个普通的函数function包装为一个WSGI函数,并符合WSGI函数的规范(实现规范的接口,evn,start_response)。

@webob.dec.wsgify
def myfunc(req): 
   return webob.Response('hey there')

 如上示例,就将myfunc包装为一个可被webserver调用的wsgi函数。如果我们不用这个包装,就要非常繁琐地写为:

def myfunc(req, start_response): 
    start_response('200 OK', header)
    return ['hey there']

 另外,用webob.exc通过生成Python异常的方式,生成HTTP的一些错误响应,如404,500等。

       下面先看route.py这个文件。上一篇文章提到,启动http server中填入的WSGI function是从glance-api-paste.ini中读取的,这个app正是glance.api.v2.router.API.factory。

class API(wsgi.Router): 
    """WSGI router for Glance v2 API requests.""" 
    def __init__(self, mapper): 
        custom_image_properties = images.load_custom_properties() 
        schemas_resource = schemas.create_resource(custom_image_properties) 
        mapper.connect('/schemas/image', controller=schemas_resource, action='image', conditions={'method': ['GET']})
        mapper.connect('/schemas/images', controller=schemas_resource, action='images', conditions={'method': ['GET']}) 
        images_resource = images.create_resource(custom_image_properties) 
        mapper.connect('/images', controller=images_resource, action='index', conditions={'method': ['GET']})
        mapper.connect('/images', controller=images_resource, action='create', conditions={'method': ['POST']}) 
        mapper.connect('/images/{image_id}', controller=images_resource, action='update', conditions={'method': ['PATCH']}) 
        mapper.connect('/images/{image_id}', controller=images_resource, action='show', conditions={'method': ['GET']}) 
        mapper.connect('/images/{image_id}', controller=images_resource, action='delete', conditions={'method': ['DELETE']}) 
        super(API, self).__init__(mapper)

 之前介绍过了routes的mapper类,这里的路由分配设置就一目了然了。因为使用的是RESTful接口,路由分配的时候还可以指定method。API这个类的基类是wsgi.Router,刚刚WSGI也是调用的其基类的工厂方法router.factory。这样我们继续介绍wsgi.py的后半部分。

class Router(object): 
    """ WSGI middleware that maps incoming requests to WSGI apps. """ 
    def __init__(self, mapper): 
        """ Create a router for the given routes.Mapper. Each route in `mapper` must specify a 'controller', which is a WSGI app to call. 
        You'll probably want to specify an 'action' as well and have your controller be a wsgi.Controller, who will route the request to the action method. Examples: 
        mapper = routes.Mapper( sc = ServerController() # Explicit mapping of one route to a controller+action mapper.connect(None, "/svrlist", controller=sc, action="list") 
        # Actions are all implicitly defined mapper.resource("server", "servers", controller=sc) 
        # Pointing to an arbitrary WSGI app. You can specify the 
        # {path_info:.*} parameter so the target app can be handed just that 
        # section of the URL. mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp()) """ 
        self.map = mapper 
        self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map) 
        @classmethod 
        def factory(cls, global_conf, **local_conf): 
            return cls(routes.Mapper()) 
        @webob.dec.wsgify 
        def __call__(self, req): 
            """ Route the incoming request to a controller based on self.map. If no match, return a 404. """
             return self._router 
         @staticmethod @webob.dec.wsgify 
         def _dispatch(req): 
             """ Called by self._router after matching the incoming request to a route and putting the information into req.environ. 
             Either returns 404 or the routed WSGI app's response. """ 
             match = req.environ['wsgiorg.routing_args'][1] 
             if not match: 
                 return webob.exc.HTTPNotFound() 
             app = match['controller'] 
             return app       

 这个基类的工厂方法被包装为类方法,实际就会创建刚才的API子类。此类的可调用对象方法__call__被包装成了wsgi,所以这个类的对象可以作为WSGI函数进行调用。routes.middleware是routes组件中另外一个重要的模块,他可以从上层wsgi程序中接收request请求的URL,并自动调用map.match()方法,将URL进行消息路由,并返回路由的结果。路由结果将会被存入request请求的环境变量['wsgiorg.routing_args']中。最后会调用其第一个参数给出的函数接口,继续下一级的WSGI调用,代码中就是self._dispath。

      这层的dispatch分发会从路由结果中找到是哪个controller,然后从router.py中查找并创建controller对应的resource,分析其创建过程,实质上最后还是会在wsgi.py中创建一个resource基类,所以我们继续看wsgi.py的最后一部分。

class Resource(object): 
    """ WSGI app that handles (de)serialization and controller dispatch. Reads routing information supplied by RoutesMiddleware and calls the requested action method 
    upon its deserializer, controller, and serializer. Those three objects may implement any of the basic controller action methods 
    (create, update, show, index, delete) along with any that may be specified in the api router. A 'default' method may also be implemented to be used in place 
    of any non-implemented actions. Deserializer methods must accept a request argument and return a dictionary. Controller methods must accept a request argument. 
    Additionally, they must also accept keyword arguments that represent the keys returned by the Deserializer. They may raise a webob.exc exception or return a dict, 
    which will be serialized by requested content type. """ 
    def __init__(self, controller, deserializer=None, serializer=None): 
        """ :param controller: object that implement methods created by routes lib :param deserializer: object that supports webob request deserializatio through
         controller-like actions :param serializer: object that supports webob response serialization through controller-like actions """ 
        self.controller = controller 
        self.serializer = serializer or JSONResponseSerializer() 
        self.deserializer = deserializer or JSONRequestDeserializer() 
        @webob.dec.wsgify(RequestClass=Request) 
        def __call__(self, request): 
            """WSGI method that controls (de)serialization and method dispatch.""" 
            action_args = self.get_action_args(request.environ) 
            action = action_args.pop('action', None) 
            deserialized_request = self.dispatch(self.deserializer, action, request) 
            action_args.update(deserialized_request) 
            action_result = self.dispatch(self.controller, action, request, **action_args) 
            try: 
                response = webob.Response(request=request) 
                self.dispatch(self.serializer, action, response, action_result) 
                return response 
            # return unserializable result (typically a webob exc) 
            except Exception: 
                return action_result 
            def dispatch(self, obj, action, *args, **kwargs): 
                """Find action-specific method on self and call it.""" 
                try: 
                    method = getattr(obj, action) 
                except AttributeError: 
                    method = getattr(obj, 'default') 
                    return method(*args, **kwargs) 
                def get_action_args(self, request_environment): 
                    """Parse dictionary created by routes library."""
                     try: 
                         args = request_environment['wsgiorg.routing_args'][1].copy() 
                     except Exception: 
                         return {} 
                     try: 
                         del args['controller'] 
                     except KeyError: 
                         pass 
                     try: 
                         del args['format'] 
                     except KeyError 
                     pass 
                 return args

 Resoutce这个类可以对请求解串行化,或串行化响应信息,并可以分发controller控制器action的方法。这个类的初始化方法会给对象添加三个属性,controller、serializer、deserializer,分别为控制器,串行化,逆串行化。对象函数__call__也被装饰为WSGI函数,接受上一级WSGI函数的请求,并将上一级Routes后的路由信息(controller=?action=?)通过dispatch方法指定的action分发到controller控制器类所对应的方法,在源码中用到了getattr,动态的获得对象的绑定方法。

      通过上述一系列步骤,就将glance命令请求消息经过路由、分发等步骤送到了控制器类所对应的action方法中,让glance得以进一步地执行客户发来的请求命令。

前面通过三章的篇幅,为大家讲解了glance/common/wsgi.py这个文件,主要实现了一个web框架的大部分功能。因为OpenStack的组件都是基于同一套rackspace框架的,这对于我们今后学习Nova,Cinder等其他OpenStack都是颇有益处的。所以今后如有Nova等组件的源码探秘,此部分也不再会单独分析,将把经历多投入到其他方面。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics