Glance源码架构分析(三)
本帖最后由 w123aw 于 2014-11-23 13:09 编辑问题导读
1.如何用webob的Request生成一个request请求和解析一个request请求的环境信息?
2.routes.middleware的作用是什么?
static/image/hrline/4.gif
上一章我们分析了OpenStack中如何用eventlet库创建一个“绿化”的协程式web server,以及WSGI相关概念及在Python中的参考实现,本章我们将具体地讲解WSGI程序内部的框架。
其实,简单的说,Glance及其他OpenStack的组件和大多数的web service一样,也是实现了一个MVC的框架的(关于MVC框架的概念,并不是本文的重点,可自行展开阅读),这个框架称之为RackSpace框架。包括消息路由,控制器Controller、action等等。
这个框架并非完全自己实现,而是借用了许多开源的组件,比如消息路由就用到了Routes这个组件。Routes是借鉴了ruby on rails的某些思想后在Python上的一个实现,用于路由URL消息至对应的控制器和动作action,也可以用来生成URLs。
Routes用法非常清晰明了
# Setup a mapper
from routes import Mapper
map = Mapper()
map.connect(None, "/error/{action}/{id}, controller="error")
map.connect("home", "/", controller="main", action="index")
# Match a URL, returns a dict or None if no match
result = map.match('/error/myapp/4')
# result == {'controller': 'main', 'action': 'myapp', 'id': '4'}
导入routes,创建一个Mapper实例。之后,我们就可以用Mapper.connect()绑定路由。最后用Mapper.match()方法可以将给定的URL转换为路由信息,包括controller,action或其他用户自定义的路由类型。
在我们正式讲解Glance的代码之前,还要给大家介绍其WSGI中用到的另外一个开源组件webob。这也是我常用以及非常喜欢的一个开源组件,使用webob可以大幅简化编写框架的复杂度和代码量。webob用来包装WSGI应用程序的请求以及响应的类库。
>>> from webob import Request
>>> environ = {'wsgi.url_scheme': 'http', ...}
>>> req = Request(environ)
>>> req = Request.blank('/article?id=1')
>>> from pprint import pprint
>>> pprint(req.environ)
{'HTTP_HOST': 'localhost:80',
'PATH_INFO': '/article',
'QUERY_STRING': 'id=1',
'REQUEST_METHOD': 'GET',
'SCRIPT_NAME': '',
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
'SERVER_PROTOCOL': 'HTTP/1.0',
'wsgi.errors': <open file '<stderr>', mode 'w' at ...>,
'wsgi.input': <...IO... object at ...>,
'wsgi.multiprocess': False,
'wsgi.multithread': False,
'wsgi.run_once': False,
'wsgi.url_scheme': 'http',
'wsgi.version': (1, 0)}
上面的示例程序就简单说明了如何用webob的Request生成一个request请求和解析一个request请求的环境信息。
然而,webob的功能不止于此。其还有一个功能就是将一个普通的函数function包装为一个WSGI函数,并符合WSGI函数的规范(实现规范的接口,evn,start_response)。webob.dec.wsgify
@wsgify
def myfunc(req):
return webob.Response('hey there')
如上示例,就将myfunc包装为了一个可被webserver调用的wsgi函数。如果我们不用这个包装,就要非常繁琐的写为
def myfunc(req, start_response):
start_response('200 OK', header)
return ['hey there']
另外,用webob.exc通过生成Python异常的方式,生成HTTP的一些错误响应,如404,500等等。
下面我们看Glance的源码节选分析
我们先来看router.py这个文件。大家应该还记得,在上一章我们启动http server中填入的WSGI function是从glance-api-paste.ini中读取的,这个app正是glance.api.v2.router.API.factory。
class API(wsgi.Router):
"""WSGI router for Glance v2 API requests."""
def __init__(self, mapper):
custom_image_properties = images.load_custom_properties()
schemas_resource = schemas.create_resource(custom_image_properties)
mapper.connect('/schemas/image',
controller=schemas_resource,
action='image',
conditions={'method': ['GET']})
mapper.connect('/schemas/images',
controller=schemas_resource,
action='images',
conditions={'method': ['GET']})
images_resource = images.create_resource(custom_image_properties)
mapper.connect('/images',
controller=images_resource,
action='index',
conditions={'method': ['GET']})
mapper.connect('/images',
controller=images_resource,
action='create',
conditions={'method': ['POST']})
mapper.connect('/images/{image_id}',
controller=images_resource,
action='update',
conditions={'method': ['PATCH']})
mapper.connect('/images/{image_id}',
controller=images_resource,
action='show',
conditions={'method': ['GET']})
mapper.connect('/images/{image_id}',
controller=images_resource,
action='delete',
conditions={'method': ['DELETE']})
......
......
super(API, self).__init__(mapper)
上面我们已经介绍过了routes的mapper类,这里的路由分配设置就一目了然了。因为是使用的RESTful接口,路由分配的时候还可以指定method。API这个类的基类是wsgi.Router,刚刚WSGI也是调用的其基类的工厂方法router.factory。这样我们继续介绍上一章没有介绍玩的wsgi.py的后半部分。
class Router(object):
"""
WSGI middleware that maps incoming requests to WSGI apps.
"""
def __init__(self, mapper):
"""
Create a router for the given routes.Mapper.
Each route in `mapper` must specify a 'controller', which is a
WSGI app to call.You'll probably want to specify an 'action' as
well and have your controller be a wsgi.Controller, who will route
the request to the action method.
Examples:
mapper = routes.Mapper()
sc = ServerController()
# Explicit mapping of one route to a controller+action
mapper.connect(None, "/svrlist", controller=sc, action="list")
# Actions are all implicitly defined
mapper.resource("server", "servers", controller=sc)
# Pointing to an arbitrary WSGI app.You can specify the
# {path_info:.*} parameter so the target app can be handed just that
# section of the URL.
mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp())
"""
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
self.map)
@classmethod
def factory(cls, global_conf, **local_conf):
return cls(routes.Mapper())
@webob.dec.wsgify
def __call__(self, req):
"""
Route the incoming request to a controller based on self.map.
If no match, return a 404.
"""
return self._router
@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
or the routed WSGI app's response.
"""
match = req.environ['wsgiorg.routing_args']
if not match:
return webob.exc.HTTPNotFound()
app = match['controller']
return app
这个基类的工厂方法被包装为了类方法,实际就会创建刚才的API子类。此类的可调用对象方法__call__被包装成了wsgi,所以这个类的对象可以作为WSGI函数进行调用。该思想可参见我的另一篇文章Python可调用对象__call__方法的用法分析。routes.middleware是routes组件中另外一个重要的模块,他可以从上层wsgi程序中接收request请求的URL,并自动调用map.match()方法,将URL进行消息路由,并返回路由的结果。路由结果将会被存入request请求的环境变量['wsgiorg.routing_args']中。最后会调用其第一个参数给出的函数接口,继续下一级的WSGI调用,代码中就是self._dispatch。
这层的dispatch分发会从路由结果中找到是哪个controller,然后从router.py中查找并创建controller对应的resource,分析其创建过程,实质上最后还是会在wsgi.py中创建一个resource基类,所以我们继续看wsgi.py的最后一部分。
class Resource(object):
"""
WSGI app that handles (de)serialization and controller dispatch.
Reads routing information supplied by RoutesMiddleware and calls
the requested action method upon its deserializer, controller,
and serializer. Those three objects may implement any of the basic
controller action methods (create, update, show, index, delete)
along with any that may be specified in the api router. A 'default'
method may also be implemented to be used in place of any
non-implemented actions. Deserializer methods must accept a request
argument and return a dictionary. Controller methods must accept a
request argument. Additionally, they must also accept keyword
arguments that represent the keys returned by the Deserializer. They
may raise a webob.exc exception or return a dict, which will be
serialized by requested content type.
"""
def __init__(self, controller, deserializer=None, serializer=None):
"""
:param controller: object that implement methods created by routes lib
:param deserializer: object that supports webob request deserialization
through controller-like actions
:param serializer: object that supports webob response serialization
through controller-like actions
"""
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)
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)
return response
# return unserializable result (typically a webob exc)
except Exception:
return action_result
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')
return method(*args, **kwargs)
def get_action_args(self, request_environment):
"""Parse dictionary created by routes library."""
try:
args = request_environment['wsgiorg.routing_args'].copy()
except Exception:
return {}
try:
del args['controller']
except KeyError:
pass
try:
del args['format']
except KeyError:
pass
return args
Resource这个类可以对请求解串行化,或串行化响应信息,并可以分发controller控制器action的方法。这个类的初始化方法会给对象添加三个属性,controller、serializer、deserializer,分别为控制器,串行化,逆串行化。对象函数__call__也被装饰为WSGI函数,接受上一级WSGI函数的请求,并将上一级Routes后的路由信息(controller=?action=?)通过dispatch方法将指定的action分发到controller控制器类所对应的方法,在源码中用到了getattr,动态的获得对象的绑定方法。
通过上述一系列步骤,就将glance命令请求消息经过路由、分发等步骤送到了控制器类所对应的action方法中,让glance得以进一步的执行客户发来的请求命令。前面通过三章的篇幅,为大家讲解了glance/common/wsgi.py这个文件,主要实现了一个web框架的大部分功能。因为OpenStack的组件都是基于同一套rackspace框架的,这对于我们今后学习Nova,Cinder等其他OpenStack都是颇有益处的。所以今后如有Nova等组件的源码探秘,此部分也不再会单独分析,将把经历多投入到其他方面。
后面几章将开始具体分析glance在接到请求命令后,如何进行镜像的上传,下载,更新等,同样包括数据库和镜像元数据更新等的操作,其中用到了许多非常棒的设计模式,敬请期待!
相关文章:
Glance源码架构分析(一)
http://www.aboutyun.com/thread-10237-1-1.html
Glance源码架构分析(二)
http://www.aboutyun.com/thread-10238-1-1.html
Glance源码架构分析(四)
http://www.aboutyun.com/thread-10240-1-1.html
作者:Ethan_熠森张
页:
[1]