APIview的請求生命周期源碼分析


APIview的請求生命周期源碼分析

Django項目啟動=>加載settings文件=>加載models、views、urls文件,執行urls文件,調用視圖類的as_view()方法。

APIview的as_view()方法繼承父類的as_view()方法,並增加了局部禁用csrf中間件的功能

 def as_view(cls, **initkwargs):
        """
        Store the original class on the view function.

        This allows us to discover information about the view when we do URL
        reverse lookups.  Used for breadcrumb generation.
        """
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '
                    'as the result will be cached and reused between requests. '
                    'Use `.all()` or call `.get_queryset()` instead.'
                )
            cls.queryset._fetch_all = force_evaluation
        #將父類as_view賦值給自己的view空間,並將其類名與參數添加到自己類的屬性
        view = super().as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        
        #
        #局部禁用csrf認證
        return csrf_exempt(view)

APIview的父類就是Django的視圖類view,as_view()繼承父類的as_view功能調用dispatch方法分發請求,下面是父類的as_view()

 @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        '''略去代碼'''
       ........................................................
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            #分發請求
            return self.dispatch(request, *args, **kwargs)
        # 這里的self是視圖類實例化的對象,不要搞錯了
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

視圖類實例化的對象在自己的名稱空間找dispatch方法,如果沒有就去基類APIview里面查找,APIview的dispatch方法是對view類的dispatch方法的重寫,對view類的dispatch方法進行了優化,具體優化一起來看APIview的dispatch方法源碼:

 def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        # initialize_request二次封裝request對象,並對request對象的內容進行解析
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            #三大認證(認證、權限、頻率),用來替換csrf安全認證,要比csrf認證強大得多
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
                #如果wsgi的request里面有對應的方法,就用wsgi的,如果沒有就用自己的,源碼中常用的方法
            else:
                handler = self.http_method_not_allowed
                #如果自己也沒有就報http_method_not_allowed異常

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            #異常處理模塊,處理異常分支
            response = self.handle_exception(exc)
		#二次封裝response,處理響應的內容,並對處理的結果進行渲染
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

總結一下重寫的dispatch里面所完成的功能:

1.二次封裝request對象,並對request對象的內容進行解析

2.調用函數initial對請求進行三大認證,並在這個過程中進行異常捕獲

3.通過反射的方法執行通過認證的自定義請求如get、post、patch、delete等

4.如果上面2、3步執行過程中有異常,就調用handle_exception方法處理捕獲到的異常。

5.通過finalize_response方法進行處理響應的內容,以及是否對內容進行渲染

以上就是Django rest framework源碼的請求流程,下面我們粗略看一下請求模塊、解析模塊、相應模塊、異常處理模塊、渲染模塊的源碼。

請求模塊

請求模塊大致的功能如下:

1.將wsgi的request對象轉換成drf的request類的對象

2.封裝后的request對象完全兼容wsgi的request對象,並且將原來request對象保存在新request._request

3.重新格式化請求數據存放位置

拼接參數:request.query_params

數據包參數:request.data

# 源碼分析:
# 入口:APIVIew的dispatch方法的 request=self.initialize_request(request, *args, **kwargs)
# print(request._request.method)  # 在內部將wsgi的request賦值給request._request

class Request:
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.

    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers_classes(list/tuple). The parsers to use for parsing the
          request content.
        - authentication_classes(list/tuple). The authentications used to try
          authenticating the request's user.
    """

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
            #將wsgi的request類的名稱空間全部存入自己的名稱空間中達到對wsgi的request完全兼容。
        )

        self._request = request
        #將父類的request存放在了自己的_request中,這樣我們可以通過對象點屬性的方法方法wsgi request的屬性和方法也可以通過對象點_request直接操作wsgi request對象。
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

    def _default_negotiator(self):
        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()

    @property
    def content_type(self):
        meta = self._request.META
        return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
    
     @property
    def query_params(self):
        """
        More semantically correct name for request.GET.
        """
        #對_request.GET屬性重新命名為query_params
        return self._request.GET

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
            #對_full_data進行重新命名
        return self._full_data
    
    
     def __getattr__(self, attr):
        """
        If an attribute does not exist on this instance, then we also attempt
        to proxy it to the underlying HttpRequest object.
        """
        try:#通過__getattr__的方法獲得_request的屬性和方法
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)


解析模塊

解析模塊只處理數據包參數

# 源碼分析:
# 入口:APIVIew的dispatch方法的 request=self.initialize_request(request, *args, **kwargs)
# 獲取解析類:parsers=self.get_parsers(),
# 進行局部全局默認配置查找順序進行查找:return [parser() for parser in self.parser_classes]
    def get_parsers(self):
        """
        Instantiates and returns the list of parsers that this view can use.
        """
        return [parser() for parser in self.parser_classes]
    #我們點擊parser_classes就會到達self.parser_classes屬性,
class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES#這里是可以看出解析器在api_settings配置里配置
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    
    #在settings里面可以看到默認的解析器配置如下,也就是默認的解析器支持的數據類型有form-data,urlencoded,json
    DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',#json
        'rest_framework.parsers.FormParser',#urlencoded
        'rest_framework.parsers.MultiPartParser'#文件 form-data
    ],
   #這里是全局配置,我們可以在項目的settings文件中自定義配置我們使用的解析器

全局配置解析器

當我們將drf settings文件中進行如下配置后再啟動項目就會優先使用我們自己的配置。

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',#json
        'rest_framework.parsers.FormParser',#urlencoded
        'rest_framework.parsers.MultiPartParser'#文件 form-data
    ]
}

局部配置解析器

我們還可以直接將解析器導入到自己的視圖類中,直接使用這時會優先使用自己類中的parser_classes

from rest_framework.views import APIView
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
class Book(APIView):
    parser_classes = [JSONParser,FormParser,MultiPartParser]

綜上我們可以知道 解析器配置的查找順序:局部(視圖類的類屬性) => 全局(settings文件的drf配置) => 默認(drf的默認配置)

響應模塊

 class Response(SimpleTemplateResponse):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """

    def __init__(self, data=None, status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):
        """
        Alters the init arguments slightly.
        For example, drop 'template_name', and instead use 'data'.

        Setting 'renderer' and 'media_type' will typically be deferred,
        For example being set automatically by the `APIView`.
        """
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        self.data = data
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in headers.items():
                self[name] = value

    
    @property
    def status_text(self):
        """
        Returns reason text corresponding to our HTTP response status code.
        Provided for convenience.
        """
        return responses.get(self.status_code, '')

    def __getstate__(self):
        """
        Remove attributes from the response that shouldn't be cached.
        """
        state = super().__getstate__()
        for key in (
            'accepted_renderer', 'renderer_context', 'resolver_match',
            'client', 'request', 'json', 'wsgi_request'
        ):
            if key in state:
                del state[key]
        state['_closable_objects'] = []
        return state


# data:響應數據
# status:響應的網絡狀態碼
# -------------------------
# template_name:drf完成前后台不分離返回頁面,但是就不可以返回data(了解)
# headers:響應頭,一般不規定,走默認
# exception:一般異常響應,會將其設置成True,默認False(不設置也沒事)
# content_type:默認就是 application/json,不需要處理

異常處理模塊

 def dispatch(self, request, *args, **kwargs):
        '''
       ...
       '''
        try:
            #三大認證(認證、權限、頻率),用來替換csrf安全認證,要比csrf認證強大得多
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
                #如果wsgi的request里面有對應的方法,就用wsgi的,如果沒有就用自己的,源碼中常用的方法
            else:
                handler = self.http_method_not_allowed
                #如果自己也沒有就報http_method_not_allowed異常

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            #異常處理模塊,處理異常分支
            response = self.handle_exception(exc)
		#二次封裝response,處理響應的內容,並對處理的結果進行渲染
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

從源碼可以看出當請求進入三大認證時就已經引入了異常捕獲的范圍,但是這里的異常不會對於客戶端的異常處理較好,而對於服務端異常就會返回前端一波代碼,我們進入handle_exception,看看異常處理的代碼。

 def handle_exception(self, exc):
        """
        Handle any exception that occurs, by returning an appropriate response,
        or re-raising the error.
        """
        #對認證異常的處理響應頭
        if isinstance(exc, (exceptions.NotAuthenticated,
                            exceptions.AuthenticationFailed)):
            # WWW-Authenticate header for 401 responses, else coerce to 403
            auth_header = self.get_authenticate_header(self.request)

            if auth_header:
                exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN
		#獲取異常處理的函數exception_handler
        exception_handler = self.get_exception_handler()

        context = self.get_exception_handler_context()
        
        #給異常處理提供額外的參數,其實就是獲取拋異常的視圖對象和請求的參數,可看下面get_exception_handler_context的源碼
        response = exception_handler(exc, context)#異常對象、視圖對象和請求的參數

        #默認的exception_handler函數只處理客戶端異常形成的response對象,服務器異常不作處理,返回None
        if response is None:
            #當response為none時交給Django中間件處理
            self.raise_uncaught_exception(exc)

        response.exception = True
        return response
    
    
    #視圖對象和請求的參數
     def get_exception_handler_context(self):
        """
        Returns a dict that is passed through to EXCEPTION_HANDLER,
        as the `context` argument.
        """
        return {
            'view': self,
            'args': getattr(self, 'args', ()),
            'kwargs': getattr(self, 'kwargs', {}),
            'request': getattr(self, 'request', None)
        }

重寫異常處理函數

為了自定義服務器異常時系統所拋的異常的內容,我們需要重寫異常處理函數,步驟:

1.在settings的drf配置中配置EXCEPTION_HANDLER,指向自定義的exception_handler函數

2.drf出現異常會回調exception_handler函數,攜帶異常對象和異常相關信息,在exception_handler函數中完成異常信息的返回以及異常信息的logging日志。

在Django的settings文件中進行配置:

   REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'api.exception_handler.exception_handler'}

在exception_handler文件中重寫exception_handler

# 一定要在settings文件中將異常模塊配置自己的異常處理函數
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response

# 先交個drf處理客戶端異常,如果結果response為None代表服務器異常,自己處理
# 最終一定要在日志文件中記錄異常現象
def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    detail = '%s - %s - %s' % (context.get('view'), context.get('request').method, exc)
    if not response:  # 服務端錯誤
        response =  Response({'detail': detail})
    else:
        response.data = {'detail': detail}

    # 核心:要將response.data.get('detail')信息記錄到日志文件
    # logger.waring(response.data.get('detail'))

    return response

渲染模塊

渲染模塊在APIView中的導入方式renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES,它的作用是Postman請求返回結果是json,瀏覽器請求結果是經過渲染的頁面,實際項目中應用場景不大可以像解析模塊一樣進行局部和全局配置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM