DRF之Response原始碼分析

ssrheart發表於2024-04-23

DRF之Response原始碼分析

【一】響應類的物件Response原始碼

【1】路由

from django.contrib import admin
from django.urls import path
from book import views

urlpatterns = [
    path('admin/', admin.site.urls),

    path('test/', views.TestView.as_view()),
]

【2】檢視

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

        return Response('ok')

【3】原始碼

from rest_framework.response import Response
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 rendered_content(self):
        renderer = getattr(self, 'accepted_renderer', None)
        accepted_media_type = getattr(self, 'accepted_media_type', None)
        context = getattr(self, 'renderer_context', None)

        assert renderer, ".accepted_renderer not set on Response"
        assert accepted_media_type, ".accepted_media_type not set on Response"
        assert context is not None, ".renderer_context not set on Response"
        context['response'] = self

        media_type = renderer.media_type
        charset = renderer.charset
        content_type = self.content_type

        if content_type is None and charset is not None:
            content_type = "{}; charset={}".format(media_type, charset)
        elif content_type is None:
            content_type = media_type
        self['Content-Type'] = content_type

        ret = renderer.render(self.data, accepted_media_type, context)
        if isinstance(ret, str):
            assert charset, (
                'renderer returned unicode, and did not specify '
                'a charset value.'
            )
            return ret.encode(charset)

        if not ret:
            del self['Content-Type']

        return ret

    @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

【二】原始碼分析

【1】__init__

def __init__(self, data=None, status=None,
             template_name=None, headers=None,
             exception=False, content_type=None):
    """
    # 稍微更改init引數。
    Alters the init arguments slightly.
    # 例如,刪除“template_name”,改為使用“data”。
    For example, drop 'template_name', and instead use 'data'.
	
	# 設定“renderer”和“media_type”
    Setting 'renderer' and 'media_type' will typically be deferred,
    For example being set automatically by the `APIView`.
    """
    # 呼叫父類的 init 方法
    super().__init__(None, status=status)
	
    # 判斷當前處理過的資料是否是 序列化過後的資料
    if isinstance(data, Serializer):
        
        # 這裡的意思是 ,data 只能是序列化後的 serializer.data 或者是 serializer.errors
        msg = (
            'You passed a Serializer instance as data, but '
            'probably meant to pass serialized `.data` or '
            '`.error`. representation.'
        )
        raise AssertionError(msg)
	
    # 初始化 data 資料
    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

【2】rendered_content

# 包裝成資料屬性
@property
# 渲染響應內容
def rendered_content(self):
    # 獲取響應物件中的屬性值,獲取模版渲染器
    renderer = getattr(self, 'accepted_renderer', None)
    # 獲取響應物件中的屬性值,獲取媒體型別
    accepted_media_type = getattr(self, 'accepted_media_type', None)
    # 獲取響應物件中的屬性值,獲取處理過後的資料
    context = getattr(self, 'renderer_context', None)
	
    # 使用斷言語句,確保上述獲取到的屬性值都不為空。如果為空,丟擲異常並給出相應的錯誤提示
    assert renderer, ".accepted_renderer not set on Response"
    assert accepted_media_type, ".accepted_media_type not set on Response"
    assert context is not None, ".renderer_context not set on Response"
    
    # 設定上下文字典中的鍵response的值為當前的響應物件
    context['response'] = self
	
    # 獲取渲染器物件的媒體型別
    media_type = renderer.media_type
    # 獲取渲染器物件的編碼集
    charset = renderer.charset
    # 獲取渲染器物件的資料型別
    content_type = self.content_type
	
    # 如果content_type為空且charset不為空
    if content_type is None and charset is not None:
        # 將media_type和charset拼接為新的content_type值
        content_type = "{}; charset={}".format(media_type, charset)
        
    # 如果content_type還是為空
    elif content_type is None:
        # 則將其值設定為media_type
        content_type = media_type
    
    # 將響應的資料格式新增到響應物件頭中
    self['Content-Type'] = content_type
	
    # 使用渲染器渲染資料
    ret = renderer.render(self.data, accepted_media_type, context)
    
    # 判斷渲染出來的資料是不是字串格式
    if isinstance(ret, str):
        
        # 若不存在,則丟擲異常並提示渲染器返回了Unicode字串但未指定字符集
        assert charset, (
            'renderer returned unicode, and did not specify '
            'a charset value.'
        )
        # 如果存在,則將ret按指定的charset進行編碼並返回
        return ret.encode(charset)
	
    # 如果ret是字串型別,再次檢查charset是否存在。
    
    # 如果ret為空,則刪除響應物件的Content-Type頭部屬性
    if not ret:
        del self['Content-Type']
	
    # 返回渲染後的結果給呼叫方
    return ret

【3】status_text

@property
# 狀態碼校驗
def status_text(self):
    """
    #返回與我們的HTTP響應狀態程式碼相對應的原因文字。
    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.
    """
    
    # 呼叫了父類的__getstate__方法來獲取父類中定義的狀態,並將其賦值給變數state
    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']。
    # 目的是確保物件在進行序列化時沒有包含不必要的屬性,從而避免潛在的問題。
    state['_closable_objects'] = []
    return state

【三】SimpleTemplateResponse(沒什麼價值)

class SimpleTemplateResponse(HttpResponse):
    rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks']

    def __init__(self, template, context=None, content_type=None, status=None,
                 charset=None, using=None, headers=None):
        # 很明顯,將這兩個成員稱為“模板”
        # It would seem obvious to call these next two members 'template' and		
        # “context”,但這些名稱是作為測試客戶端的一部分保留的
        # 'context', but those names are reserved as part of the test Client
        # API為了避免名稱衝突,我們使用不同的名稱。
        # API. To avoid the name collision, we use different names.
        
        # 初始化模版名字
        self.template_name = template
        # 初始化上下文物件
        self.context_data = context
		
        # 判斷是夠被使用
        self.using = using
		
        # 是否有回撥函式
        self._post_render_callbacks = []
		
        
        # request將當前請求物件儲存在已知的子類中
        #關於請求,如TemplateResponse。它是在基類中定義的
        #以最大限度地減少程式碼重複。
        #這叫做自我_請求,因為self.request被覆蓋
        #django.test.client.client.與template_name和context_data不同,
        #_request不應被視為公共API的一部分。
        # _request stores the current request object in subclasses that know
        # about requests, like TemplateResponse. It's defined in the base class
        # to minimize code duplication.
        # It's called self._request because self.request gets overwritten by
        # django.test.client.Client. Unlike template_name and context_data,
        # _request should not be considered part of the public API.
        
        # 初始化 request 物件 ---- 上一步處理過的request物件(檢視類中的request物件)
        self._request = None
	
    	#content引數在這裡沒有意義,因為它將被替換
        #使用渲染的模板,所以我們總是傳遞空字串,以便
        #防止錯誤並提供更短的簽名。
        # content argument doesn't make sense here because it will be replaced
        # with rendered template so we always pass empty string in order to
        # prevent errors and provide shorter signature.
        # 呼叫父類初識化方法初始化
        super().__init__('', content_type, status, charset=charset, headers=headers)
		
        #_is_render跟蹤模板和上下文是否已烘焙
        #轉化為最終響應。
        #超級__init__不知道什麼比將self.content設定為更好的了
        #我們剛剛給它的空字串,它錯誤地設定了_is_render
        #True,所以我們在呼叫super__init__之後將其初始化為False。
        # _is_rendered tracks whether the template and context has been baked
        # into a final response.
        # Super __init__ doesn't know any better than to set self.content to
        # the empty string we just gave it, which wrongly sets _is_rendered
        # True, so we initialize it to False after the call to super __init__.
        self._is_rendered = False

    def __getstate__(self):
        """
        Raise an exception if trying to pickle an unrendered response. Pickle
        only rendered data, not the data used to construct the response.
        """
        obj_dict = self.__dict__.copy()
        if not self._is_rendered:
            raise ContentNotRenderedError('The response content must be '
                                          'rendered before it can be pickled.')
        for attr in self.rendering_attrs:
            if attr in obj_dict:
                del obj_dict[attr]

        return obj_dict

    def resolve_template(self, template):
        """Accept a template object, path-to-template, or list of paths."""
        if isinstance(template, (list, tuple)):
            return select_template(template, using=self.using)
        elif isinstance(template, str):
            return get_template(template, using=self.using)
        else:
            return template

    def resolve_context(self, context):
        return context

    @property
    def rendered_content(self):
        """Return the freshly rendered content for the template and context
        described by the TemplateResponse.

        This *does not* set the final content of the response. To set the
        response content, you must either call render(), or set the
        content explicitly using the value of this property.
        """
        template = self.resolve_template(self.template_name)
        context = self.resolve_context(self.context_data)
        return template.render(context, self._request)

    def add_post_render_callback(self, callback):
        """Add a new post-rendering callback.

        If the response has already been rendered,
        invoke the callback immediately.
        """
        if self._is_rendered:
            callback(self)
        else:
            self._post_render_callbacks.append(callback)

    def render(self):
        """Render (thereby finalizing) the content of the response.

        If the content has already been rendered, this is a no-op.

        Return the baked response instance.
        """
        retval = self
        if not self._is_rendered:
            self.content = self.rendered_content
            for post_callback in self._post_render_callbacks:
                newretval = post_callback(retval)
                if newretval is not None:
                    retval = newretval
        return retval

    @property
    def is_rendered(self):
        return self._is_rendered

    def __iter__(self):
        if not self._is_rendered:
            raise ContentNotRenderedError(
                'The response content must be rendered before it can be iterated over.'
            )
        return super().__iter__()

    @property
    def content(self):
        if not self._is_rendered:
            raise ContentNotRenderedError(
                'The response content must be rendered before it can be accessed.'
            )
        return super().content

    @content.setter
    def content(self, value):
        """Set the content for the response."""
        HttpResponse.content.fset(self, value)
        self._is_rendered = True


class TemplateResponse(SimpleTemplateResponse):
    rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request']

    def __init__(self, request, template, context=None, content_type=None,
                 status=None, charset=None, using=None, headers=None):
        super().__init__(template, context, content_type, status, charset, using, headers=headers)
        self._request = request

【四】響應類的物件Respons引數詳解

【1】data

  • 響應體的內容,可以字串,字典,列表

【2】status

  • http響應狀態碼

    • drf把所有響應碼都定義成了一個常量
    from rest_framework.status import HTTP_200_OK
    
    HTTP_100_CONTINUE = 100
    HTTP_101_SWITCHING_PROTOCOLS = 101
    HTTP_102_PROCESSING = 102
    HTTP_103_EARLY_HINTS = 103
    HTTP_200_OK = 200
    HTTP_201_CREATED = 201
    HTTP_202_ACCEPTED = 202
    HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
    HTTP_204_NO_CONTENT = 204
    HTTP_205_RESET_CONTENT = 205
    HTTP_206_PARTIAL_CONTENT = 206
    HTTP_207_MULTI_STATUS = 207
    HTTP_208_ALREADY_REPORTED = 208
    HTTP_226_IM_USED = 226
    HTTP_300_MULTIPLE_CHOICES = 300
    HTTP_301_MOVED_PERMANENTLY = 301
    HTTP_302_FOUND = 302
    HTTP_303_SEE_OTHER = 303
    HTTP_304_NOT_MODIFIED = 304
    HTTP_305_USE_PROXY = 305
    HTTP_306_RESERVED = 306
    HTTP_307_TEMPORARY_REDIRECT = 307
    HTTP_308_PERMANENT_REDIRECT = 308
    HTTP_400_BAD_REQUEST = 400
    HTTP_401_UNAUTHORIZED = 401
    HTTP_402_PAYMENT_REQUIRED = 402
    HTTP_403_FORBIDDEN = 403
    HTTP_404_NOT_FOUND = 404
    HTTP_405_METHOD_NOT_ALLOWED = 405
    HTTP_406_NOT_ACCEPTABLE = 406
    HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
    HTTP_408_REQUEST_TIMEOUT = 408
    HTTP_409_CONFLICT = 409
    HTTP_410_GONE = 410
    HTTP_411_LENGTH_REQUIRED = 411
    HTTP_412_PRECONDITION_FAILED = 412
    HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
    HTTP_414_REQUEST_URI_TOO_LONG = 414
    HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
    HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
    HTTP_417_EXPECTATION_FAILED = 417
    HTTP_418_IM_A_TEAPOT = 418
    HTTP_421_MISDIRECTED_REQUEST = 421
    HTTP_422_UNPROCESSABLE_ENTITY = 422
    HTTP_423_LOCKED = 423
    HTTP_424_FAILED_DEPENDENCY = 424
    HTTP_425_TOO_EARLY = 425
    HTTP_426_UPGRADE_REQUIRED = 426
    HTTP_428_PRECONDITION_REQUIRED = 428
    HTTP_429_TOO_MANY_REQUESTS = 429
    HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
    HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451
    HTTP_500_INTERNAL_SERVER_ERROR = 500
    HTTP_501_NOT_IMPLEMENTED = 501
    HTTP_502_BAD_GATEWAY = 502
    HTTP_503_SERVICE_UNAVAILABLE = 503
    HTTP_504_GATEWAY_TIMEOUT = 504
    HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
    HTTP_506_VARIANT_ALSO_NEGOTIATES = 506
    HTTP_507_INSUFFICIENT_STORAGE = 507
    HTTP_508_LOOP_DETECTED = 508
    HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509
    HTTP_510_NOT_EXTENDED = 510
    HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
    

【3】template_name

  • 模板名字,用瀏覽器訪問,看到好看的頁面,用postman訪問,返回正常資料
    • 自定製頁面
    • 根本不用

【4】headers

  • 響應頭加資料(後面講跨域問題再講)
    • headers=

【5】content_type

  • 響應編碼,一般不用

三個重要的引數:data,status,headers

【五】請求響應的格式

  • 預設是兩種:
    • 純json
    • 瀏覽器看到的樣子

【1】限制方式一:

  • 在檢視類上寫
    • 只是區域性檢視類有效
# 總共有兩個個:JSONRenderer,BrowsableAPIRenderer
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
    renderer_classes = [JSONRenderer]

【2】限制方式二:

  • 在配置檔案中寫
    • 全域性有效
# drf的配置,統一寫成它
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        # 'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}

【3】全域性配置了只支援json,區域性想支援2個

  • 只需要在區域性,檢視類中,寫2個即可
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
    renderer_classes = [JSONRenderer,BrowsableAPIRenderer]

相關文章