DRF之请求执行流程和APIView源码分析
【一】路由入口
| 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()), |
| 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): |
| |
| |
| |
| renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES |
| |
| |
| parser_classes = api_settings.DEFAULT_PARSER_CLASSES |
| |
| |
| 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 = api_settings |
| |
| |
| 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. |
| """ |
| |
| |
| |
| |
| 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.cls = cls |
| |
| |
| view.initkwargs = initkwargs |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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. |
| """ |
| |
| |
| |
| |
| for key, value in kwargs.items(): |
| |
| setattr(self, key, value) |
| |
| |
| @classonlymethod |
| |
| 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( |
| '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) |
| |
| |
| if not hasattr(self, 'request'): |
| |
| raise AttributeError( |
| |
| "%s instance has no 'request' attribute. Did you override " |
| "setup() and forget to call super()?" % cls.__name__ |
| ) |
| |
| |
| return self.dispatch(request, *args, **kwargs) |
| |
| |
| view.view_class = cls |
| |
| view.view_initkwargs = initkwargs |
| |
| |
| |
| update_wrapper(view, cls, updated=()) |
| |
| |
| |
| |
| update_wrapper(view, cls.dispatch, assigned=()) |
| |
| |
| return view |
| def setup(self, request, *args, **kwargs): |
| |
| """Initialize attributes shared by all view methods.""" |
| |
| |
| if hasattr(self, 'get') and not hasattr(self, 'head'): |
| |
| self.head = self.get |
| |
| |
| 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
![image-20230913192421317]()
| 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.args = args |
| |
| self.kwargs = kwargs |
| |
| |
| request = self.initialize_request(request, *args, **kwargs) |
| |
| |
| self.request = request |
| |
| |
| self.headers = self.default_response_headers |
| |
| try: |
| |
| self.initial(request, *args, **kwargs) |
| |
| |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| return 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. |
| """ |
| |
| |
| |
| |
| 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. |
| """ |
| |
| self.format_kwarg = self.get_format_suffix(**kwargs) |
| |
| |
| |
| |
| neg = self.perform_content_negotiation(request) |
| |
| |
| request.accepted_renderer, request.accepted_media_type = neg |
| |
| |
| |
| |
| version, scheme = self.determine_version(request, *args, **kwargs) |
| |
| request.version, request.versioning_scheme = version, scheme |
| |
| |
| |
| |
| |
| |
| |
| self.perform_authentication(request) |
| |
| |
| self.check_permissions(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)
![image-20230913201506292]()
- 在
APIView
中触发了self.as_view()
- 但是
APIView
没有 as_view()
- 于是调用了父类中的
as_view()
方法
![image-20230913201952923]()
- 在父类的
as_view()
方法又触发了dispatch
方法
- 于是又回到了
APIView
的 dispatch
方法
![image-20230913202319757]()
- 在
APIView
的 dispatch
方法中对数据进行处理
![image-20230913202624533]()
- 在
dispatch
方法中有一个initial
方法,这个方法完成了三大认证
![image-20230913202810027]()
- 三大认证完成后,执行 handler
- 先到视图类中映射视图函数,然后执行视图函数,获得响应数据,并返回
![image-20230913205517624]()
- 所有数据都处理完后接着向下走
- 对返回的 view 对象去除的 csrf 认证
![image-20230913202835740]()
【2】APIView相较View的大变化
- 以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
- 以后只要继承APIView的所有视图类的方法 中的request是新的request了
- 在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
- 期间除了各种错误,都会被异常捕获,统一处理
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步