DRF之請求執行流程和APIView原始碼分析
【一】路由入口
from django.contrib import admin
from django.urls import path
from book import views
urlpatterns = [
path('admin/', admin.site.urls),
# 原來的路由寫法
# path('test_http/', views.TestHttpResponse),
# 現在的路由寫法
path('test/', views.TestView.as_view()),
path('test_http/', views.TestHttpResponse.as_view()),
]
- 在檢視類中我們繼承了
APIView
- 在路由中我們由原來的繼承
View
的檢視函式 TestHttpResponse
變成了 繼承 APIView
的檢視函式 TestView
,並使用了寫的路由寫法,即TestView.as_view()
- 因此我們的入口就是在
as_view()
方法上
【二】檢視分析
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
class TestView(APIView):
def get(self, request, *args, **kwargs):
print(request)
print(type(request))
print(dir(request))
return Response('ok')
【三】APIView原始碼分析
【1】執行流程入口
path('test/', views.TestView.as_view())
- 執行 檢視函式
TestView
的 as_view
方法
- 那我們就從
as_view
進去
【2】路由中的 as_view()
class APIView(View):
# The following policies may be set at either globally, or per-view.
# 設定用於渲染響應的類,預設使用api_settings.DEFAULT_RENDERER_CLASSES。
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
# 設定用於解析請求內容的類,預設使用api_settings.DEFAULT_PARSER_CLASSES。
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
# 設定用於認證使用者身份的類,預設使用api_settings.DEFAULT_AUTHENTICATION_CLASSES。
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
# throttle_classes:設定用於限制API訪問頻率的類,預設使用api_settings.DEFAULT_THROTTLE_CLASSES。
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
# 設定用於確定使用者許可權的類,預設使用api_settings.DEFAULT_PERMISSION_CLASSES。
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
# 設定用於協商內容的類,預設使用api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS。
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
# 設定用於處理後設資料的類,預設使用api_settings.DEFAULT_METADATA_CLASS。
metadata_class = api_settings.DEFAULT_METADATA_CLASS
# 設定用於API版本控制的類,預設使用api_settings.DEFAULT_VERSIONING_CLASS。
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
# Allow dependency injection of other settings to make testing easier.
# 允許依賴注入其他設定以方便測試,允許在配置檔案中自定義配置並使用自定義配置
settings = api_settings
# 引用了DefaultSchema,表示預設的API模式類
schema = DefaultSchema()
# 包裝成靜態方法
@classmethod
def as_view(cls, **initkwargs):
"""
# 將原始類儲存在檢視函式中
Store the original class on the view function.
# 這允許我們在執行URL時發現有關檢視的資訊反向查詢
This allows us to discover information about the view when we do URL
reverse lookups. Used for breadcrumb generation.
"""
# 判斷獲取到的屬性值是否為models.query.QuerySet型別
# cls 檢視類 去檢視類中反射,是否存在 queryset 物件
# getattr(cls, 'queryset', None)
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
# 作用是在直接訪問.queryset屬性時觸發一個執行時錯誤
def force_evaluation():
# 不要直接評估.queryset屬性,因為結果會被快取並在請求之間重用
# 應該使用.all()方法或呼叫.get_queryset()方法來獲取資料集。
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.'
)
# 將force_evaluation()函式賦值給cls.queryset._fetch_all
# 當外部程式碼直接訪問.queryset屬性時,會丟擲RuntimeError異常
# 提醒開發者按照建議的方式來獲取資料集。
cls.queryset._fetch_all = force_evaluation
# 呼叫父類的 as_view 方法
view = super().as_view(**initkwargs)
# 將當前檢視類 新增 給 view 方法
view.cls = cls
# 將所有傳入的引數 新增給 view 方法
view.initkwargs = initkwargs
# 基於會話的身份驗證是顯式CSRF驗證的
# Note: session based authentication is explicitly CSRF validated,
# 所有其他認證都是免除CSRF的
# all other authentication is CSRF exempt.
# 用 csrf_exempt 包裝了 view 方法,去除了 csrf 認證
# 這裡返回出去的去除了 csrf 認證的 view 物件就是我們上面的as_view
# 而我們在上面執行了 as_view() 方法其實就是 這個 view() 方法 對到相應的檢視函式就是 get(request,*args,**kwargs)
return csrf_exempt(view)
【3】父類 View
的 as_view
方法
class View:
"""
# 為所有檢視建立簡單的父類。僅實現按方法排程和簡單的健全性檢查。
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
# 定義允許請求的請求方式
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# 定義初始化方法
def __init__(self, **kwargs):
"""
#在URLconf中呼叫;可以包含有用的額外關鍵字引數和其他內容。
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
# 遍歷傳入的所有引數
for key, value in kwargs.items():
# 將遍歷得到的鍵和值,全部新增到 self 物件中
setattr(self, key, value)
# 包裝成靜態方法
@classonlymethod
# 允許傳入檢視類和其他引數
def as_view(cls, **initkwargs):
# 請求-響應過程的主要入口點
"""Main entry point for a request-response process."""
# 遍歷 initkwargs 傳入的引數的鍵
for key in initkwargs:
# 判斷當前請求方式是否在上述請求方式列表中存在
if key in cls.http_method_names:
# 丟擲異常
# 方法名稱 不被接受為關鍵字引數
raise TypeError(
'The method name %s is not accepted as a keyword argument '
'to %s().' % (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)
# 呼叫啟動方法,初識化公共類屬性
self.setup(request, *args, **kwargs)
# 判斷當前物件是否存在 request 屬性
if not hasattr(self, 'request'):
# 不存在則丟擲異常
raise AttributeError(
# 不存在 request 屬性,必須提供
"%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.view_class = cls
# 將所有引數 新增給 view 物件
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
# 是否存在裝飾器,例如 csrf認證
update_wrapper(view, cls.dispatch, assigned=())
# 將 view 物件返回
return view
def setup(self, request, *args, **kwargs):
# 初始化所有檢視方法共享的屬性
"""Initialize attributes shared by all view methods."""
# 判斷當前類物件中存在get方法,並且沒有 head 方法
if hasattr(self, 'get') and not hasattr(self, 'head'):
# 將自身的 head 方法替換成 get 方法
self.head = self.get
# 將傳入的 request 賦值給當前物件
self.request = request
# 將傳入的 位置引數 賦值給當前物件
self.args = args
# 將傳入的 關鍵字引數 賦值給當前物件
self.kwargs = kwargs
【4】APIView 的 dispatch 方法
- 透過上面分析,我們發現在APIView中呼叫了父類的 as_view()方法
- 在父類 View 中,又呼叫了 dispatch 方法
- 因為我們是又 APIView 進到的 View ,所以我們當前的 self 其實是 APIView
- 那 self.dispatch() ,理所應當的就要從自己找,就是在下面所示的 APIView 中的 dispatch
def dispatch(self, request, *args, **kwargs):
"""
# 大致意識是和 APIView相似但是新增了新的功能
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
# 初識化引數,將 位置引數 新增給 self 物件
self.args = args
# 初識化引數,將 關鍵字引數 新增給 self 物件
self.kwargs = kwargs
# 初始化傳入的請求物件,將其封裝為符合Django規範的請求物件
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
# 獲取適當的處理程式方法
# 將 請求方式小寫 ,並判斷當前請求方式是否在允許的請求方式類表內
if request.method.lower() in self.http_method_names:
# handler : 當前的請求當時,獲取到當前請求方式
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
# 如果請求方法不存在,則會呼叫self.http_method_not_allowed方法,返回不允許的HTTP方法的響應
handler = self.http_method_not_allowed
# 呼叫選擇的處理方法,將請求物件和引數傳遞給它,並獲取返回的響應物件
response = handler(request, *args, **kwargs)
except Exception as exc:
# 如果在處理請求過程中發生任何異常,異常處理方法可以根據實際需求進行自定義,可以返回適當的錯誤響應。
response = self.handle_exception(exc)
# 對響應進行最後的處理,例如新增額外的響應頭部資訊、修改響應內容等。
self.response = self.finalize_response(request, response, *args, **kwargs)
# 返回處理好的最終響應物件
return self.response
def initialize_request(self, request, *args, **kwargs):
"""
# 返回一個例項化的 request 物件
Returns the initial request object.
"""
# 拿到解析後的資料字典
parser_context = self.get_parser_context(request)
# 返回例項化後的Request物件
return Request(
# 當前 request 物件
request,
# 解析器
parsers=self.get_parsers(),
# 認證使用者
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
# 解析後的資料
parser_context=parser_context
)
def get_parser_context(self, http_request):
"""
# 返回一個被解析器解析過得資料字典
Returns a dict that is passed through to Parser.parse(),
as the `parser_context` keyword argument.
"""
# Note: Additionally `request` and `encoding` will also be added
# to the context by the Request object.
# 返回了 類 物件本身
return {
'view': self,
# 將 位置引數 返回,無則為空
'args': getattr(self, 'args', ()),
# 將 關鍵字引數 返回,無則為空
'kwargs': getattr(self, 'kwargs', {})
}
def initial(self, request, *args, **kwargs):
"""
# 在呼叫方法處理程式之前執行任何需要發生的事情。
Runs anything that needs to occur prior to calling the method handler.
"""
# 透過get_format_suffix方法獲取到的格式字尾儲存在例項變數self.format_kwarg中
self.format_kwarg = self.get_format_suffix(**kwargs)
# Perform content negotiation and store the accepted info on the request
# 呼叫了perform_content_negotiation方法,並將請求物件request作為引數傳遞進去
# 執行內容協商,並返回一個包含可接受的渲染器和媒體型別的元組
neg = self.perform_content_negotiation(request)
# 將內容協商結果中的渲染器和媒體型別儲存在請求物件request的accepted_renderer和accepted_media_type屬性中
request.accepted_renderer, request.accepted_media_type = neg
# Determine the API version, if versioning is in use.
# 呼叫了determine_version方法,並將請求物件request以及其他引數傳遞進去。
# 該方法用於確定API的版本和版本控制方案,並返回一個包含版本和版本控制方案的元組。
version, scheme = self.determine_version(request, *args, **kwargs)
# 將確定的API版本和版本控制方案儲存在請求物件request的version和versioning_scheme屬性中
request.version, request.versioning_scheme = version, scheme
# 確保允許傳入請求
# Ensure that the incoming request is permitted
# 登入認證:呼叫了perform_authentication方法,並將請求物件request作為引數傳遞進去。
# 該方法用於執行身份驗證,確保傳入的請求是合法的。
self.perform_authentication(request)
# 許可權認證:呼叫了check_permissions方法,並將請求物件request作為引數傳遞進去。
# 該方法用於檢查許可權,確保使用者有權訪問該資源
self.check_permissions(request)
# 頻率認證:呼叫了check_throttles方法,並將請求物件request作為引數傳遞進去。
# 該方法用於檢查限流,確保請求沒有超過預定的頻率限制。
self.check_throttles(request)
def get_format_suffix(self, **kwargs):
"""
# 確定請求是否包含“.json”樣式的格式字尾
Determine if the request includes a '.json' style format suffix
"""
if self.settings.FORMAT_SUFFIX_KWARG:
return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG)
【四】總結
【1】請求過來的完整執行流程
- 當請求過來時,觸發路由中的
TestView.as_view()
方法
- 也就是
TestView.as_view()(request)
- 在
APIView
中觸發了self.as_view()
- 但是
APIView
沒有 as_view()
- 於是呼叫了父類中的
as_view()
方法
- 在父類的
as_view()
方法又觸發了dispatch
方法
- 於是又回到了
APIView
的 dispatch
方法
- 在
APIView
的 dispatch
方法中對資料進行處理
- 在
dispatch
方法中有一個initial
方法,這個方法完成了三大認證
- 三大認證完成後,執行 handler
- 先到檢視類中對映檢視函式,然後執行檢視函式,獲得響應資料,並返回
- 所有資料都處理完後接著向下走
- 對返回的 view 物件去除的 csrf 認證
【2】APIView相較View的大變化
- 以後只要繼承APIView的所有檢視類的方法,都沒有csrf的校驗了
- 以後只要繼承APIView的所有檢視類的方法 中的request是新的request了
- 在執行檢視類的方法之前,執行了三大認證(認證,許可權,頻率)
- 期間除了各種錯誤,都會被異常捕獲,統一處理