M版本keystone v3的pipeline如下
pipeline = cors sizelimit url_normalize request_id admin_token_auth build_auth_context token_auth json_body ec2_extension_v3 s3_extension service_v3

pipeline对应调用代码如下

点击(此处)折叠或打开

  1. def _pipeline_app_context(self, object_type, section, name,
  2.                               global_conf, local_conf, global_additions):
  3.         if 'pipeline' not in local_conf:
  4.             raise LookupError(
  5.                 "The [%s] section in %s is missing a 'pipeline' setting"
  6.                 % (section, self.filename))
  7.         pipeline = local_conf.pop('pipeline').split()
  8.         if local_conf:
  9.             raise LookupError(
  10.                 "The [%s] pipeline section in %s has extra "
  11.                 "(disallowed) settings: %s"
  12.                 % (', '.join(local_conf.keys())))
  13.         context = LoaderContext(None, PIPELINE, None, global_conf,
  14.                                 local_conf, self)
  15.         context.app_context = self.get_context(
  16.             APP, pipeline[-1], global_conf)
  17.         context.filter_contexts = [
  18.             self.get_context(FILTER, name, global_conf)
  19.             for name in pipeline[:-1]]
  20.         return context
  21.     
  22. class _PipeLine(_ObjectType):
  23.     name = 'pipeline'

  24.     def invoke(self, context):
  25.         app = context.app_context.create()
  26.         filters = [c.create() for c in context.filter_contexts]
  27.         filters.reverse()
  28.         for filter in filters:
  29.             app = filter(app)
  30.         return app
之前我们说过pipe里除了最后一个是app以外其他都是filter
app初始通过app = context.app_context.create()来初始化
定位app
[app:service_v3]
use = egg:keystone#service_v3
查找entry_point.txt
service_v3 = keystone.version.service:v3_app_factory
app的create()后返回的对象就是wsgi.ComposingRouter类实例

注: app继承的route的call方法比较绕,看不懂参数怎么传进去的,装饰器非常复杂
参考一个简要的说明http://blog.csdn.net/spch2008/article/details/9003410
下面的测试代码是模仿写的,知道能工作就可以了

点击(此处)折叠或打开

  1. import webob.dec
  2. import webob.exc
  3. from webob import Response
  4. from wsgiref.simple_server import make_server

  5. def loli(a, b):
  6.     res = Response('wtf~~~')
  7.     gg = res(a, b)
  8.     return gg

  9. class Wtf():
  10.     def __init__(self, fun):
  11.         self.app = fun

  12.     def __call__(self, a, b):
  13.         return self.app(a, b)

  14. class Rtest(object):
  15.     def __init__(self):
  16.         self._router = Wtf(self._dispatch)

  17.     @webob.dec.wsgify()
  18.     def __call__(self, req):
  19.         return self._router

  20.     @staticmethod
  21.     @webob.dec.wsgify()
  22.     def _dispatch(req):
  23.         return loli

  24. application = Rtest()
  25. httpd = make_server('localhost', 8080, application)
  26. httpd.serve_forever()
上述代码直接用application(1,2)是不行的,
wsgi调用就没问题,那个装饰器会校验输入参数
妈的我越来越讨厌python的的装饰器了,顺便,根据https://www.ustack.com/blog/demoapi1/
openstack的新服务都不用Paste + PasteDeploy + Routes + WebOb的框架转用Pecan框架了

filter我们拿json_body来当例子
[filter:json_body]
use = egg:keystone#json_body
c.create()调用_Filter类的invoke方法
查找entry_point.txt
json_body = keystone.middleware:JsonBodyMiddleware.factory
定位到JsonBodyMiddleware的factory
和APP不同的是
c.create()最后返回的不是JsonBodyMiddleware类实例,而是一个函数
当这个函数输入参数app就返回JsonBodyMiddleware类
那参数app是什么呢......
看后面PipeLine类的invoke

点击(此处)折叠或打开

  1. # 倒转filter列表
  2. filters.reverse()
  3. for filter in filters:
  4.     app = filter(app)
  5. return app
filters.reverse()  倒转filter列表
最后一个filter的初始化用的参数就是service_v3返回的wsgi.ComposingRouter类实例
然后倒着把每一个filter类作为前一个filter的参数来初始化
每个filter类实例都有一个属性application指向后一个filter
最后一个filter的application属性指向service_v3返回的wsgi.ComposingRouter类实例
最后urlmap字典里的app就是第一个filter的实例

所以,当一个请求过来的时候,执行的处理顺序为
请求-->cors-->sizelimit-->....顺序处理...service_v3
返回就是
service_v3-->s3_extension-->.......cors

每个filter有需要就重写处理请求的代码和处理返回的代码
没有对应操作,就传入下层或者返回给上层
看懂上面,通过openstack封装好的类写filter就非常简单了
一个最简单的方式就是继承wsgi.Middleware类
我们看看wsgi.Middleware

点击(此处)折叠或打开

  1. class Middleware(Application):
  2.     @classmethod
  3.     def factory(cls, global_config):
  4.         # factory方法就是之前说的用来生成自身实例的方法
  5.         def _factory(app):
  6.             return cls(app)
  7.         return _factory

  8.     def __init__(self, application):
  9.         super(Middleware, self).__init__()
  10.         self.application = application

  11.     def process_request(self, request):

  12.     def process_response(self, request, response):

  13.     @webob.dec.wsgify()
  14.     def __call__(self, request):
  15.     # 所有filter类都必须有
  16.     # response = request.get_response(self.application)
  17.     # 通过这个调用下层的filter
写自己的filter,只要重写process_request或者process_response方法就可以了
process_request方法非常简单,只要设置参数request的数据即可,如果process_request有返回值,必须返回response类,返回后不继续往下走(请求被中途拦截返回)
process_response不重写的话自动返回给上一层
process_request和process_response都在这里调用都是是封装在__call__里调用的

这个写filter的方法优点是处理请求和返回的数据简单,但没有添加路由的接口
所以添加路由的filter通过继承wsgi.V3ExtensionRouter
我们看s3_extension这个filter是怎么添加路由的

点击(此处)折叠或打开

  1. class S3Extension(wsgi.V3ExtensionRouter):
  2.     def add_routes(self, mapper):
  3.         controller = S3Controller()
  4.         # validation
  5.         self._add_resource(
  6.             mapper, controller,
  7.             path='/s3tokens',
  8.             post_action='authenticate',
  9.             rel=json_home.build_v3_extension_resource_relation(
  10.                 's3tokens', '1.0', 's3tokens'))
定义好controller,重写add_routes类添加路由....真TM简单
这个方法适合添加路由但是不适合做filter该干的事情——拦截、处理请求和返回
如果需要处理请求和返回需要重写__call__方法

所以
1、想通过filter扩展controller,集成wsgi.V3ExtensionRouter
2、通过filter做一些拦截、校验之类的功能,继承wsgi.Middleware

顺便总结下默认pipeline每个filter的作用


cors 跨域
sizelimit  限制bodysize
url_normalize  格式化url,  比如url最后一个字符串是/ 通过切片剔除最后一个字符串
request_id  生成request_id
admin_token_auth   允许使用admin_token_auth登陆,就是配置文件里写死的那个token, 这个在filter在创建管理员后需要剔除掉
build_auth_context   从request中获取auth_context并处理一下, 写入store中, 然后把auth_context塞到request.environ中
token_auth  没干什么事情,就是把获取到的数据 从head取出来塞到environ里  TokenAuthMiddleware
json_body  把request的body转换为dict塞到request.environ[PARAMS_ENV]中
ec2_extension_v3  加了几个ec2相关的登陆认证    顺便当headers['Content-Type'] != 'application/json-home' 把body转成了bytes
s3_extension  加了一个/s3tokens的路由   s3方式登陆 和上面一样body转bytes(都是集成V3ExtensionRouter)
service_v3  基本路由

也就是说 
其实只用uuid不上ssl什么的v3的pipe只需要
sizelimit url_normalize request_id build_auth_context token_auth json_body service_v3
就够了
10-17 02:49