openstack glance-api-paster

来源:互联网 发布:海马模拟器 for mac 编辑:程序博客网 时间:2024/05/18 09:42

OpenStack Glance 之paste

Python paste 是WSGI (web server gateway interface)的一个工具库,Openstack的每个项目基本都用到了该库,本文以Glance在paste上的使用为例来介绍paste。WSGI是web服务与应用之间交互的一种规范,它定义了应用、服务、中间件的概念。
分析过程中用到的glance的配置文件glanc-api-paste.ini,其中内容较多,主要分析如下配置:

[pipeline:glance-api-keystone]pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken context  rootapp[composite:rootapp]paste.composite_factory = glance.api:root_app_factory/: apiversions/v1: apiv1app/v2: apiv2app[filter:authtoken]paste.filter_factory = keystonemiddleware.auth_token:filter_factorydelay_auth_decision = true

glance-api 服务的入口函数:

 

def main():   。。。        server = wsgi.Server(initialize_glance_store=True)        #加载glance-api的app        server.start(config.load_paste_app('glance-api'), default_port=9292)        server.wait()    except KNOWN_EXCEPTIONS as e:        fail(e)if __name__ == '__main__':    main()
上面这段代码是glance-api服务端启动的部分代码,服务监听在9292上,app是由config.load_paste_app(‘glance-api’)加载。详细过程如下:

 

...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。

...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。



api 名称经过修改:



...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


paste.deploy.loadapp(“config:/etc/glance/glance-api-paste.ini”, glance-api-keystone)
调用栈:

paste.deploy.loadapp(uri, name=None, **kw)    >paste.deploy.loadobj(APP, uri, name=name, **kw)
paste.deploy.loadobj(object_type, uri, name=None, relative_to=None, global_conf=None)def loadobj(object_type, uri, name=None, relative_to=None,            global_conf=None):            #load context     context = loadcontext(        object_type, uri, name=name, relative_to=relative_to,        global_conf=global_conf)    return context.create()

object_type共有六种:APP(_App) FILTER(_Filter) SERVER(_Server) PIPELINE(_Pipeline) FILTER_APP(_FilterApp) FILTER_WITH(_FilterWith),本文会涉及到APP、FILTER、PIPELINE。

...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


#object_type=APP, uri=config:/etc/glance/glance-api-paste.ini name=glance-api-keystonedef loadcontext(object_type, uri, name=None, relative_to=None,                global_conf=None):    if '#' in uri:        if name is None:            uri, name = uri.split('#', 1)        else:            # @@: Ignore fragment or error?            uri = uri.split('#', 1)[0]    if name is None:        name = 'main'    if ':' not in uri:        raise LookupError("URI has no scheme: %r" % uri)        #scheme=config path=/etc/glance/glance-api-paste.ini    scheme, path = uri.split(':', 1)    scheme = scheme.lower()    if scheme not in _loaders:        raise LookupError(            "URI scheme not known: %r (from %s)"#_loaders={'config': _loadconfig, 'egg': _loadegg, 'call': _loadfunc}#_loader[scheme] == _loadconfig    return _loaders[scheme](        object_type,        uri, path, name=name, relative_to=relative_to,        global_conf=global_conf)

字典 _loaders={‘config’: _loadconfig, ‘egg’: _loadegg, ‘call’: _loadfunc}由下面部分代码片段可知。

..._loaders['config'] = _loadconfig_loaders['egg'] = _loadegg_loaders['call'] = _loadfunc
...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


_loadconfig(…)详细加载过程

#object_type=APP uri=config:/etc/glance/glance-api-paste.ini path=/etc/glance/glance-api-paste.inidef _loadconfig(object_type, uri, path, name, relative_to,                global_conf):    ...    loader = ConfigLoader(path)   ...    return loader.get_context(object_type, name, global_conf)

ConfigLoader.get_context(…)主要过程:

#object_type=APP name=glance-api-keystonedef get_context(self, object_type, name=None, global_conf=None):
...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


  ...       #1.获取glance-api-keystone在配置文件中对应的section:pipeline:glance-api-keystone        section = self.find_config_section(            object_type, name=name)        ...        local_conf = {}       ...       #2.解析section pipeline:glance-api-keystone下的配置项:并保持到local_conf字典中。        for option in self.parser.options(section):            if option.startswith('set '):                name = option[4:].strip()                global_additions[name] = global_conf[name] = (                    self.parser.get(section, option))            elif option.startswith('get '):                name = option[4:].strip()                get_from_globals[name] = self.parser.get(section, option)            else:                if option in defaults:                    # @@: It's a global option (?), so skip it                    continue                local_conf[option] = self.parser.get(section, option)        ...        if section.startswith('filter-app:'):            context = self._filter_app_context(                object_type, section, name=name,                global_conf=global_conf, local_conf=local_conf,                global_additions=global_additions)                #3.为pipeline创建LoaderContext对象。        elif section.startswith('pipeline:'):            context = self._pipeline_app_context(                object_type, section, name=name,                global_conf=global_conf, local_conf=local_conf,                global_additions=global_additions)        elif 'use' in local_conf:            context = self._context_from_use(                object_type, local_conf, global_conf, global_additions,                section)        else:            context = self._context_from_explicit(                object_type, local_conf, global_conf, global_additions,                section)    ...    #4.返回pipeline LoaderContext对象。    return context
...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


 ...       #1.获取pipeline        pipeline = local_conf.pop('pipeline').split()        ...        context = LoaderContext(None, PIPELINE, None, global_conf,                                local_conf, self)                                #2为app创建LoaderContext对象        context.app_context = self.get_context(            APP, pipeline[-1], global_conf)            #3.为filter创建LoaderContext对象        context.filter_contexts = [            self.get_context(FILTER, name, global_conf)            for name in pipeline[:-1]]        return context

从上面的源代码中可以看出,主要做了,key pipeline的value,创建PIPELINE的context对象,创建APP的context对象和FILTER对象。

...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


FILTER Context对象的创建,主要是对实例对象的内部属性的赋值操作:

def __init__(self, obj, object_type, protocol,                 global_conf, local_conf, loader,                 distribution=None, entry_point_name=None):        self.object = obj         self.object_type = object_type        self.protocol = protocol        #assert protocol in _flatten(object_type.egg_protocols), (        #    "Bad protocol %r; should be one of %s"        #    % (protocol, ', '.join(map(repr, _flatten(object_type.egg_protocols)))))        self.global_conf = global_conf        self.local_conf = local_conf        self.loader = loader        self.distribution = distribution        self.entry_point_name = entry_point_name

APP Context对象的创建:

        context.app_context = self.get_context(            APP, pipeline[-1], global_conf)

这里又调用了get_context(…)方法,该方法前文已经介绍,这里不再赘述。该方法根据传入参数的不同,相应的也会走不同的逻辑,APP Context的get_context过程主要如下:
1)寻找app对应的section

        section = self.find_config_section(            object_type, name=name)

2)把app section下的配置项保存到local_conf字典中

        for option in self.parser.options(section):            if option.startswith('set '):                name = option[4:].strip()                global_additions[name] = global_conf[name] = (                    self.parser.get(section, option))            elif option.startswith('get '):                name = option[4:].strip()                get_from_globals[name] = self.parser.get(section, option)            else:                if option in defaults:                    # @@: It's a global option (?), so skip it                    continue                local_conf[option] = self.parser.get(section, option)

3)调用_context_from_explicit(…)方法创建context对象。

            context = self._context_from_explicit(                object_type, local_conf, global_conf, global_additions,                section)

_context_from_explicit(…)的源代码如下:

    def _context_from_explicit(self, object_type, local_conf, global_conf,                               global_addition, section):        possible = []        #查找local_conf中被支持的协议,找到后,把该协议与该协议所对应的value构建成一个元组放入possible列表中        for protocol_options in object_type.egg_protocols:            for protocol in protocol_options:                if protocol in local_conf:                    possible.append((protocol, local_conf[protocol]))                    break                    #检查possible变量        if len(possible) > 1:            raise LookupError(                "Multiple protocols given in section %r: %s"                % (section, possible))        if not possible:            raise LookupError(                "No loader given in section %r" % section)        found_protocol, found_expr = possible[0]        del local_conf[found_protocol]        #导入协议说对应的value,以为value是python的一个模块、方法或对象,所以可以之间导入。        value = import_string(found_expr)        #实例化context对象        context = LoaderContext(            value, object_type, found_protocol,            global_conf, local_conf, self)        return context

该函数的主要逻辑是:
1)查找local_conf中被支持的协议,找到后,把该协议与该协议所对应的value构建成一个元组放入possible列表中

...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。

 

...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


该函数的主要逻辑是:
1)查找local_conf中被支持的协议,找到后,把该协议与该协议所对应的value构建成一个元组放入possible列表中
2)对possible做检查。
3)导入被支持协议对应的value,该value是python支持的类型,本文是glance.api:root_app_factory、
keystonemiddleware.auth_token:filter_factory,即是factory方法。
4)实例化context对象,LoaderContext对象的实例化上文有介绍,不再赘述。
FILTER Context的创建与APP Context的创建过程类似,区别是filter模块有多个需要一个循环,源代码如下:

        context.filter_contexts = [            self.get_context(FILTER, name, global_conf)            for name in pipeline[:-1]]

获取context的具体过程,与app context是相似的,这里就不再赘述。

...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


获取context的具体过程,与app context是相似的,这里就不再赘述。
从上文的loadobj(…)的方法可以知道context获取之后就开始调用context.create(…)方法:
context类型是LoaderContext,该类的create的方法如下:

    def create(self):        return self.object_type.invoke(self)

有源码可知,create方法是调用了LoaderContext属性object_type的invoke(…)方法。
有上文可知object_type的类型有APP(_App) FILTER(_Filter) SERVER(_Server) PIPELINE(_Pipeline) FILTER_APP(_FilterApp) FILTER_WITH(_FilterWith)这六种。这里主要会分析PIPELINE、APP、FILTER。

PIPELIEN.invoke(…)

    def invoke(self, context):    #创建app        app = context.app_context.create()        #创建filter        filters = [c.create() for c in context.filter_contexts]        #将filters列表反转        filters.reverse()        #filter封装app        for filter in filters:            app = filter(app)        return app

由上面的源码可知:
1)PIPELINE的invoke方法先调用了APP 的create方法,并把返回值赋值给app。
2)常见filter实例,并将filter顺序反转。
3)通过filter来封装app,并返回。

APP.invoke(…)

    def invoke(self, context):        if context.protocol in ('paste.composit_factory',                                'paste.composite_factory'):            return fix_call(context.object,                            context.loader, context.global_conf,                            **context.local_conf)        elif context.protocol == 'paste.app_factory':            return fix_call(context.object, context.global_conf, **context.local_conf)        else:            assert 0, "Protocol %r unknown" % context.protocol

FILTER.invoke(…)

    def invoke(self, context):        if context.protocol == 'paste.filter_factory':            return fix_call(context.object,                            context.global_conf, **context.local_conf)        elif context.protocol == 'paste.filter_app_factory':            def filter_wrapper(wsgi_app):                # This should be an object, so it has a nicer __repr__                return fix_call(context.object,                                wsgi_app, context.global_conf,                                **context.local_conf)            return filter_wrapper        else:            assert 0, "Protocol %r unknown" % context.protocol

有源码可知APP和FILTER的invoke方法都调用了fix_call(…)方法。该方法位于/usr/lib/python2.7/site-packages/paste/deploy/util.py 源码如下:

def fix_call(callable, *args, **kw):    """    Call ``callable(*args, **kw)`` fixing any type errors that come out.    """    try:        val = callable(*args, **kw)    except TypeError:        exc_info = fix_type_error(None, callable, args, kw)        reraise(*exc_info)    return val

有源码可知,fix_call是调用了之前导入的factory方法。以keystonemiddleware.auth_token:filter_factory为例,来分析filter:

def filter_factory(global_conf, **local_conf):    """Returns a WSGI filter app for use with paste.deploy."""    conf = global_conf.copy()    conf.update(local_conf)    def auth_filter(app):        return AuthProtocol(app, conf)    return auth_filter

从上文源码可知,filter_factory复制了一份global_conf的值然后返回一个内部函数实例auth_filter。如果调用该返回的实例,者就会使用参入的参数app,和conf为参数创建一个AuthProtocol实例。
代码分析到这里,我想大家都该清楚了,paste的loadapp的基本过程,即filter列表中的filter,都以倒序的方式对最后一个app进行封装。



...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


Pipeline模型及app的加载原型
通过上面源代码的分析,建立以下模型:

 [pipeline:xxx] pipeline = filter0 filter2 ... filtern app [app:yyy]  paste.app_factory = aaa.bbb.ccc:app_factoryapp = loadapp("config:$PATH/zzz.ini", "xxx") ->filter0(filter2(...filtern(app)))

从app的加载过程可以看出在pipeline链上,前面的filter对邻近后面的filter进行封装,最后一个filter对app进行封装。
那么其具体是这么封装的呢?这里以keystonemiddleware.auth_token:filter_factory 对app的封装为例来介绍其封装过程,从上面的源码分析,和app的加载原型课可以看出,filter_factory返回一个auth_filter可执行函数,然后auth_filter被调用,使用app和conf作为参数,创建app被封装后的对象AuthProtocol对象,所以封装就是使用app作为参数来实例化AuthProtocol filter app实例对象。
AuthProtocol实例化过程:

#AuthProtocol(BaseAuthProtocol)    def __init__(self, app, conf):        ...        self._conf = _conf_values_type_convert(conf)        ...        super(AuthProtocol, self).__init__(            app,            log=log,            enforce_token_bind=self._conf_get('enforce_token_bind'))        ...
#BaseAuthProtocol    def __init__(self,                 app,                 log=_LOG,                 enforce_token_bind=_BIND_MODE.PERMISSIVE):        self.log = log        self._app = app        self._enforce_token_bind = enforce_token_bind

由源码可以知道filter封装的过程就是把参数参入的app赋值给当前实例的_app属性。

既然filter 是wsgi app那么它也一定要满足wsgi的app标准,即:返回的可调用对象 filter app必须是app(environ,start_response)满足这样的调用方式。然而在openstack的一些子项目中除了compsite和app外,filter都没有满足这个要求,那他又是为什么呢?仔细看到的同学会看到每个filter的__call__(…)方法都用了装饰器来修饰,如:

    @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest)    def __call__(self, req):        """Handle incoming request."""        response = self.process_request(req)        if response:            return response        response = req.get_response(self._app)        return self.process_response(response)

所以可以断定一定是这个装饰器在其中起了作用。那么这个装饰器做了什么呢,让原本的environ和start_response变成了req。进入装饰器中看一下就一目了然了,如下是装饰器的__init__方法:

#注:RequestClass = webob.Request    def __init__(self, func=None, RequestClass=None,                 args=(), kwargs=None, middleware_wraps=None):        self.func = func        if (RequestClass is not None            and RequestClass is not self.RequestClass):            self.RequestClass = RequestClass        self.args = tuple(args)        if kwargs is None:            kwargs = {}        self.kwargs = kwargs        self.middleware_wraps = middleware_wraps    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)            return resp(environ, start_response)        else:            if self.middleware_wraps:                args = (self.middleware_wraps,) + args            return self.func(req, *args, **kw)

该装饰器是一个类,返回的是一个可执行的对象。同以上代码分析可知,该装饰器是一个带参装饰器,当调用被该装饰器修饰的函数时,就会实例化该装饰器实例,然后再调用该可执行实例,参数为AuthProtocol.call(…),最后再次被调用参数为environ和start_response




...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


模型:webob.dec.wsgify(RequestClass=_request._AuthTokenRequest)(\AuthProtocol.call(…))(req, *args, **kw),由此可以看出实际上environ和start_response是直接传给了装饰器实例,那么装饰器实例对其做了什么呢,有如下代码片段可以知道:

    def __call__(self, req, *args, **kw):...            environ = req            start_response = args[0]            req = self.RequestClass(environ)            req.response = req.ResponseClass()...            resp = self.call_func(req, *args, **self.kwargs)            ...    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)



...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


装饰器实例对其做了什么呢,有如下代码片段可以知道:

    def __call__(self, req, *args, **kw):...            environ = req            start_response = args[0]            req = self.RequestClass(environ)            req.response = req.ResponseClass()...            resp = self.call_func(req, *args, **self.kwargs)            ...    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)

由上面的源码可知,服务器在接受请求后,调用app时,传递的参数environ和start_response没有变,只是把他传递给了装饰器修饰后的相应方法,该方法对参数做了一层处理,把使用environ作为参数,创建RequestClass(Request)实例,args[0]就是start_response。然后在调用filter的__call__(self, req)方法。

该装饰器在webob包中,webob也是与wsgi相关的工具库。其中Request、Response和Exception对象很重要。这里就不展开讲解。



...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


到这里就清楚了,当server接受到http请求之后来,把请求的一些相关信息都封装在environ对象中,再调用app(environ, start_response)来处理请求,start_response是服务器放的hook,webob对app(environ,start_response)做了拦截,转换成app(req)的请求传递方式,经过多个filter app处理后,再由最后一个app来处理请求。
filter app在哪里做的过滤操作
我们知道装饰器在做了参数转换之后,交给了filter app来处理,下面以keystonemiddleware.auth_token:filter_factory filter app的处理过程为例,该filter的app是一个AuthProtocol类型的实例,他的父类中实现了__call__(…)方法。

    @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest)    def __call__(self, req):        """Handle incoming request."""        #处理接受到的请求        response = self.process_request(req)        if response:            return response            #传递到下一个app继续处理        response = req.get_response(self._app)        #处理上一个app处理后的结果        return self.process_response(response)

由源码可知,filter是通过实现process_request和process_response方法来实现对请求和响应的过滤。每个filter因功能的不同,其方法的实现也有所差异,具体过程不再赘述。
request的处理过程如下:

...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。


这里写图片描述


...from paste import deploy...def load_paste_app(app_name, flavor=None, conf_file=None):#更新app_name,主要根据glance-api.conf配置文件中的paste_deploy片段的flavor配置项,更新后app_name为glance-api-{flavor}    app_name += _get_deployment_flavor(flavor)    if not conf_file:        conf_file = _get_deployment_config_file()        ...        app = deploy.loadapp("config:%s" % conf_file, name=app_name)        return app

从上面的部分代码可以看出glance-api的app是使用paste.deploy.loadapp(…)加载的。
由此可以知道paste在glance-api服务中是用来加载app的。

0 0
原创粉丝点击