1. 程式人生 > >Nova API服務 之 處理HTTP請求的流程

Nova API服務 之 處理HTTP請求的流程

一、回顧

1、Nova API服務的主要功能都在osapi_compute_app_v2應用程式中實現。osapi_compute_app_v2應用程式對應的工

方法是APIRouter類的factory方法。APIRouter類的factory方法會返回一個APIRouter物件。當客戶端傳送HTTP請

求的時候,會呼叫APIRouter物件的__call__方法。

2、APIRouter繼承自Router類,__call__方法便定義在Router類中。Router類的__call__方法的定義是self.router

象,最終會返回資源對應的Controller物件。

3、servers資源的Controller物件是一個wsgi.Resource物件。因此。當客戶端傳送HTTP請求後,Nova API服務最終

會呼叫wsgi.Resource物件的__call__方法。 /nova/api/openstack/wsgi.py

def create_resource(ext_mgr):
    return wsgi.Resource(Controller(ext_mgr))

二、wsgi.Resource類的初始化方法和__call__方法的定義

1、初始化方法

wsgi.Resource類的初始化方法定義如下:

def __init__(self, controller, action_peek = None, inherits = None,
	#底層Controller物件
	self.controller = controller
    # 反序列化物件
	default_deserializers = dict(json = JSONDeserializer)
	default_deserializers.update(deserializers)

	self.default_deserializers = default_deserializers
	# 序列化物件
	self.default_serializers = dict(json = JSONDictSerializer)
	......
 Resource類是在資源的底層Controller類的基礎上,添加了序列化和反序列化的功能。

序列化:是指將XML或JSON格式的資料轉化成字串格式,以便於在網路間傳輸

反序列化:將字串數格式的資料轉換為XML或JSON資料格式,以便於顯示和處理。 所以,在Resource類中通過兩個

成員變數default_deserializers和default_serializers來分別實現資料的反序列化和序列化。 

2、__call__方法

Resource類的__call__方法定義如下:

@webob.dec.wsgify(RequestClass=Request) //wsgify裝飾器
def __call__(self, request):


    # Identify the action, its arguments, and the requested
    # content type
	#獲取客戶端傳入的HTTP請求引數
    action_args = self.get_action_args(request.environ)
	#獲取HTTP請求的操作名:post put delete get
    action = action_args.pop('action', None)
	#獲取客戶端傳入的報文
	#content_type:客戶端傳入的報文格式  body:客戶端傳入的報文內容
    content_type, body = self.get_body(request)
	#獲取伺服器返回的報文型別
    accept = request.best_match_content_type()

	#處理HTTP請求
    return self._process_stack(request, action, action_args,
                               content_type, body, accept)
可以看出,__call__方法最後呼叫了_process_stack方法。

_process_stack方法的定義如下:

def _process_stack(self, request, action, action_args,
                       content_type, body, accept):

        #獲取處理HTTP請求的方法
        try:
            meth, extensions = self.get_method(request, action,
                                               content_type, body)
        except (AttributeError, TypeError):
            return Fault(webob.exc.HTTPNotFound())
        except KeyError as ex:
            msg = _("There is no such action: %s") % ex.args[0]
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))
        except exception.MalformedRequestBody:
            msg = _("Malformed request body")
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))
		
        if body:
            msg = _("Action: '%(action)s', calling method: %(meth)s, body: "
                    "%(body)s") % {'action': action,
                                   'body': six.text_type(body, 'utf-8'),
                                   'meth': str(meth)}
            LOG.debug(strutils.mask_password(msg))
        else:
            LOG.debug("Calling method '%(meth)s'",
                      {'meth': str(meth)})

        #反序列化客戶端傳入的訊息
        try:
            contents = {}
            if self._should_have_body(request): #如果傳入客戶端訊息
                # allow empty body with PUT and POST
                if request.content_length == 0:
                    contents = {'body': None}
                else:
                    contents = self.deserialize(meth, content_type, body)
        except exception.InvalidContentType:
            msg = _("Unsupported Content-Type")
            return Fault(webob.exc.HTTPUnsupportedMediaType(explanation=msg))
        except exception.MalformedRequestBody:
            msg = _("Malformed request body")
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))

        #更新請求的引數,將傳入的訊息體內容新增到action_args中
        action_args.update(contents)
		#獲取客戶端所屬的專案ID
        project_id = action_args.pop("project_id", None)
        context = request.environ.get('nova.context')
		#檢查客戶端請求是否合法
        if (context and project_id and (project_id != context.project_id)):
            msg = _("Malformed request URL: URL's project_id '%(project_id)s'"
                    " doesn't match Context's project_id"
                    " '%(context_project_id)s'") % \
                    {'project_id': project_id,
                     'context_project_id': context.project_id}
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))

        #執行HTTP請求的前向擴充套件方法
        response, post = self.pre_process_extensions(extensions,
                                                     request, action_args)
		#前向擴充套件方法沒有返回response,說明需要對請求進行進一步處理
        if not response:
            try:
                with ResourceExceptionHandler():
					#執行底層Controller物件中 處理HTTP請求 的方法
                    action_result = self.dispatch(meth, request, action_args)
            except Fault as ex:
                response = ex
		#前向擴充套件方法沒有返回response,處理底層controller物件 方法返回的結果
        if not response:
            resp_obj = None
			#如果controller物件方法返回結果為字典,則封裝成ResponseObject物件
            if type(action_result) is dict or action_result is None:
                resp_obj = ResponseObject(action_result)
			#如果controller物件方法返回結果為ResponseObject物件
            elif isinstance(action_result, ResponseObject):
                resp_obj = action_result
			#否則認為返回的結果是response物件
            else:
                response = action_result

            #如果controller物件方法沒有返回response物件,則繼續處理resp_obj物件
            if resp_obj:
                #獲取controller物件方法 指定的序列化物件
                serializers = getattr(meth, 'wsgi_serializers', {})
				#繫結序列化物件
                resp_obj._bind_method_serializers(serializers)
				#獲取controller物件方法預設的HTTP code 
                if hasattr(meth, 'wsgi_code'):
                    resp_obj._default_code = meth.wsgi_code
				#獲取accept報文格式下的序列化方法
				#如果controller物件方法未指定序列化方法,則使用預設的序列化方法
                resp_obj.preserialize(accept, self.default_serializers)

                #執行HTTP請求的後向擴充套件方法
                response = self.post_process_extensions(post, resp_obj,
                                                        request, action_args)
			#如果後向方法沒有返回response物件
            if resp_obj and not response:
				#將controller物件方法返回結果 序列化
                response = resp_obj.serialize(request, accept,
                                              self.default_serializers)

        if hasattr(response, 'headers'):

            for hdr, val in response.headers.items():
                # Headers must be utf-8 strings
                response.headers[hdr] = utils.utf8(str(val))

            if not request.api_version_request.is_null():
                response.headers[API_VERSION_REQUEST_HEADER] = \
                    request.api_version_request.get_string()
                response.headers['Vary'] = API_VERSION_REQUEST_HEADER

        return response
(1)、獲取HTTP請求的引數,呼叫deserialize方法將 HTTP請求的訊息體反序列化成 字典物件。並且通過檢查專案ID

來驗證客戶是否有執行HTTP請求的許可權。

(2)、呼叫pre_process_extensions方法執行HTTP請求的前向擴充套件方法。前向擴充套件方法是為了便於二次開發預留的接

口。在Nova API處理HTTP請求的時候,會首先執行前向擴充套件方法,然後再執行底層Controller物件中的處理方法。

pre_process_extensions方法返回一個post物件,它是HTTP請求後向擴充套件方法的列表。後向擴充套件方法也是預留的二次

開發介面,會在底層的Controller物件的處理方法執行之後執行。

(3)、通過呼叫dispatch方法執行底層Controller物件的處理方法。底層Controller物件處理方法返回一個字典。

resp_obj = ResponseObject(action_result)將返回的結果封裝成ResponseObject物件。

(4)、對ResponseObject物件進行一些配置,需要配置的屬性有:序列化物件、預設的HTTP CODE。

(5)、ResponseObject物件的序列化物件和HTTP Code是在底層Controller物件的處理方法中指定。底層Controller對

象的每個處理方法都可以通過裝飾器指定序列化物件、反序列化物件和HTTP Code。例如:


指定了index方法的XML序列化物件為MinimalServersTemplate物件,如下程式碼片段:


指定了create方法的XML序列化物件為 FullServersTemplate物件,xml反序列化物件為CreateDeserializer,預設的

HTTP Code為202。當HTTP請求處理成功時,Nova API伺服器會向客戶端返回202 的HTTP Code。

(6)、HTTP請求的後向擴充套件方法,將ResponseObject物件序列化。

總結:由於前向擴充套件方法和後向擴充套件方法在Nova中沒有使用過,因此HTTP請求的核心工作依然在底層Controller類的

處理方法中定義。_process_stack方法主要是完成了資料的序列化和反序列化工作