Openstack源代码分析之PasteDeploy+Webob实例以及Openstack源代码下PasteDeploy+Webob+Routes分析

以下内容转自 :http://blog.csdn.net/bluefire1991/article/details/13614243   作者:bluefire1991


通过PasteDeploy+Webob来配置WSGI服务器接口

Webob是一种封装了HTTP协议的模块,具体课参考官方文档,不过这两天不知为什么不能访问,我是直接下载的源代码,源代码下docs自带本地文档,可以通过sphnix-builder的命令来生成本地文档

测试了两种方案

一种是不使用Webob装饰器的方式

一种是使用Webob装饰器的方式

配置文件如下test-deploy.ini

[plain]  view plain copy
  1. [DEFAULT]  
  2. key1=value1  
  3. key2=value2  
  4. key3=values  
  5.   
  6. [composite:main]  
  7. use=egg:Paste#urlmap  
  8. /=show  
  9. /auther=auther  
  10. /version=version  
  11.   
  12. [pipeline:show]  
  13. pipeline = auth root  
  14.   
  15. [pipeline:version]  
  16. pipeline = logrequest showversion  
  17.   
  18. [pipeline:auther]  
  19. pipeline = logrequest showauther  
  20.   
  21. [filter:logrequest]  
  22. username = root  
  23. password = 123  
  24. paste.filter_factory = test.testdeploy:log_factory  
  25.   
  26. [app:showversion]  
  27. version = 1.0.0  
  28. paste.app_factory = test.testdeploy:version_factory  
  29.   
  30. [app:showauther]  
  31. auther = bluefire1991  
  32. paste.app_factory = test.testdeploy:showauther_factory  
  33.   
  34. [app:root]  
  35. paste.app_factory = test.testdeploy:show_factory  
  36.       
  37. [filter:auth]    
  38. paste.filter_factory = test.testdeploy:filter_factory  

配置方案用的类似openstack实现的配置方案,paste.xxx_factory和pipeline的方式实现配置,不过路径的配置openstack源代码实现的是用Routes实现RESTful的功能,这里没有用Routes直接在ini文件下配置的路径。为什么这样配置可以参考我的上一篇博客。

代码文件testdeploy.py

[python]  view plain copy
  1. ''''' 
  2. Created on 2013-10-28 
  3.  
  4. @author: root 
  5. '''  
  6.   
  7. import logging  
  8. import os  
  9. import sys  
  10. import webob  
  11. from webob import Request  
  12. from webob import Response  
  13. from webob.dec import *  
  14. from webob.exc import *  
  15.   
  16. from paste.deploy import loadapp  
  17. from wsgiref.simple_server import make_server  
  18. import signal  
  19.   
  20. def sigint_handler(signal, frame):  
  21.     """Exits at SIGINT signal."""  
  22.     logging.debug('SIGINT received, stopping servers.')  
  23.     sys.exit(0)  
  24.   
  25. #用于封装app     
  26. @wsgify  
  27. def test(request):    
  28.     return Response('Here!')  
  29.   
  30. #用于封装app  
  31. @wsgify  
  32. def application(request):    
  33.     return Response('Hello and Welcome!')  
  34.   
  35. #wsgify.middleware用于wsgi的对外filter层,必须要两个参数,request对象和app对象,app用于向下一个filter或者app传递参数  
  36. @wsgify.middleware  
  37. def auth_filter(request, app):    
  38.     if request.headers.get('X-Auth-Token') != 'bluefire1991':  
  39.         #类似于在这里写了start_response和return函数,只不过这里由webob的Request对象封装好了,本来test是一个函数对象,调用需要test(req),  
  40.         #通过装饰器@wsgi直接变成一个对象,参数在装饰器内部实现wsgify(test)  
  41.         return test  
  42.     return app(request)  
  43.   
  44. #app_factory  
  45. def show_factory(global_conf,**local_conf):  
  46.     return application  
  47.   
  48. #app_factory  
  49. def version_factory(global_conf,**local_conf):  
  50.         return ShowVersion(global_conf,local_conf)  
  51.   
  52. #app_fatory  
  53. def showauther_factory(global_conf,**local_conf):  
  54.         return ShowAuther(global_conf,local_conf)  
  55.   
  56. #filter_factory  
  57. def filter_factory(global_conf, **local_conf):    
  58.     return auth_filter  
  59.   
  60. #filter_factory  
  61. def log_factory(global_conf,**local_conf):  
  62.     def filter(app):  
  63.         return LogFilter(app,global_conf,local_conf)  
  64.     return filter  
  65.   
  66.   
  67. class LogFilter():  
  68.     def __init__(self,app,global_conf,local_conf):  
  69.         self.app = app  
  70.         self.global_conf=global_conf  
  71.         self.local_conf=local_conf  
  72.           
  73.     def __call__(self,environ,start_response):  
  74.         print "filter:LogFilter is called."  
  75.         req = Request(environ)  
  76.         if req.GET.get("username", "")==self.local_conf['username'] and req.GET.get("password", "")==self.local_conf['password']:  
  77.             return self.app(environ,start_response)  
  78.         start_response("200 OK",[("Content-type""text/plain")])  
  79.         return ["You are not authorized"]  
  80.   
  81. class ShowVersion():  
  82.     def __init__(self,global_conf,local_conf):  
  83.         self.global_conf=global_conf  
  84.         self.local_conf=local_conf  
  85.           
  86.     def __call__(self,environ,start_response):  
  87.         start_response("200 OK",[("Content-type""text/plain")])  
  88.         return ['Version',self.local_conf['version']]  
  89.   
  90. class ShowAuther():  
  91.     def __init__(self,global_conf,local_conf):  
  92.         self.global_conf=global_conf  
  93.         self.local_conf=local_conf          
  94.       
  95.     def __call__(self,environ,start_response):  
  96.         res = Response()  
  97.         res.status = "200 OK"  
  98.         res.content_type = "text/plain"  
  99.         # get operands  
  100.         res.body = ["auther",self.local_conf['auther']]  
  101.         return res  
  102.   
  103. if __name__ == '__main__':  
  104.     signal.signal(signal.SIGINT, sigint_handler)  
  105.     configfile="test-deploy.ini"  
  106.     appname="main"  
  107.     wsgi_app = loadapp("config:%s" % os.path.abspath(configfile), appname)  
  108.     server = make_server('localhost',8088,wsgi_app)  
  109.     server.serve_forever()  
  110.     pass  

由代码和配置文件分析,配置文件有三个路径,/,/auther,/version,/的实现路径是filter_factory(auth_filter)->show_factory(application),/auther和/version的实现方案是由log_factory(LogFilter)->showauther_factory(ShowAuther.__call___)和version_factory(ShowVersion.__call__),/路径的方案都是直接通过wsgi的装饰器实现的,/verison是python原生的start_response方案实现的,/auther是利用webob封装的Response对象实现的。


但是到openstack这里有一个问题,在keystone处理时,有多个路径,不可能都放在ini里面配置(而且里面也没有),那这么多路径他是怎么实现的呢?

源代码说明,他用了Routes,来实现路径映射功能,官方文档地址是Routes官方文档

先放几段Openstack的关键源代码,他在xxx_factory(都很类似)实现的代码如下

[python]  view plain copy
  1. @fail_gracefully  
  2. def public_app_factory(global_conf, **local_conf):  
  3.     controllers.register_version('v2.0')  
  4.     conf = global_conf.copy()  
  5.     conf.update(local_conf)  
  6.     return wsgi.ComposingRouter(routes.Mapper(),  
  7.                                 [identity.routers.Public(),  
  8.                                  token.routers.Router(),  
  9.                                  routers.VersionV2('public'),  
  10.                                  routers.Extension(False)])  

实现了一个ComposingRouter对象,其中router.Mapper()基于创建了一个路由对象,通过这个就可以实现映射,ComposingRoute类实现

[python]  view plain copy
  1. class ComposingRouter(Router):  
  2.     def __init__(self, mapper=None, routers=None):  
  3.         if mapper is None:  
  4.             mapper = routes.Mapper()  
  5.         if routers is None:  
  6.             routers = []  
  7.         for router in routers:  
  8.             router.add_routes(mapper)  
  9.         super(ComposingRouter, self).__init__(mapper)  
可见创建了mapper对象,调用[]里面所有对象(identity.routers.Public())下的add_routers方法,再跟进add_routers方法看看,以identity.routers.Public()为例

[python]  view plain copy
  1. class Public(wsgi.ComposableRouter):  
  2.     def add_routes(self, mapper):  
  3.         tenant_controller = controllers.Tenant()  
  4.         mapper.connect('/tenants',  
  5.                        controller=tenant_controller,  
  6.                        action='get_projects_for_token',  
  7.                        conditions=dict(method=['GET']))  
可见,他用mapper.connect生成了一个路径/tenants,访问方式必须为GET,调用函数为tenant_controller(controller.Tenant()对象)下get_projects_for_token函数。如下

[python]  view plain copy
  1. class Tenant(controller.V2Controller):  
  2.     def get_all_projects(self, context, **kw):  
  3.         """Gets a list of all tenants for an admin user."""  
  4.         if 'name' in context['query_string']:  
  5.             return self.get_project_by_name(  
  6.                 context, context['query_string'].get('name'))  
  7.   
  8.         self.assert_admin(context)  
  9.         tenant_refs = self.identity_api.list_projects()  
  10.         for tenant_ref in tenant_refs:  
  11.             tenant_ref = self.filter_domain_id(tenant_ref)  
  12.         params = {  
  13.             'limit': context['query_string'].get('limit'),  
  14.             'marker': context['query_string'].get('marker'),  
  15.         }  
  16.         return self._format_project_list(tenant_refs, **params)  
  17.   
  18.     def get_projects_for_token(self, context, **kw):  
  19.         """Get valid tenants for token based on token used to authenticate. 
  20.  
  21.         Pulls the token from the context, validates it and gets the valid 
  22.         tenants for the user in the token. 
  23.  
  24.         Doesn't care about token scopedness. 
  25.  
  26.         """  
  27.         try:  
  28.             token_ref = self.token_api.get_token(context['token_id'])  
  29.         except exception.NotFound as e:  
  30.             LOG.warning('Authentication failed: %s' % e)  
  31.             raise exception.Unauthorized(e)  
  32.   
  33.         user_ref = token_ref['user']  
  34.         tenant_refs = (  
  35.             self.assignment_api.list_projects_for_user(user_ref['id']))  
  36.         tenant_refs = [self.filter_domain_id(ref) for ref in tenant_refs  
  37.                        if ref['domain_id'] == DEFAULT_DOMAIN_ID]  
  38.         params = {  
  39.             'limit': context['query_string'].get('limit'),  
  40.             'marker': context['query_string'].get('marker'),  
  41.         }  
  42.         return self._format_project_list(tenant_refs, **params)  

这时又有两个疑问产生了:

1.paste_factory的对象不是用webob来封装的HTTP服务吗,我在get_projects_for_token函数下没看见啊?

2.mapper.connect()配置完成后为什么就能实现映射了呢?

问题1.我的理解:

Tenant的基类是V2Controller,V2Controller的基类是class V2Controller(wsgi.Application),而在wsgi.Application类中实现如下

[python]  view plain copy
  1. class Application(BaseApplication):  
  2.     @webob.dec.wsgify(RequestClass=Request)  
  3.     def __call__(self, req):  
  4.         arg_dict = req.environ['wsgiorg.routing_args'][1]  
  5.         action = arg_dict.pop('action')  
  6.         del arg_dict['controller']  
  7.         LOG.debug(_('arg_dict: %s'), arg_dict)  
  8.   
  9.         # allow middleware up the stack to provide context, params and headers.  
  10.         context = req.environ.get(CONTEXT_ENV, {})  
  11.         context['query_string'] = dict(req.params.iteritems())  
  12.         context['headers'] = dict(req.headers.iteritems())  
  13.         context['path'] = req.environ['PATH_INFO']  
  14.         params = req.environ.get(PARAMS_ENV, {})  
  15.   
  16.         for name in ['REMOTE_USER''AUTH_TYPE']:  
  17.             try:  
  18.                 context[name] = req.environ[name]  
  19.             except KeyError:  
  20.                 try:  
  21.                     del context[name]  
  22.                 except KeyError:  
  23.                     pass  
  24.   
  25.         params.update(arg_dict)  
  26.   
  27.         context.setdefault('is_admin'False)  
  28.   
  29.         # TODO(termie): do some basic normalization on methods  
  30.         method = getattr(self, action)  
  31.   
  32.         # NOTE(vish): make sure we have no unicode keys for py2.6.  
  33.         params = self._normalize_dict(params)  
  34.   
  35.         try:  
  36.             result = method(context, **params)  
  37.         except exception.Unauthorized as e:  
  38.             LOG.warning(  
  39.                 _('Authorization failed. %(exception)s from %(remote_addr)s') %  
  40.                 {'exception': e, 'remote_addr': req.environ['REMOTE_ADDR']})  
  41.             return render_exception(e, user_locale=req.best_match_language())  
  42.         except exception.Error as e:  
  43.             LOG.warning(e)  
  44.             return render_exception(e, user_locale=req.best_match_language())  
  45.         except TypeError as e:  
  46.             LOG.exception(e)  
  47.             return render_exception(exception.ValidationError(e),  
  48.                                     user_locale=req.best_match_language())  
  49.         except Exception as e:  
  50.             LOG.exception(e)  
  51.             return render_exception(exception.UnexpectedError(exception=e),  
  52.                                     user_locale=req.best_match_language())  
  53.   
  54.         if result is None:  
  55.             return render_response(status=(204'No Content'))  
  56.         elif isinstance(result, basestring):  
  57.             return result  
  58.         elif isinstance(result, webob.Response):  
  59.             return result  
  60.         elif isinstance(result, webob.exc.WSGIHTTPException):  
  61.             return result  
  62.   
  63.         response_code = self._get_response_code(req)  
  64.         return render_response(body=result, status=response_code)  
终于看到了久违的wsgi了,那我怎么在调用对象时候,调用我需要的函数呢,代码很清楚

[python]  view plain copy
  1. method = getattr(self, action)  
  2.   
  3. # NOTE(vish): make sure we have no unicode keys for py2.6.  
  4. params = self._normalize_dict(params)  
  5.   
  6. try:  
  7.     result = method(context, **params)  
getattr里action就是我们上面的需要的函数,在这里调用,再通过render_response(body=result, status=response_code),render_response是自己的http返回函数。问题到这里应该能得到解答(?)

问题2,我的理解:

跟到class ComposingRouter(Router)的基类Router


[python]  view plain copy
  1. class Router(object):  
  2.     """WSGI middleware that maps incoming requests to WSGI apps."""  
  3.   
  4.     def __init__(self, mapper):  
  5.         """Create a router for the given routes.Mapper. 
  6.  
  7.         Each route in `mapper` must specify a 'controller', which is a 
  8.         WSGI app to call.  You'll probably want to specify an 'action' as 
  9.         well and have your controller be an object that can route 
  10.         the request to the action-specific method. 
  11.  
  12.         Examples: 
  13.           mapper = routes.Mapper() 
  14.           sc = ServerController() 
  15.  
  16.           # Explicit mapping of one route to a controller+action 
  17.           mapper.connect(None, '/svrlist', controller=sc, action='list') 
  18.  
  19.           # Actions are all implicitly defined 
  20.           mapper.resource('server', 'servers', controller=sc) 
  21.  
  22.           # Pointing to an arbitrary WSGI app.  You can specify the 
  23.           # {path_info:.*} parameter so the target app can be handed just that 
  24.           # section of the URL. 
  25.           mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp()) 
  26.  
  27.         """  
  28.         # if we're only running in debug, bump routes' internal logging up a  
  29.         # notch, as it's very spammy  
  30.         if CONF.debug:  
  31.             logging.getLogger('routes.middleware')  
  32.   
  33.         self.map = mapper  
  34.         self._router = routes.middleware.RoutesMiddleware(self._dispatch,  
  35.                                                           self.map)  
  36.  
  37.     @webob.dec.wsgify(RequestClass=Request)  
  38.     def __call__(self, req):  
  39.         """Route the incoming request to a controller based on self.map. 
  40.  
  41.         If no match, return a 404. 
  42.  
  43.         """  
  44.         return self._router  
  45.  
  46.     @staticmethod  
  47.     @webob.dec.wsgify(RequestClass=Request)  
  48.     def _dispatch(req):  
  49.         """Dispatch the request to the appropriate controller. 
  50.  
  51.         Called by self._router after matching the incoming request to a route 
  52.         and putting the information into req.environ.  Either returns 404 
  53.         or the routed WSGI app's response. 
  54.  
  55.         """  
  56.         match = req.environ['wsgiorg.routing_args'][1]  
  57.         if not match:  
  58.             return render_exception(  
  59.                 exception.NotFound(_('The resource could not be found.')),  
  60.                 user_locale=req.best_match_language())  
  61.         app = match['controller']  
  62.         return app  
根据Router基类下routes对象自己的实现机制__dispatch和map实现对请求路径匹配,在找到路径后根据routes机制即可映射wsgi服务(怎么实现的?routes官方文档没有搜索到)。问题2应该能得到解答(?)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值