Django(47)drf請求生命週期分析

專注的阿熊發表於2021-06-10

前言

  一般我們寫完序列化以後,我們就會開始寫檢視了,drf中我們一般使用CBV的方式,也就是類檢視的方式,最基礎的我們會使用from rest_framework.views import APIViewAPIView繼承自View,關於檢視的詳解,我們後續再細講。本章介紹drf的請求生命週期
 

前置準備工作

我們先寫一個檢視類TestView,程式碼如下:

from rest_framework.views import APIView
from rest_framework.response import Response
class TestView(APIView):
    def get(self, request, *args, **kwargs):
        return Response("drf get ok")
    def post(self, request, *args, **kwargs):
        return Response("drf post ok")

注意:這裡的Response必須是drf下的Response,不能是Django原生的HttpResponse或者是JsonResponse,否則會出錯

接著,在urls.py中配置路由,如下

urlpatterns = [
    path('test/', views.TestView.as_view(), name="Test"),
]

然後我們訪問http://127.0.0.1:8000/drf/test/,會出現下圖樣式,代表請求成功

接著我們在介面工具中使用POST請求方式訪問,返回結果如下:

"drf post ok"

以上2種訪問方式都成功了,接下來我們分析其中的請求過程以及原理
 

請求生命週期分析

首先我們先從路由配置中看到views.TestView.as_view(),呼叫的是TestView類檢視下的as_view方法,但是我們上面定義該方法的時候,沒有重寫as_view()方法,所以會呼叫父類APIView中的as_view方法,原始碼如下:

@classmethod
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.
    """

    # 判斷queryset是否是QuerySet物件
    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 = 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)

通過這行程式碼view = super().as_view(**initkwargs),可以知道APIViewas_view方法也呼叫了父類Viewas_view方法,原始碼如下:

def as_view(cls, **initkwargs):
    """Main entry point for a request-response process."""
    for key in initkwargs:
        if key in cls.http_method_names:
            raise TypeError("You tried to pass in the %s method name as a "
                            "keyword argument to %s(). Don't do that."
                            % (key, cls.__name__))
        if not hasattr(cls, key):
            raise TypeError("%s() received an invalid keyword %r. as_view "
                            "only accepts arguments that are already "
                            "attributes of the class." % (cls.__name__, key))

    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        # 如果有get屬性,沒有head屬性,那麼head就是get
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get

        # 初始化所有檢視方法共享的屬性
        self.setup(request, *args, **kwargs)

        # 如果沒有request屬性,報異常
        if not hasattr(self, 'request'):
            raise AttributeError(
                "%s instance has no 'request' attribute. Did you override "
                "setup() and forget to call super()?" % cls.__name__
            )

        # 返回一個`dispatch`方法
        return self.dispatch(request, *args, **kwargs)
    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

as_view方法返回的是viewview返回的是dispatch方法,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
    # 初始化請求,返回的是Request物件
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        # 在呼叫方法處理程式之前執行任何需要發生的操作
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        # 獲取request的請求方法
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
        else:
            handler = self.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返回一個response響應物件,得到請求的響應結果,返回給前臺
 

總結

  1. url請求走的是APIViewas_view函式
  2. APIViewas_view呼叫父類(django原生)的as_view,還禁用了csrf認證
  3. 在父類的as_view中的dispatch方法請求走的又是APIViewdispatch
  4. 完成任務方法交給檢視類函式處理,得到請求的響應結果,返回給前臺

相關文章