openstack glance app

openstack glance app apiv2app

glance中有多个app,每个app的具体功能都有所差别,但是实现方式,使用的技术基本都是一样的,本文就以apiv2app为例来分析,其中的实现,及使用到的技术。
通过之前http://blog.csdn.net/litianze99/article/details/52120749的分析可以知道,app的主要实现在factory创建的可执行对象中:

[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory

该factory创建的可执行对象到底是什么呢?下面是glance.api.v2.router:API类的实现部分:

...
from glance.common import wsgi


class API(wsgi.Router):

    """WSGI router for Glance v2 API requests."""

    def __init__(self, mapper):
    #加载自定义json文件 schema-image.json
        custom_image_properties = images.load_custom_properties()
        #创建controller
        reject_method_resource = wsgi.Resource(wsgi.RejectMethodController())
#创建controller
        schemas_resource = schemas.create_resource(custom_image_properties)
        #注册路由规则
        mapper.connect('/schemas/image',
                       controller=schemas_resource,
                       action='image',
                       conditions={'method': ['GET']},
                       body_reject=True)
    ...

由类的注释可以大题知道该类是为请求提供路由功能的。该类没有实现factory函数,但是他的父类glance.common.wsgi.Router实现了该方法。父类的部分代码:

class Router(object): """ WSGI middleware that maps incoming requests to WSGI app ... @classmethod def factory(cls, global_conf, **local_conf): return cls(APIMapper()) 

有factory的类方法可以知道该facotory返回的是一个自身的实例,且实例化的参数是APIMapper,
下面是APIMapper的定义:

class APIMapper(routes.Mapper):
    """ Handle route matching when url is '' because routes.Mapper returns an error in this case. """

    def routematch(self, url=None, environ=None):
        if url is "":
            result = self._match("", environ)
            return result[0], result[1]
         return routes.Mapper.routematch(self, url, environ)

APIMapper继承于routes.Mapper类。由此可以看出该app使用了routes基础库来提供对请求的路由功能,即:将url映射到handler。

下面我们就深入分析它怎么实现路由的:
分两步来分析:

  1. app的初始化 。
  2. app对请求的处理过程。

    app的初始化
    glance.api.v2.router:API.factory创建了API实例,参数为APIMapper实例对象,API类继承了glance.common.wsgi.Router类,APIMapper类继承了routes.Mapper类。
    首先看一下APIMapper类的实例化:
    该类只实现了routematch 方法,初始化过程在routes.Mapper中实现,这里不赘述,在介绍routes时在详细介绍。这里只需要记住APIMapper是routes.Mapper的子类。
    API的实例化:
    在API类的__init__(…)中主要是mapper的初始化工作。
    wsgi.Router类的__init__(…)主要是如下:

        mapper.redirect("", "/")
        self.map = mapper
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                          self.map)

初始化map、_router属性。

app对请求的处理过程:

#类 Router 
    @webob.dec.wsgify
    def __call__(self, req):
        """ Route the incoming request to a controller based on self.map. If no match, return either a 404(Not Found) or 501(Not Implemented). """
        return self._router

#类 webob.dec.wsgify
    def __call__(self, req, *args, **kw):
        """Call this as a WSGI application or with a request"""
        func = self.func
        if func is None:
            if args or kw:
                raise TypeError(
                    "Unbound %s can only be called with the function it "
                    "will wrap" % self.__class__.__name__)
            func = req
            return self.clone(func)
        if isinstance(req, dict):
            if len(args) != 1 or kw:
                raise TypeError(
                    "Calling %r as a WSGI app with the wrong signature")
            environ = req
            start_response = args[0]
            req = self.RequestClass(environ) 
            req.response = req.ResponseClass()
            try:
                args = self.args
                if self.middleware_wraps:
                     args = (self.middleware_wraps,) + args
                resp = self.call_func(req, *args, **self.kwargs)
            except HTTPException as exc:
                resp = exc
            if resp is None:
                ## FIXME: I'm not sure what this should be?
                resp = req.response
            if isinstance(resp, text_type):
                resp = bytes_(resp, req.charset)
            if isinstance(resp, bytes):
                body = resp
                resp = req.response
                resp.write(body)
            if resp is not req.response:
                resp = req.response.merge_cookies(resp)
                #self._router
            return resp(environ, start_response)
        else:
            if self.middleware_wraps:
                args = (self.middleware_wraps,) + args
            return self.func(req, *args, **kw)

    def call_func(self, req, *args, **kwargs):
        """Call the wrapped function; override this in a subclass to change how the function is called."""
        return self.func(req, *args, **kwargs)


    @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, 501, or the routed WSGI app's response. """
        match = req.environ['wsgiorg.routing_args'][1]
        if not match:
            implemented_http_methods = ['GET', 'HEAD', 'POST', 'PUT',
                                        'DELETE', 'PATCH']
            if req.environ['REQUEST_METHOD'] not in implemented_http_methods:
                return webob.exc.HTTPNotImplemented()
            else:
                return webob.exc.HTTPNotFound()
        app = match['controller']
        return app

由上面的源码可知请求经Router实例处理后,继续有self._router可调用实例继续处理。

self._router是类routes.middleware.RoutesMiddleware(self._dispatch, self.map)
类的实例。RoutesMiddleware部分核心代码如下:

#routes.middleware.RoutesMiddleware
class RoutesMiddleware(object):

    def __init__(self, wsgi_app, mapper, use_method_override=True, path_info=True, singleton=True):
        self.app = wsgi_app
        self.mapper = mapper
        self.singleton = singleton
        self.use_method_override = use_method_override
        self.path_info = path_info
        log_debug = self.log_debug = logging.DEBUG >= log.getEffectiveLevel()
        if self.log_debug:
            log.debug("Initialized with method overriding = %s, and path "
                  "info altering = %s", use_method_override, path_info)

    def __call__(self, environ, start_response):
        """Resolves the URL in PATH_INFO, and uses wsgi.routing_args to pass on URL resolver results."""
        old_method = None
        if self.use_method_override:
            req = None

            # In some odd cases, there's no query string
            try:
                qs = environ['QUERY_STRING']
            except KeyError:
                qs = ''
            if '_method' in qs:
                req = Request(environ)
                req.errors = 'ignore'
                if '_method' in req.GET:
                    old_method = environ['REQUEST_METHOD']
                    environ['REQUEST_METHOD'] = req.GET['_method'].upper()
                    if self.log_debug:
                        log.debug("_method found in QUERY_STRING, altering request"
                                " method to %s", environ['REQUEST_METHOD'])
            elif environ['REQUEST_METHOD'] == 'POST' and is_form_post(environ):
                if req is None:
                    req = Request(environ)
                    req.errors = 'ignore'
                if '_method' in req.POST:
                    old_method = environ['REQUEST_METHOD']
                    environ['REQUEST_METHOD'] = req.POST['_method'].upper()
                    if self.log_debug:
                        log.debug("_method found in POST data, altering request "
                                  "method to %s", environ['REQUEST_METHOD'])
        if self.singleton:
            config = request_config()
            config.mapper = self.mapper
            config.environ = environ
            match = config.mapper_dict
            route = config.route
        else:
            results = self.mapper.routematch(environ=environ)
            if results:
                match, route = results[0], results[1]
            else:
                match = route = None

        if old_method:
            environ['REQUEST_METHOD'] = old_method

        if not match:
            match = {}
            if self.log_debug:
                urlinfo = "%s %s" % (environ['REQUEST_METHOD'], environ['PATH_INFO'])
                log.debug("No route matched for %s", urlinfo)
        elif self.log_debug:
            urlinfo = "%s %s" % (environ['REQUEST_METHOD'], environ['PATH_INFO'])
            log.debug("Matched %s", urlinfo)
            log.debug("Route path: '%s', defaults: %s", route.routepath,
                      route.defaults)
            log.debug("Match dict: %s", match)

        url = URLGenerator(self.mapper, environ)
        environ['wsgiorg.routing_args'] = ((url), match)
        environ['routes.route'] = route
        environ['routes.url'] = url

        if route and route.redirect:
            route_name = '_redirect_%s' % id(route)
            location = url(route_name, **match)
            log.debug("Using redirect route, redirect to '%s' with status"
                      "code: %s", location, route.redirect_status)
            start_response(route.redirect_status,
                           [('Content-Type', 'text/plain; charset=utf8'),
                            ('Location', location)])
            return []

        if self.path_info and 'path_info' in match:
            oldpath = environ['PATH_INFO']
            newpath = match.get('path_info') or ''
            environ['PATH_INFO'] = newpath
            if not environ['PATH_INFO'].startswith('/'):
                environ['PATH_INFO'] = '/' + environ['PATH_INFO']
            environ['SCRIPT_NAME'] += re.sub(r'^(.*?)/' + re.escape(newpath) + '$',
                                             r'\1', oldpath)

        response = self.app(environ, start_response)

        # Wrapped in try as in rare cases the attribute will be gone already
        try:
            del self.mapper.environ
        except AttributeError:
            pass
        return response

class Resource(object):

    def __init__(self, controller, deserializer=None, serializer=None):
        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)
        body_reject = strutils.bool_from_string(
            action_args.pop('body_reject', None))

        try:
            if body_reject and self.deserializer.has_body(request):
                msg = _('A body is not expected with this request.')
                raise webob.exc.HTTPBadRequest(explanation=msg)
            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)
            # encode all headers in response to utf-8 to prevent unicode errors
            for name, value in list(response.headers.items()):
                if six.PY2 and isinstance(value, six.text_type):
                    response.headers[name] = encodeutils.safe_encode(value)
            return response
       ...

    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')
          #将不同的请求映射到其对应的controller的method上,并调用处理。
        return method(*args, **kwargs)

由源代码可以知道最后请求传递给传入的参数self._diaptach 即:wsgi_app 处理,之后有交个controller来处理,controller会根据请求的url,action调用相对应的方法来做最终的处理操作。
基本流程如下:
APP()–@webob.dec.wsgi –__call__()–>routes.middleware.RoutesMiddleware()–__call__()–>
APP().dispatch()–@webob.dec.wsgi–>Controller()–@webob.dec.wsgi–__call_()–>action()。
这里用到了routes很重要的库,但本文不会对其的使用,做详细描述。

你可能感兴趣的:(openstack)