glance中有多个app,每个app的具体功能都有所差别,但是实现方式,使用的技术基本都是一样的,本文就以apiv2app为例来分析,其中的实现,及使用到的技术。
通过之前http://blog.csdn.net/litianze99/article/details/52120749的分析可以知道,app的主要实现在factory创建的可执行对象中:
[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory
该factory创建的可执行对象到底是什么呢?下面是glance.api.v2.router:API类的实现部分:
...
from glance.common import wsgi
class API(wsgi.Router):
"""WSGI router for Glance v2 API requests."""
def __init__(self, mapper):
#加载自定义json文件 schema-image.json
custom_image_properties = images.load_custom_properties()
#创建controller
reject_method_resource = wsgi.Resource(wsgi.RejectMethodController())
#创建controller
schemas_resource = schemas.create_resource(custom_image_properties)
#注册路由规则
mapper.connect('/schemas/image',
controller=schemas_resource,
action='image',
conditions={'method': ['GET']},
body_reject=True)
...
由类的注释可以大题知道该类是为请求提供路由功能的。该类没有实现factory函数,但是他的父类glance.common.wsgi.Router实现了该方法。父类的部分代码:
class Router(object): """ WSGI middleware that maps incoming requests to WSGI app ... @classmethod def factory(cls, global_conf, **local_conf): return cls(APIMapper())
有factory的类方法可以知道该facotory返回的是一个自身的实例,且实例化的参数是APIMapper,
下面是APIMapper的定义:
class APIMapper(routes.Mapper):
""" Handle route matching when url is '' because routes.Mapper returns an error in this case. """
def routematch(self, url=None, environ=None):
if url is "":
result = self._match("", environ)
return result[0], result[1]
return routes.Mapper.routematch(self, url, environ)
APIMapper继承于routes.Mapper类。由此可以看出该app使用了routes基础库来提供对请求的路由功能,即:将url映射到handler。
下面我们就深入分析它怎么实现路由的:
分两步来分析:
app对请求的处理过程。
app的初始化
glance.api.v2.router:API.factory创建了API实例,参数为APIMapper实例对象,API类继承了glance.common.wsgi.Router类,APIMapper类继承了routes.Mapper类。
首先看一下APIMapper类的实例化:
该类只实现了routematch 方法,初始化过程在routes.Mapper中实现,这里不赘述,在介绍routes时在详细介绍。这里只需要记住APIMapper是routes.Mapper的子类。
API的实例化:
在API类的__init__(…)中主要是mapper的初始化工作。
wsgi.Router类的__init__(…)主要是如下:
mapper.redirect("", "/")
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
self.map)
初始化map、_router属性。
app对请求的处理过程:
#类 Router
@webob.dec.wsgify
def __call__(self, req):
""" Route the incoming request to a controller based on self.map. If no match, return either a 404(Not Found) or 501(Not Implemented). """
return self._router
#类 webob.dec.wsgify
def __call__(self, req, *args, **kw):
"""Call this as a WSGI application or with a request"""
func = self.func
if func is None:
if args or kw:
raise TypeError(
"Unbound %s can only be called with the function it "
"will wrap" % self.__class__.__name__)
func = req
return self.clone(func)
if isinstance(req, dict):
if len(args) != 1 or kw:
raise TypeError(
"Calling %r as a WSGI app with the wrong signature")
environ = req
start_response = args[0]
req = self.RequestClass(environ)
req.response = req.ResponseClass()
try:
args = self.args
if self.middleware_wraps:
args = (self.middleware_wraps,) + args
resp = self.call_func(req, *args, **self.kwargs)
except HTTPException as exc:
resp = exc
if resp is None:
## FIXME: I'm not sure what this should be?
resp = req.response
if isinstance(resp, text_type):
resp = bytes_(resp, req.charset)
if isinstance(resp, bytes):
body = resp
resp = req.response
resp.write(body)
if resp is not req.response:
resp = req.response.merge_cookies(resp)
#self._router
return resp(environ, start_response)
else:
if self.middleware_wraps:
args = (self.middleware_wraps,) + args
return self.func(req, *args, **kw)
def call_func(self, req, *args, **kwargs):
"""Call the wrapped function; override this in a subclass to change how the function is called."""
return self.func(req, *args, **kwargs)
@staticmethod
@webob.dec.wsgify
def _dispatch(req):
""" Called by self._router after matching the incoming request to a route and putting the information into req.environ. Either returns 404, 501, or the routed WSGI app's response. """
match = req.environ['wsgiorg.routing_args'][1]
if not match:
implemented_http_methods = ['GET', 'HEAD', 'POST', 'PUT',
'DELETE', 'PATCH']
if req.environ['REQUEST_METHOD'] not in implemented_http_methods:
return webob.exc.HTTPNotImplemented()
else:
return webob.exc.HTTPNotFound()
app = match['controller']
return app
由上面的源码可知请求经Router实例处理后,继续有self._router可调用实例继续处理。
self._router是类routes.middleware.RoutesMiddleware(self._dispatch, self.map)
类的实例。RoutesMiddleware部分核心代码如下:
#routes.middleware.RoutesMiddleware
class RoutesMiddleware(object):
def __init__(self, wsgi_app, mapper, use_method_override=True, path_info=True, singleton=True):
self.app = wsgi_app
self.mapper = mapper
self.singleton = singleton
self.use_method_override = use_method_override
self.path_info = path_info
log_debug = 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'])
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 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
class Resource(object):
def __init__(self, controller, deserializer=None, serializer=None):
self.controller = controller
self.serializer = serializer or JSONResponseSerializer()
self.deserializer = deserializer or JSONRequestDeserializer()
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, request):
"""WSGI method that controls (de)serialization and method dispatch."""
action_args = self.get_action_args(request.environ)
action = action_args.pop('action', None)
body_reject = strutils.bool_from_string(
action_args.pop('body_reject', None))
try:
if body_reject and self.deserializer.has_body(request):
msg = _('A body is not expected with this request.')
raise webob.exc.HTTPBadRequest(explanation=msg)
deserialized_request = self.dispatch(self.deserializer,
action, request)
action_args.update(deserialized_request)
action_result = self.dispatch(self.controller, action,
request, **action_args)
...
try:
response = webob.Response(request=request)
self.dispatch(self.serializer, action, response, action_result)
# encode all headers in response to utf-8 to prevent unicode errors
for name, value in list(response.headers.items()):
if six.PY2 and isinstance(value, six.text_type):
response.headers[name] = encodeutils.safe_encode(value)
return response
...
def dispatch(self, obj, action, *args, **kwargs):
"""Find action-specific method on self and call it."""
try:
method = getattr(obj, action)
except AttributeError:
method = getattr(obj, 'default')
#将不同的请求映射到其对应的controller的method上,并调用处理。
return method(*args, **kwargs)
由源代码可以知道最后请求传递给传入的参数self._diaptach 即:wsgi_app 处理,之后有交个controller来处理,controller会根据请求的url,action调用相对应的方法来做最终的处理操作。
基本流程如下:
APP()–@webob.dec.wsgi –__call__()–>routes.middleware.RoutesMiddleware()–__call__()–>
APP().dispatch()–@webob.dec.wsgi–>Controller()–@webob.dec.wsgi–__call_()–>action()。
这里用到了routes很重要的库,但本文不会对其的使用,做详细描述。