分享

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

hochikong 发表于 2014-7-27 11:24:55 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 25800
问题导读:
1.什么是WSGI?
2.什么是WebOb?
3.
在keystone处理时,如何实现路径映射(
Routes)








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

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

测试了两种方案:

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

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

配置文件如下test-deploy.ini:


  1. [DEFAULT]
  2. key1=value1
  3. key2=value2
  4. key3=values
  5. [composite:main]
  6. use=egg:Paste#urlmap
  7. /=show
  8. /auther=auther
  9. /version=version
  10. [pipeline:show]
  11. pipeline = auth root
  12. [pipeline:version]
  13. pipeline = logrequest showversion
  14. [pipeline:auther]
  15. pipeline = logrequest showauther
  16. [filter:logrequest]
  17. username = root
  18. password = 123
  19. paste.filter_factory = test.testdeploy:log_factory
  20. [app:showversion]
  21. version = 1.0.0
  22. paste.app_factory = test.testdeploy:version_factory
  23. [app:showauther]
  24. auther = bluefire1991
  25. paste.app_factory = test.testdeploy:showauther_factory
  26. [app:root]
  27. paste.app_factory = test.testdeploy:show_factory
  28.    
  29. [filter:auth]  
  30. paste.filter_factory = test.testdeploy:filter_factory</font></font>
复制代码
配置方案用的类似openstack实现的配置方案,paste.xxx_factory和pipeline的方式实现配置,不过路径的配置openstack源代码实现的是用Routes实现RESTful的功能,这里没有用Routes直接在ini文件下配置的路径。为什么这样配置可以参考我的上一篇博客。


代码文件testdeploy.py:


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


  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)])</font></font>
复制代码
实现了一个ComposingRouter对象,其中router.Mapper()基于创建了一个路由对象,通过这个就可以实现映射,ComposingRoute类实现

  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)</font></font>
复制代码
可见创建了mapper对象,调用[]里面所有对象(identity.routers.Public())下的add_routers方法,再跟进add_routers方法看看,以identity.routers.Public()为例

  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']))</font></font>
复制代码
可见,他用mapper.connect生成了一个路径/tenants,访问方式必须为GET,调用函数为tenant_controller(controller.Tenant()对象)下get_projects_for_token函数。如下

  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.         self.assert_admin(context)
  8.         tenant_refs = self.identity_api.list_projects()
  9.         for tenant_ref in tenant_refs:
  10.             tenant_ref = self.filter_domain_id(tenant_ref)
  11.         params = {
  12.             'limit': context['query_string'].get('limit'),
  13.             'marker': context['query_string'].get('marker'),
  14.         }
  15.         return self._format_project_list(tenant_refs, **params)
  16.     def get_projects_for_token(self, context, **kw):
  17.         """Get valid tenants for token based on token used to authenticate.
  18.         Pulls the token from the context, validates it and gets the valid
  19.         tenants for the user in the token.
  20.         Doesn't care about token scopedness.
  21.         """
  22.         try:
  23.             token_ref = self.token_api.get_token(context['token_id'])
  24.         except exception.NotFound as e:
  25.             LOG.warning('Authentication failed: %s' % e)
  26.             raise exception.Unauthorized(e)
  27.         user_ref = token_ref['user']
  28.         tenant_refs = (
  29.             self.assignment_api.list_projects_for_user(user_ref['id']))
  30.         tenant_refs = [self.filter_domain_id(ref) for ref in tenant_refs
  31.                        if ref['domain_id'] == DEFAULT_DOMAIN_ID]
  32.         params = {
  33.             'limit': context['query_string'].get('limit'),
  34.             'marker': context['query_string'].get('marker'),
  35.         }
  36.         return self._format_project_list(tenant_refs, **params)</font></font>
复制代码
这时又有两个疑问产生了:

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

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

问题1.我的理解:

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


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

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

问题2,我的理解:

跟到class ComposingRouter(Router)的基类Router

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


疑惑的部分:

1.routes的机制到底怎么实现routes到webob的映射,我是看到一篇别人的博客才找到思路,一会转载到我的收藏夹


地址:http://blog.csdn.net/spch2008/article/details/9005885



#######################################################################################
本文转自:http://blog.csdn.net/bluefire1991/article/details/13614243


参考资料及文献:
1.Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI):
http://zh.wikipedia.org/zh/Web%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3
2.paste.deploy学习:
http://blog.chinaunix.net/uid-22400280-id-3281767.html
3.PasteDeployment官方手册:
http://pythonpaste.org/deploy/index.html
4.Python.Paste指南之Deploy(1)-概念:
http://wanglianghuaihua.blog.163.com/blog/static/5425153120138273471531/
5.Openstack源代码分析之paste.deploy:
http://blog.csdn.net/bluefire1991/article/details/13289485










欢迎加入about云群9037177932227315139327136 ,云计算爱好者群,亦可关注about云腾讯认证空间||关注本站微信

没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条