今天看了drf的五個組件的源碼,可讀性仍是很高的,只是讀組件的時候要注意的是 大部分的組件都是由dispatch分發出去的,因此看源碼的時候必定要抓住dispatch這條主線,一步一步看下去前端
drf的request是在wsgi的request的基礎上進行再次封裝python
**wsgi的request做爲drf的request一個屬性:_request(下面附源碼解釋)**django
#源碼: #在rest-framework 的views.py文件中 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 # 在下面這行代碼中 django原來的request傳入 self.initialize_request 這個方法 request = self.initialize_request(request, *args, **kwargs) #self.initialize_request方法源碼分析 def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) # 在下面這個函數的返回結果能夠看出來 傳入的原生django(wsgi)的request當作初始化參數傳入Request這個類中 返回的就是Request這個類的實例化對象 return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context ) # 因爲Request是實例化 因此走的是 __init__方法 下面就是Request的__init__方法代碼 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__) ) self._request = request #只看這一行 咱們就能夠得出結果 Request實例傳進來的原生的django(wsgi)的request在 Request的__init__方法中 從新賦值給self._request 而這個self就是咱們在rest-framework中的request
#源碼 #在rest-framework的request.py中 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: return getattr(self._request, attr) # 經過反射獲取屬性,若是self._request存在,則使用self._request except AttributeError: # 不存在 則使用 rest-framework的request return self.__getattribute__(attr)
新的request對數據解析更規範化 :全部的拼接參數都解析到 requery_params中,全部數據包數據都被解析到了data中, query_params和 data屬於QueryDict類型,可使用.dict()轉化爲原生的dict類型json
drf的APIView類:重寫了as_view(),但主題邏輯仍是調用父類View的as_view()方法 ,最大的改動就是局部禁用了csrf認證(下面附源碼解釋)api
def as_view(cls, **initkwargs): #目錄:rest-framework/views.py 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 view = super().as_view(**initkwargs) # 調用父類View的as_view()方法 view.cls = cls view.initkwargs = initkwargs return csrf_exempt(view) # 局部禁用csrf
能夠在視圖;類中經過rendere_classes類類屬性對該視圖的數據響應渲染作配置 --局部配置服務器
能夠在項目的配資文件的drf配置中經過DEFAULT_RENDERER_CLASSES對該視圖的數據響應渲染作配置 --全局配置(下面附渲染模塊源碼分析)網絡
注:若是在一個視圖類在有全局配置下,還進行了局部配置,優先走本身的局部配置app
局部(views中) :renderer_classes = [JSONRenderer, BrowsableAPIRenderer]函數
全局(項目配置文件中) : DEFAULT_RENDERER_CLASSES = [JSONRenderer, BrowsableAPIRenderer]源碼分析
""" 渲染模塊源碼分析 一、二次處理響應對象:APIView的dispatch方法 - self.finalize_response(request, response, *args, **kwargs) 二、獲取渲染類對象:進入finalize_response方法 - self.perform_content_negotiation(request, force=True) 三、從配置文件中獲得渲染類對象:perform_content_negotiation -> self.get_renderers() -> [renderer() for renderer in self.renderer_classes] """ """ 核心:能夠全局和局部配置視圖類支持的結果渲染:默承認以json和頁面渲染,學習該模塊的目的是開發能夠全局只配置json方式渲染 """ #詳細分析 #位置 rest-framework/views.py中的dispatch函數中 507行 # 下面這行代碼利用finalize_response這個函數 對響應對象進行二次處理(規定返回數據的樣式) self.response = self.finalize_response(request, response, *args, **kwargs) # 再來看看 finalize_response 這個函數作了什麼 # 位置:rest-framework/views.py/finalize_response 414行 def finalize_response(self, request, response, *args, **kwargs): """ Returns the final response object. """ # Make the error obvious if a proper response is not returned # 下面這行代碼的做用是:判斷返回的是不是HttpResponseBase的對象,不是則報錯 assert isinstance(response, HttpResponseBase), ( 'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` ' 'to be returned from the view, but received a `%s`' % type(response) ) if isinstance(response, Response): #先經過反射到request中尋找accepted_renderer是否存在 若是不存在,就用本身默認的 if not getattr(request, 'accepted_renderer', None): # 利用perform_content_negotiation函數 找到全局或者默認的渲染規則(項目中沒有在 settings中設置全局的渲染規則的話 就用rest-framework默認的渲染規則) neg = self.perform_content_negotiation(request, force=True) request.accepted_renderer, request.accepted_media_type = neg response.accepted_renderer = request.accepted_renderer response.accepted_media_type = request.accepted_media_type response.renderer_context = self.get_renderer_context() #perform_content_negotiation 函數 # 位置 rest-framework/view.py/perform_content_negotiation 302行 def perform_content_negotiation(self, request, force=False): renderers = self.get_renderers() #經過get_renderers方法獲取配置 # 位置 rest-framework/view.py/get_renderers 256行 def get_renderers(self): # 又經過renderer_classes 獲取配置 經過列表推導式實例化返回 return [renderer() for renderer in self.renderer_classes] # 位置 rest-framework/view.py 107行 # 獲取全局DEFAULT_RENDERER_CLASSES配置 renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
做用:服務與數據包數據
能夠在視圖類中經過parser_classes類屬性對該視圖的數據包解析作配置 --局部配置
可在項目的配置文件的drf配置中經過DEFAULT_PARSER_CLASSES對該視圖的數據響應渲染作配置 --全局配置
解析模塊源碼分析
APIVIEW的dispatch方法:self.initialize_request(request, *args, **kwargs)內部還提供了數據解析
self.get_parser_context(request)提供要解析的數據,self.get_parsers() 提供解析的類對象(內部從配置中找到解析類)
在settings的drf配置中配置EXCEPTION_HANDLER,指向之定義的exception_handle函數
drf出現異常了,都會回調exception_handle函數,攜帶異常對象和異常相關信息內容,
在exception_handle函數完成異常信息的返回以及異常信息的logging日誌
源碼分析
在APIView的dispatch方法中,有一個超大的try...except..., 將代碼運行異常都交給異常處理self.handle_exception(exc)
try: 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) else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc: #捕捉錯誤信息 將錯誤信息交給handle_exception response = self.handle_exception(exc)
從配置中映射出配置處理異常的函數(自定義異常模塊就是自定義配置指向本身的函數): self.get_exception_handle()
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 = self.get_exception_handler() #獲取錯誤處理函數 自定義了就用你自定義的 沒有自定義就用系統的 context = self.get_exception_handler_context() response = exception_handler(exc, context) if response is None: self.raise_uncaught_exception(exc) response.exception = True return response # drf默認的錯誤處理函數 def exception_handler(exc, context): if isinstance(exc, Http404): exc = exceptions.NotFound() elif isinstance(exc, PermissionDenied): exc = exceptions.PermissionDenied() if isinstance(exc, exceptions.APIException): headers = {} if getattr(exc, 'auth_header', None): headers['WWW-Authenticate'] = exc.auth_header if getattr(exc, 'wait', None): headers['Retry-After'] = '%d' % exc.wait if isinstance(exc.detail, (list, dict)): data = exc.detail else: data = {'detail': exc.detail} set_rollback() # 處理的錯誤 就返回錯誤結果 return Response(data, status=exc.status_code, headers=headers) # 處理不了的就返回none return None
異常函數exception_handle(exc,context)處理異常 就會走本身的先交給系統處理(客戶端的異常),系統沒處理(服務器異常),再本身處理
# 自定義異常處理文件能夠放在項目任何位置,你找獲得就行,不過建議放在根目錄或者應用目錄 from rest_framework.response import Response from rest_framework.views import exception_handler as drf_exception_handler from rest_framework import status def exception_handler(exc, context): # 重寫錯誤處理方法 response = drf_exception_handler(exc, context) #先執行系統定義的錯誤函數 ,過濾掉請求錯誤 if response is None: # drf沒有處理的異常(服務器異常) return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={ 'status': 7, 'exc': '%s' % exc }) # 項目階段,要記錄到日誌文件 return Response(status=response.status_code, data={ 'status': 7, # drf處理的客戶端異常,原始處理方式是將異常信息放在response對象的data中,data的格式是{'datail': '具體的異常信息'} 'exc': '%s' % response.data.get('detail') })
Response類生成對象須要的參數,以及Response類的對象可使用的屬性
1. 參數: Response(data=響應的數據,status=響應的網絡狀態碼,headers=想經過響應頭再攜帶不部分信息給前端)
屬性:response.data response.status_code response.status_text
源碼:Response類的_init_方法
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