Openstack Keystone 认证流程(五)--路由
来源:互联网 发布:mac系统盘在哪 编辑:程序博客网 时间:2024/04/29 01:25
1. 路由实现
在上一章中, 我们一起过了admin_api的所有流水线的处理, 其中有好几个点是用来添加路由信息的。这一章我们来详细看看路由的具体实现。
路由是MVC架构中非常重要的一个关键节点, 可以说, 如果没有路由,就不可能去实现一个很好的MVC架构, 路由也是整个系统中最先处理的节点。如果想弄清楚Keystone的整体架构, 路由是必须先搞清楚的。
看具体实现之前, 我们先看看路由的具体用法。比如说有如下一条路由:
import routes.middleware...class Ec2Extension(wsgi.ExtensionRouter): def add_routes(self, mapper): ec2_controller = controllers.Ec2Controller() # validation mapper.connect( '/ec2tokens', controller=ec2_controller, action='authenticate', conditions=dict(method=['POST']))
假设Keystone的服务器为127.0.0.1:35357, 那么如果我们访问如下地址时:
http://127.0.0.1:35357/ec2tokens
经过路由分发, 代码就会跑到类ec2_controller的authenticate方法中。
知道了怎么使用之后, 我们再来看看它的具体实现。我们找到Ec2Extension的路由父类:
class ExtensionRouter(Router): def __init__(self, application, mapper=None): if mapper is None: mapper = routes.Mapper() self.application = application self.add_routes(mapper) mapper.connect('{path_info:.*}', controller=self.application) super(ExtensionRouter, self).__init__(mapper) def add_routes(self, mapper): pass @classmethod def factory(cls, global_config, **local_config): def _factory(app): conf = global_config.copy() conf.update(local_config) return cls(app, **local_config) return _factory
在ExtensionRouter的__init__ 方法中, routes.Mapper创建了一个Mappper对象。这是一个实际完成路由功能的模块, 这里我们不去管里面的具体实现, 有兴趣的可以参考routes – Route and Mapper core classes。
至此,可以看到, 在ExtensionRouter的factory方法中,创建一个ExtensionRouter的一个实例, 并在其它构造方法中,创建Mapper对象,并把所有的路由信息全部增加到Mapper对象中,然后调用其它父类的构造方法。继续看看Router类的构造方法。
class Router(object): def __init__(self, mapper): if CONF.debug: logging.getLogger('routes.middleware') self.map = mapper self._router = routes.middleware.RoutesMiddleware(self._dispatch,self.map) @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): return self._router @staticmethod @webob.dec.wsgify(RequestClass=Request) def _dispatch(req): match = req.environ['wsgiorg.routing_args'][1] if not match: return render_exception( exception.NotFound(_('The resource could not be found.')), user_locale=req.best_match_language()) app = match['controller'] return app
在Route类的__init__ 中使用, 使用Router._dispath方法作为一个中间件的应用程序初始化一个中间件。找到这个中间件的实现方法。在routes.middleware.py 中, 其实现如下:
class RoutesMiddleware(object): self.app = wsgi_app self.mapper = mapper self.singleton = singleton self.use_method_override = use_method_override self.path_info = path_info self.log_debug = logging.DEBUG >= log.getEffectiveLevel() if self.log_debug: log.debug("Initialized with method overriding = %s, and path " "info altering = %s", use_method_override, path_info) def __call__(self, environ, start_response): """Resolves the URL in PATH_INFO, and uses wsgi.routing_args to pass on URL resolver results.""" old_method = None if self.use_method_override: req = None # In some odd cases, there's no query string try: qs = environ['QUERY_STRING'] except KeyError: qs = '' if '_method' in qs: req = Request(environ) req.errors = 'ignore' if '_method' in req.GET: old_method = environ['REQUEST_METHOD'] environ['REQUEST_METHOD'] = req.GET['_method'].upper() if self.log_debug: log.debug("_method found in QUERY_STRING, altering " "request method to %s", environ['REQUEST_METHOD']) elif environ['REQUEST_METHOD'] == 'POST' and is_form_post(environ): if req is None: req = Request(environ) req.errors = 'ignore' if '_method' in req.POST: old_method = environ['REQUEST_METHOD'] environ['REQUEST_METHOD'] = req.POST['_method'].upper() if self.log_debug: log.debug("_method found in POST data, altering " "request method to %s", environ['REQUEST_METHOD']) # Run the actual route matching # -- Assignment of environ to config triggers route matching if self.singleton: config = request_config() config.mapper = self.mapper config.environ = environ match = config.mapper_dict route = config.route else: results = self.mapper.routematch(environ=environ) if results: match, route = results[0], results[1] else: match = route = None if old_method: environ['REQUEST_METHOD'] = old_method if not match: match = {} if self.log_debug: urlinfo = "%s %s" % (environ['REQUEST_METHOD'], environ['PATH_INFO']) log.debug("No route matched for %s", urlinfo) elif self.log_debug: urlinfo = "%s %s" % (environ['REQUEST_METHOD'], environ['PATH_INFO']) log.debug("Matched %s", urlinfo) log.debug("Route path: '%s', defaults: %s", route.routepath, route.defaults) log.debug("Match dict: %s", match) url = URLGenerator(self.mapper, environ) environ['wsgiorg.routing_args'] = ((url), match) environ['routes.route'] = route environ['routes.url'] = url if route and route.redirect: route_name = '_redirect_%s' % id(route) location = url(route_name, **match) log.debug("Using redirect route, redirect to '%s' with status" "code: %s", location, route.redirect_status) start_response(route.redirect_status, [('Content-Type', 'text/plain; charset=utf8'), ('Location', location)]) return [] # If the route included a path_info attribute and it should be used to # alter the environ, we'll pull it out if self.path_info and 'path_info' in match: oldpath = environ['PATH_INFO'] newpath = match.get('path_info') or '' environ['PATH_INFO'] = newpath if not environ['PATH_INFO'].startswith('/'): environ['PATH_INFO'] = '/' + environ['PATH_INFO'] environ['SCRIPT_NAME'] += re.sub( r'^(.*?)/' + re.escape(newpath) + '$', r'\1', oldpath) response = self.app(environ, start_response) # Wrapped in try as in rare cases the attribute will be gone already try: del self.mapper.environ except AttributeError: pass return response
代码看起来有点多, 我们只要找关键部分及连接部分。首先这是个可调用的类。在其__call__ 中, 调用其app, 并返回其返回值。
但是这样,肯定是不够的, 因为在之前的步骤中, 我们把所有的路由信息全部交给了它来处理, 也就是说在这里,我们需要找到URL请求中,所对应的controller及action. 仔细查找,可以看到如下代码:
if self.singleton: config = request_config() config.mapper = self.mapper config.environ = environ match = config.mapper_dict route = config.routeelse: results = self.mapper.routematch(environ=environ) if results: match, route = results[0], results[1] else: match = route = None...url = URLGenerator(self.mapper, environ)environ['wsgiorg.routing_args'] = ((url), match)environ['routes.route'] = routeenviron['routes.url'] = url
这里有两种方法来查找路由。在Keystone的实现中,是通过routes.middleware.RoutesMiddleware(self._dispatch,self.map)
来实现的, 所以self.singleton的值为默认值True.
那么这里又通过config = request_config()创建了一个新的对象。继续找到其实现, 在routes 的__init__.py中, 代码如下:
def request_config(original=False): obj = _RequestConfig() try: if obj.request_local and original is False: return getattr(obj, 'request_local')() except AttributeError: obj.request_local = False obj.using_request_local = False return _RequestConfig()
它创建了一个_RequestConfig对象,在同一个文件中, 找到这个类的实现。
class _RequestConfig(object): __shared_state = threading.local() def __getattr__(self, name): return getattr(self.__shared_state, name) def __setattr__(self, name, value): """ If the name is environ, load the wsgi envion with load_wsgi_environ and set the environ """ if name == 'environ': self.load_wsgi_environ(value) return self.__shared_state.__setattr__(name, value) return self.__shared_state.__setattr__(name, value) def __delattr__(self, name): delattr(self.__shared_state, name) def load_wsgi_environ(self, environ): """ Load the protocol/server info from the environ and store it. Also, match the incoming URL if there's already a mapper, and store the resulting match dict in mapper_dict. """ if 'HTTPS' in environ or environ.get('wsgi.url_scheme') == 'https' \ or environ.get('HTTP_X_FORWARDED_PROTO') == 'https': self.__shared_state.protocol = 'https' else: self.__shared_state.protocol = 'http' try: self.mapper.environ = environ except AttributeError: pass # Wrap in try/except as common case is that there is a mapper # attached to self try: if 'PATH_INFO' in environ: mapper = self.mapper path = environ['PATH_INFO'] result = mapper.routematch(path) if result is not None: self.__shared_state.mapper_dict = result[0] self.__shared_state.route = result[1] else: self.__shared_state.mapper_dict = None self.__shared_state.route = None except AttributeError: pass if 'HTTP_X_FORWARDED_HOST' in environ: # Apache will add multiple comma separated values to # X-Forwarded-Host if there are multiple reverse proxies self.__shared_state.host = \ environ['HTTP_X_FORWARDED_HOST'].split(', ', 1)[0] elif 'HTTP_HOST' in environ: self.__shared_state.host = environ['HTTP_HOST'] else: self.__shared_state.host = environ['SERVER_NAME'] if environ['wsgi.url_scheme'] == 'https': if environ['SERVER_PORT'] != '443': self.__shared_state.host += ':' + environ['SERVER_PORT'] else: if environ['SERVER_PORT'] != '80': self.__shared_state.host += ':' + environ['SERVER_PORT']
在这个类的实现中, 可以看到其只是定义了一些方法, 但是并没有任何调用。
但是再回到RoutesMiddleware的实现中,这里给config对象设置了两个值。
config = request_config()config.mapper = self.mapperconfig.environ = environmatch = config.mapper_dictroute = config.route
这个时候再回头看看_RequestConfig的实现, 可以看到在其setattr 方法中,如果属性名为environ, 它就会调用load_wsgi_environ, 然后就可找到如下代码:
mapper = self.mapperpath = environ['PATH_INFO']result = mapper.routematch(path)if result is not None: self.__shared_state.mapper_dict = result[0] self.__shared_state.route = result[1]else: self.__shared_state.mapper_dict = None self.__shared_state.route = None
至此, 可以看到其调用的Mapper对象的routematch方法来查找路由,并将其值放到mapper_dict, route中。最终把它取出来,并放到环境变量中:
match = config.mapper_dictroute = config.routeurl = URLGenerator(self.mapper, environ)environ['wsgiorg.routing_args'] = ((url), match)environ['routes.route'] = routeenviron['routes.url'] = url
现在我们回忆下调用的整体过程:
在服务器收到请求后,交给paste.deployment来处理, 然后paste.deployment调用各个中间件进行处理,直到wsgi.ComposingRouter,
然后调用它。最后会调用Router._dispath方法,在_dispath中,从req.environ[‘wsgiorg.routing_args’][1] 取出match的值,然后取出controller,
app = match[‘controller’]. 这样代码就跑到controller中。
@staticmethod @webob.dec.wsgify(RequestClass=Request) def _dispatch(req): match = req.environ['wsgiorg.routing_args'][1] if not match: return render_exception( exception.NotFound(_('The resource could not be found.')), user_locale=req.best_match_language()) app = match['controller'] return app
到此为止, 代码可以跑到app中, 也就是Ec2Controller, 也可看到Ec2Controller是Application的一个子类, 在Application中,它实现了一个__call__ 方法,也就是说,它也是一个适用WSGI标准的应用程序。它的__call__ 方法定义如下:
def __call__(self, req): arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict.pop('action') del arg_dict['controller'] LOG.debug(_('arg_dict: %s'), arg_dict) # allow middleware up the stack to provide context, params and headers. context = req.environ.get(CONTEXT_ENV, {}) context['query_string'] = dict(req.params.iteritems()) context['headers'] = dict(req.headers.iteritems()) context['path'] = req.environ['PATH_INFO'] params = req.environ.get(PARAMS_ENV, {}) for name in ['REMOTE_USER', 'AUTH_TYPE']: try: context[name] = req.environ[name] except KeyError: try: del context[name] except KeyError: pass params.update(arg_dict) context.setdefault('is_admin', False) # TODO(termie): do some basic normalization on methods method = getattr(self, action) # NOTE(vish): make sure we have no unicode keys for py2.6. params = self._normalize_dict(params) try: result = method(context, **params) except exception.Unauthorized as e: LOG.warning( _('Authorization failed. %(exception)s from %(remote_addr)s') % {'exception': e, 'remote_addr': req.environ['REMOTE_ADDR']}) return render_exception(e, user_locale=req.best_match_language()) except exception.Error as e: LOG.warning(e) return render_exception(e, user_locale=req.best_match_language()) except TypeError as e: LOG.exception(e) return render_exception(exception.ValidationError(e), user_locale=req.best_match_language()) except Exception as e: LOG.exception(e) return render_exception(exception.UnexpectedError(exception=e), user_locale=req.best_match_language()) if result is None: return render_response(status=(204, 'No Content')) elif isinstance(result, basestring): return result elif isinstance(result, webob.Response): return result elif isinstance(result, webob.exc.WSGIHTTPException): return result response_code = self._get_response_code(req) return render_response(body=result, status=response_code)
可以看出, 它用action = arg_dict.pop('action')
,从路由信息中,把action给取了出来,然后通过method = getattr(self, action)
把对应的action作为一个可调用的对象取出来,并且调用它。 result = method(context, **params)
到此, 路由已经成功的跑进了我们想要的地方。
- Openstack Keystone 认证流程(五)--路由
- Openstack Keystone 认证流程(六)--认证
- Openstack Keystone 认证流程(一)--Overview
- Openstack Keystone 认证流程(二)--门
- Openstack Keystone 认证流程(三)-WSGI
- Openstack Keystone 认证流程(八)--总结
- Openstack Keystone 认证流程(四)--Filter流水线
- Openstack Keystone 认证流程(七)--API 及 Driver
- OpenStack点滴积累2--KeyStone的认证流程
- openstack keystone认证过程之一
- 理解OpenStack认证:Keystone PKI
- OpenStack 认证服务 KeyStone [二]
- OpenStack Keystone安装部署流程
- OpenStack Keystone安装部署流程
- Openstack组件部署 — Keystone功能介绍与认证实现流程
- OpenStack Swift源码分析(五)keystone鉴权
- OpenStack Newton版本部署----认证服务(keystone)
- OpenStack实践系列②认证服务Keystone
- java编程思想并发学习笔记(rocket and rockets)
- leetcode_147_Insertion Sort Lis
- 模仿淘宝京东数量选择器
- 设计模式学习笔记七:策略模式
- Vaadin系列(二) 应用开发:Table 组件
- Openstack Keystone 认证流程(五)--路由
- Team Queue UVA 540 queue+map LRJ做法
- 详谈排序算法之插入类排序(两种思路实现希尔排序)
- Java语言基础——第三回 Eclipse使用技巧和包的用途
- linux bio学习总结
- CListCtrl使用技巧
- logstash在windows上fileinput会锁定文件的问题
- NGUI报错小结
- “dos2unix 既不是内部或外部命令,也不是可运行的程序”的解决方法