本篇分为如下几个部分java
- paste 是什么
- 怎样使用paste
- paste of neutron
WSGI 是python 中application 和 web server互通的标准。 咱们知道了wsgi 中包括 app, middleware , server并且middleware能够有不少个。wsgi结构的系统最大的好处就是middleware像积木同样,能够灵活的添加组成不一样的功能。python
咱们上一篇文章中,把2个middleware和一个app组合到了一块儿,但咱们采用的写法是在代码中这样写:c++
httpd = make_server('10.79.99.86', 8051, CheckError(AuthToken(check_number)))
把各个模块之间的关系硬编码到了代码中,这很显然不够灵活,假设有一天想从新组织middleware的结构,咱们还须要找到相关的代码去修改代码。最好的办法是经过配置文件来组织各个模块之间的关系,经过修改配置文件来影响系统中各个模块的组合与调用。 Paste 就是解决这个问题的。程序员
paste 是python的一个module,经过paste, 你能够把wsgi的模块写入ini风格的配置文件,灵活部署。web
下面咱们用paste构建一个小系统,进一步了解设计模式
首先,咱们将构建三个wsgi组件, 一个application叫check_number, 该application的功能是check http req中的数字是奇数仍是偶数,若是是奇数返回odd不然返回even。 代码以下:api
class CheckNumber(object): def __call__(self, env, start_response): number = int(env.get('PATH_INFO').split('/')[-1]) response_body = 'even' if number % 2 == 0 else 'odd' status = '200 OK' response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] @classmethod def factory(cls, global_config, **local_config): return cls()
__call__是wsgi application的入口,逻辑很是简单,这里不作解释。服务器
why use factory
看factory函数的逻辑,其实至关于一个构造函数。 下面两行代码其实是同样的效果app
CheckNumber() CheckNumber.factory()
但为何还要多加一个factory呢?两个缘由curl
无论怎样,咱们知道这是个构造函数便可。
接下来,咱们构造一个middleware,名字叫AuthToken。 该middleware将验证http请求header中的token,若是知足要求则放行request继续传递给application,不然返回错误,代码以下:
class AuthToken(object): def __init__(self, app): self.app = app def __call__(self, env, start_response): if env.get('HTTP_TOKEN') == '222': return self.app(env, start_response) else: response_body = 'Auth failed' status = '403 forbidden' response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] @classmethod def factory(cls, global_config, **local_config): def _factory(app): return cls(app) return _factory
Middleware的特性就是能够调用application,同时自身能够像application同样被调用。因此__init__中接受一个app做为参数,而且在__call__中调用这个参数。所以
AuthToken(CheckNumber)
的效果是返回一个可调用的object。 在调用该object的__call__方法时,首先会检查token,若是token符合要求则继续调用CheckNumber。一样要注意的是factory跟以前同样,彻底能够用构造函数替代。
咱们继续构造第三个middleware,叫CheckError。 该middleware 用于检查系统中其它application , middleware返回的错误异常等信息,并转化成用户友好的信息。 逻辑以下:
class CheckError(object): def __init__(self, app): self.app = app def __call__(self, env, start_response): try: return self.app(env, start_response) except Exception as e: response_body = 'Server is maintaining ' status = '503 service unavailable now' response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] @classmethod def factory(cls, global_config, **local_config): def _factory(app): return cls(app) return _factory
CheckError 的构造函数接受一个app做为参数并返回一个callable对象。调用该对象,即运行__call__方法时会用try catch去包裹一个对app的调用。若是调用有异常则进入异常处理返回,不然返回app调用
若是没有paste deploy,咱们须要像下面这样写:
CatchError(AuthToken(CheckNumber(env, callback)))
经过程序来组织各个wsgi组件。这样很是不方便,一旦想添加或者删除甚至更改各个component的关系,须要修改代码。 经过paste deploy,咱们能够利用配置文件,以下:
[DEFAULT] security = high [composite: my_pipeline] use = call:my_module:pipeline_factory auth = check_error auth_token check_number noauth = check_error check_number [app:check_number] paste.app_factory = my_module:CheckNumber.factory [filter:auth_token] paste.filter_factory = my_module:AuthToken.factory [filter:check_error] paste.filter_factory = my_module:CheckError.factory
paste 配置文件采用INI风格。每一个中括号及其下面跟着的一个区域叫作 section 如:
[filter:check_error] paste.filter_factory = my_module:CheckError.factory
这里filter为section type。paste中有不少种类型。app类型表明wsgi application, filter 表明wsgi middleware,server表明server。其它类型咱们后面遇到再说。 check_error 是section的name。下面跟着的是section的变量,paste.filter_factory = my_module:CheckError.factory 告诉paste,能够用my_module模块的CheckError.factory load catch_error filter。 简单的说,这段配置告诉paste 咱们有一个catch_error middleware 以及怎样加载这个middleware。
paste中,每一个section(除了[DEFAULT])表明一个wsgi组件,paste 经过配置文件能够加载该组件,在加载组件的同时能够读取该section下面的配置信息。这里的信息是该section对应的wsgi组件独享的。可是有个section除外,即[DEFAULT],DEFAULT section不是wsgi组件,只是用来存配置。这里的配置是全局的,每一个section均可以访问。
auth_token, check_number其实和这里同样,都至关于声明一个wsgi组件而且告诉paste如何加载。不一样的是check_number是一个app。
咱们重点看一下my_pipeline区域。这里咱们用的是composite类型。 回顾wsgi 会发现咱们有middle/app/server 但就是没有composite。composite不是wsgi原生的类型,它表明多个组件的组合。好比这里是两个pipeline auth和noauth。paste中pipeline也是一种类型,表明串联多个wsgi组件,好比:auth 至关于 CatchError(AuthToken(CheckNumber)) 而 noauth表明CatchError(CheckNumber)。
use = call:my_module:pipeline_factory
这一行表示对应的代码在my_module 模块的pipline_factory里。 表示从这行代码加载composite组件。看一下代码的详细信息:
def pipeline_factory(loader, global_config, **local_config): if global_config.get('security') == 'high': pipeline = local_config.get('auth') else: pipeline = local_config.get('noauth') # space-separated list of filter and app names: pipeline = pipeline.split() filters = [loader.get_filter(n) for n in pipeline[:-1]] app = loader.get_app(pipeline[-1]) filters.reverse() # apply in reverse order! for filter in filters: app = filter(app) return app
由于配置文件中指定该区域是composite,因此该函数接受3个参数loader, global_config 和 local_config。这是paste语法规则指定的。
loader用于加载app,而global_config是配置文件中全局区的配置{security:high}。local_config是 {auth:"catch_error auth_token check_number"}和{noauth:"catch_error auth_token check_number"}。
if global_config.get('security') == 'high': pipeline = local_config.get('auth') else: pipeline = local_config.get('noauth')
这段的逻辑是,若是全局配置中指定了security为high, 则pipeline中包含auth不然不包含。
pipeline = pipeline.split() filters = [loader.get_filter(n) for n in pipeline[:-1]]
这段的逻辑是从pipeline中找到filter的列表(pipeline最后一项是app,其他都是filter)。
app = loader.get_app(pipeline[-1]) filters.reverse() # apply in reverse order! for filter in filters: app = filter(app)
这段的逻辑是,获取app,而且把filter列表中的中间件反过来逐层包裹,达到
CatchError(AuthToken(CheckNumber))
的效果。
接下来咱们看一下如何使用。 咱们把3个wsgi组件和这个pipeline_factory 放入config.ini。 而后运行以下代码:
if __name__ == '__main__': from paste import httpserver from paste.deploy import loadapp httpserver.serve(loadapp('config:conf.ini', name='my_pipeline', relative_to='.'), host='127.0.0.1', port='8080')
如今经过下面的命令
curl http://127.0.0.1/check_number/100
访问会返回noauth错误,由于你没有加上 -H “token:222” 的验证信息。 但设置security=False重启服务器,继续访问,则不用加验证信息,由于根据composite section对应的代码逻辑,AuthToken组件并无加到pipeline
neutron的paste配置文件在
cat /usr/share/neutron/api-paste.ini [composite:neutron] use = egg:Paste#urlmap /: neutronversions /v2.0: neutronapi_v2_0 [composite:neutronapi_v2_0] use = call:neutron.auth:pipeline_factory noauth = request_id catch_errors extensions neutronapiapp_v2_0 keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0 [filter:request_id] paste.filter_factory = oslo_middleware:RequestId.factory [filter:catch_errors] paste.filter_factory = oslo_middleware:CatchErrors.factory [filter:keystonecontext] paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory [filter:extensions] paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory [app:neutronversions] paste.app_factory = neutron.api.versions:Versions.factory [app:neutronapiapp_v2_0] paste.app_factory = neutron.api.v2.router:APIRouter.factory
整个neutron api server就是一个wsgi server 加一堆 wsgi middleware 和 wsgi app组成的一个大的pipeline。 启动加载该配置文件是neutron 的wsgi server负责的事情。这部分咱们后面再看。咱们先看该配置文件。一样,从下向上看发现有两个APP neutronversions和neutronapiapp_v2_0 以及5个middleware。 在composite区域中又组合了两个pipeline。一个不带验证,一个带有keystone验证。最后composite部分经过paste包中的urlmap函数指定,若是URL是/则访问neutronversion中间件,若是是/v2.0则访问composite部分的pipeline,而这也正是咱们真正API访问所走的路径。
大部分环境中所采用的pipeline是keystone这条。由于咱们经过devstack或者官方文档安装的环境通常都会带有权限校验。 对于咱们来讲,真正须要关注的代码在extensions和neutron_app_v2_0这两部分,其余部分都和业务无关。 由配置文件能够很容易找到各个组件的代码位置,如neutron.api.v2.router表明 neutron/api/v2/router。从这里开始,就能够逐步阅读代码追踪每一个API的调用流程。