感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如果转载,请保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
邮箱地址:dong.liu@siat.ac.cn
从这篇博客开始,我将以cinder中卷的建立的过程的源码解析为例,来说明客户端传递过来的request的执行过程。
示例:
我们执行命令:cinder create --display_name shinian01 1
便可获取响应信息:
+---------------------+--------------------------------------+
| Property | Value |
+---------------------+--------------------------------------+
| attachments | [] |
| availability_zone | nova |
| bootable | false |
| created_at | 2014-03-29T12:36:23.998481 |
| display_description | None |
| display_name | shinian01 |
| id | 3f0aa242-9dab-48db-b63d-92b6dd38cf20 |
| metadata | {} |
| size | 1 |
| snapshot_id | None |
| source_volid | None |
| status | creating |
| volume_type | None |
+---------------------+--------------------------------------+
实际上,cinder模块中实现命令行指定操作主要有以下几个步骤:
1.应用若干中间件对客户端发送过来的请求信息进行过滤(封装)处理;
2.获取要执行的方法及其相关的扩展方法;
3.请求中body部分的反序列化;
4.获取的扩展方法的执行;
5.执行具体的方法,如卷的建立方法create;
6.响应信息的生成;
下面我们来进行逐条分析:
1.应用若干中间件对客户端发送过来的请求信息进行过滤(封装)处理;
当请求信息过来以后,会先后调用以下类中的__call__方法对其进行处理:
FaultWrapper->RequestBodySizeLimiter->CinderKeystoneContext->APIRouter->Resource
其中:
FaultWrapper:关于错误异常的处理;
RequestBodySizeLimiter:请求信息中body部分长度的检测;
CinderKeystoneContext:身份验证相关;
APIRouter:请求信息的路由处理;
Resource:action执行前的一些预处理;
这五个类中的__call__方法都是以语句@webob.dec.wsgify(RequestClass=Request)实现装饰的,其中前三个类为cinder定义的中间件,这里不进行深入的分析。我们具体来看APIRouter和Resource。
1.1 class APIRouter----def __call__
这里调用的是类APIRouter的父类class Router中的__call__方法,我们来看具体代码:
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 an object that can route
the request to the action-specific 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)
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
"""Route the incoming request to a controller based on self.map.
If no match, return a 404.
"""
return self._router
我们可以看到在
__call__方法
中,返回了变量self._router,而可以得到:
self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)
这就是我要说的重点,在前面的博客中,我们分析了cinder API的路由表形成过程,这条语句实现的功能就是根据之前形成的API路由表,实现请求信息的映射和匹配,得到实现这个请求所要执行的具体action的方法。比如上面例子,在这里就可以匹配到具体方法create。
1.2 classResource----def __call__
我们上面说过这几个中间件的__call__方法,都通过装饰器@webob.dec.wsgify(RequestClass=Request)进行了封装,我们以classResource----def __call__为例,来看看这一过程是怎么实现的,来看方法/cinder/api/openstack/wsgi.py----class Resource(wsgi.Application)----def __call__(self, request)的源码:
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, request):
"""
WSGI method that controls (de)serialization and method dispatch.
"""
LOG.info("%(method)s %(url)s" % {"method": request.method,
"url": request.url})
# Identify the action, its arguments, and the requested
# content type
action_args = self.get_action_args(request.environ)
# 输出:action_args = {'action': u'create', 'project_id': u'ecf0109bda814fa1a548af63f9ada370'}
action = action_args.pop('action', None)
# 输出:action = create
# get_body:通过检测header,确定request body的内容类型;获取request中的body部分;
content_type, body = self.get_body(request)
# 输出:content_type = application/json
# 输出:body = {"volume": {"status": "creating", "availability_zone": null, "source_volid": null,
# "display_description": null, "snapshot_id": null, "user_id": null, "size": 1,
# "display_name": "shinian01", "imageRef": null, "attach_status": "detached",
# "volume_type": null, "project_id": null, "metadata": {}}}
# best_match_content_type:确定请求响应的内容类型;
accept = request.best_match_content_type()
# 输出:accept = application/json
#request = POST /v1/ecf0109bda814fa1a548af63f9ada370/volumes HTTP/1.0
......
#action = create
#action_args = {'project_id': u'ecf0109bda814fa1a548af63f9ada370'}
#content_type = application/json
#body = {"volume": {"status": "creating",
# "availability_zone": null,
# "source_volid": null,
# "display_description": null,
# "snapshot_id": null,
# "user_id": null,
# "size": 1,
# "display_name": "shinian01",
# "imageRef": null,
# "attach_status": "detached",
# "volume_type": null,
# "project_id": null,
# "metadata": {}}}
#accept = application/json
return self._process_stack(request, action, action_args,
content_type, body, accept)
来看/webob/dec.py----class wsgify(object)----def __call__(self, req, *args, **kw)的源码实现:
def __call__(self, req, *args, **kw):
"""
Call this as a WSGI application or with a request
req = {'HTTP_X_TENANT_NAME': u'admin', 'routes.route': <routes.route.Route object at 0x240b090>, 'HTTP_X_ROLE': u'_member_,admin', 'HTTP_X_USER_NAME': u'admin', 'SCRIPT_NAME': '/v1', 'webob.adhoc_attrs': {'response': <Response at 0x257e3d0 200 OK>}, 'REQUEST_METHOD': 'POST', 'PATH_INFO': '/ecf0109bda814fa1a548af63f9ada370/volumes', ......}
args = (<function start_response at 0x2552050>,)
kw = {}
"""
func = self.func
#输出示例:func = <bound method Resource.__call__ of <cinder.api.openstack.wsgi.Resource object at 0x240b4d0>>
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]
#输出示例:start_response = args[0] = <function start_response at 0x2552050>
#self.RequestClass = <class 'cinder.api.openstack.wsgi.Request'>
#获取类Request的初始化对象req = Request(req),形成正式的req;
req = self.RequestClass(environ)
#获取响应信息格式;
req.response = req.ResponseClass()
try:
args = self.args
#args = self.args = ()
#self.middleware_wraps = None
if self.middleware_wraps:
args = (self.middleware_wraps,) + args
# 这里调用指定中间件的__call__方法;
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)
return resp(environ, start_response)
else:
if self.middleware_wraps:
args = (self.middleware_wraps,) + args
return self.func(req, *args, **kw)
总体来讲,这个方法主要实现了对请求信息req和执行请求之后的响应信息进行了一些格式和内容上的处理操作。这里有一条比较重要的语句:
req = self.RequestClass(environ)
就这里的示例来讲,输出示例为self.RequestClass = <class 'cinder.api.openstack.wsgi.Request'>,所实现的功能就是通过现有的请求信息,对类Request进行实例初始化,形成后面所要应用到的常见格式的req。我们可以看看这里的输出示例:
req = POST /v1/ecf0109bda814fa1a548af63f9ada370/volumes HTTP/1.0
Accept: application/json
Accept-Encoding: gzip, deflate, compress
Content-Length: 294
Content-Type: application/json
Host: 172.21.5.164:8776
User-Agent: python-cinderclient
X-Auth-Project-Id: admin
X-Auth-Token: MIIQKQYJKoZIhvc......
X-Domain-Id: None
X-Domain-Name: None
X-Identity-Status: Confirmed
X-Project-Domain-Id: None
X-Project-Domain-Name: None
X-Project-Id: ecf0109bda814fa1a548af63f9ada370
X-Project-Name: admin
X-Role: _member_,admin
X-Roles: _member_,admin
X-Service-Catalog: [{"endpoints_links": [], ......]
X-Tenant: admin
X-Tenant-Id: ecf0109bda814fa1a548af63f9ada370
X-Tenant-Name: admin
X-User: admin
X-User-Domain-Id: None
X-User-Domain-Name: None
X-User-Id: d2ee2dd06c9e49098a3d2a278c650b6c
X-User-Name: admin
{"volume": {"status": "creating",
"availability_zone": null,
"source_volid": null,
"display_description": null,
"snapshot_id": null,
"user_id": null,
"size": 1,
"display_name": "shinian01",
"imageRef": null,
"attach_status": "detached",
"volume_type": null,
"project_id": null,
"metadata": {}}}
再来看语句req.response = req.ResponseClass(),这条语句在这里实现的就是形成响应信息的初步格式,来看看输出示例:
req.response = 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 0
再来看语句resp = self.call_func(req, *args, **self.kwargs),这条语句实现的就是调用上面提到的方法
/cinder/api/openstack/wsgi.py----class Resource(wsgi.Application)----def __call__(self, request)。来看方法call_func的实现:
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)
这里的self.func的值在类
class wsgify(object)的初始化方法中已经进行了赋值,此处self.func = <bound method Resource.__call__ of <cinder.api.openstack.wsgi.Resource object at 0x240b4d0>>。待到方法class Resource(wsgi.Application)----def __call__(self, request)执行过后,会回到方法/webob/dec.py----class wsgify(object)----def __call__(self, req, *args, **kw)中继续后面代码的执行,其具体功能也是对获取到的响应信息进行若干格式化上的转换和处理。
现在,应用若干中间件对客户端发送过来的请求信息进行过滤(封装)处理部分简单的解析完成,下一篇博客中会具体解析方法class Resource(wsgi.Application)----def __call__(self, request),具体深入解析cinder建立volume的过程。
好困啊又感冒了,明后天我会继续这部分博客的撰写,谢谢大家批评指正!