本帖最后由 坎蒂丝_Swan 于 2014-12-6 19:32 编辑
问题导读
问题1:什么是webob?有什么功能?发挥功能时有什么优势? 问题2:Resource这个类发挥了什么作用?
Glance源码架构探秘(一)
Glance源码架构探秘(二) Glance源码架构探秘(三)
Glance源码架构探秘(四)
上一章我们分析了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'][1]
- if not match:
- return webob.exc.HTTPNotFound()
- app = match['controller']
- return app
复制代码
这个基类的工厂方法被包装为了类方法,实际就会创建刚才的API子类。此类的可调用对象方法__call__被包装成了wsgi,所以这个类的对象可以作为WSGI函数进行调用。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'][1].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在接到请求命令后,如何进行镜像的上传,下载,更新等,同样包括数据库和镜像元数据更新等的操作,其中用到了许多非常棒的设计模式,敬请期待!
|