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对应调用代码如下
-
def _pipeline_app_context(self, object_type, section, name,
-
global_conf, local_conf, global_additions):
-
if 'pipeline' not in local_conf:
-
raise LookupError(
-
"The [%s] section in %s is missing a 'pipeline' setting"
-
% (section, self.filename))
-
pipeline = local_conf.pop('pipeline').split()
-
if local_conf:
-
raise LookupError(
-
"The [%s] pipeline section in %s has extra "
-
"(disallowed) settings: %s"
-
% (', '.join(local_conf.keys())))
-
context = LoaderContext(None, PIPELINE, None, global_conf,
-
local_conf, self)
-
context.app_context = self.get_context(
-
APP, pipeline[-1], global_conf)
-
context.filter_contexts = [
-
self.get_context(FILTER, name, global_conf)
-
for name in pipeline[:-1]]
-
return context
-
-
class _PipeLine(_ObjectType):
-
name = 'pipeline'
-
-
def invoke(self, context):
-
app = context.app_context.create()
-
filters = [c.create() for c in context.filter_contexts]
-
filters.reverse()
-
for filter in filters:
-
app = filter(app)
-
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
下面的测试代码是模仿写的,知道能工作就可以了
-
import webob.dec
-
import webob.exc
-
from webob import Response
-
from wsgiref.simple_server import make_server
-
-
def loli(a, b):
-
res = Response('wtf~~~')
-
gg = res(a, b)
-
return gg
-
-
class Wtf():
-
def __init__(self, fun):
-
self.app = fun
-
-
def __call__(self, a, b):
-
return self.app(a, b)
-
-
class Rtest(object):
-
def __init__(self):
-
self._router = Wtf(self._dispatch)
-
-
@webob.dec.wsgify()
-
def __call__(self, req):
-
return self._router
-
-
@staticmethod
-
@webob.dec.wsgify()
-
def _dispatch(req):
-
return loli
-
-
application = Rtest()
-
httpd = make_server('localhost', 8080, application)
-
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
-
# 倒转filter列表
-
filters.reverse()
-
for filter in filters:
-
app = filter(app)
-
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
-
class Middleware(Application):
-
@classmethod
-
def factory(cls, global_config):
-
# factory方法就是之前说的用来生成自身实例的方法
-
def _factory(app):
-
return cls(app)
-
return _factory
-
-
def __init__(self, application):
-
super(Middleware, self).__init__()
-
self.application = application
-
-
def process_request(self, request):
-
-
def process_response(self, request, response):
-
-
@webob.dec.wsgify()
-
def __call__(self, request):
-
# 所有filter类都必须有
-
# response = request.get_response(self.application)
-
# 通过这个调用下层的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是怎么添加路由的
-
class S3Extension(wsgi.V3ExtensionRouter):
-
def add_routes(self, mapper):
-
controller = S3Controller()
-
# validation
-
self._add_resource(
-
mapper, controller,
-
path='/s3tokens',
-
post_action='authenticate',
-
rel=json_home.build_v3_extension_resource_relation(
-
'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
就够了
阅读(1248) | 评论(0) | 转发(0) |