• drf 02 APIView的请求生命周期及各个模块源码分析和配置


    django 视图类和drf视图类区别

    模块 请求类型 继承关系 响应数据格式 需注销csrf
    django get View JsonResponse(字典),没有Response
    django post View JsonResponse(字典),没有Response
    drf get APIView JsonResponse(字典),Response(python基本数据类型皆可)
    drf post APIView JsonResponse(字典),Response(python基本数据类型皆可)
    # d_proj>urls.py
    from django.conf.urls import url,include
    from django.contrib import admin
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^api/',include('api.urls')),
    ]
    
    
    # api>urls.py
    from django.conf.urls import url
    from . import views
    urlpatterns = [
        url(r'^v1/books/$',views.BookViews.as_view()),
        url(r'^v1/books/(?P<pk>d+)/$',views.BookViews.as_view()),
    
        url(r'^v2/books/$',views.BookAPIViews.as_view()),
        url(r'^v2/books/(?P<pk>d+)/$',views.BookAPIViews.as_view()),
    ]
    
    
    # views.py
    from rest_framework.views import APIView
    from django.views import View
    from django.http import JsonResponse
    class BookViews(View):
    
        def get(self,*args,**kwargs):
            return JsonResponse({
                'msg':'get BookViews'
            })
    
        def post(self,*args,**kwargs):
            return JsonResponse({
                'msg': 'post BookViews'
            })
    
    
    class BookAPIViews(APIView):
        def get(self,*args,**kwargs):
            return JsonResponse({
                'msg': 'get BookAPIViews'
            })
    
        def post(self,*args,**kwargs):
            return JsonResponse({
                'msg': 'post BookAPIViews'
    
    

    APIView的请求生命周期

    APIView的请求生命周期
     1)APIView类继承View类,重写了as_view和dispatch方法
     2)重写的as_view方法,主体还是django中View的as_view,只是在返回视图view函数地址时,局部禁用csrf认证,所以以后只要是视图类继承了APIView,就不需要注销settings中的csrf中间件了,其实这是就相当于注销掉了settings的中间件csrf。然后调用djangoas_view内部的闭包函数view,在函数view内部返回cls(我们定义的视图类)实例化的对象self.dispatch(request,*args,**kwargs),按照属性的查找顺序(自己类》继承的父类》父类的父类),在APIView中定义的有dispatch方法,所以不会走django》View类中的dispatch方法了。
     3)重写的dispatch方法,
           在执行请求逻辑前:请求模块(二次封装request)、解析模块(三种数据包格式的数据解析)
           在执行请求逻辑中:异常模块(执行出现任何异常交给异常模块处理)
           在执行请求逻辑后:响应模块(二次封装response)、渲染模块(响应的数据可以是两种渲染方式:可以是JSON数据格式,也可以是页面),在浏览器输入接口是页面渲染,postman是JSON格式
    
    # urls.py
    
    urlpatterns = [
        url(r'^books/$', views.BookAPIViews.as_view()),
        url(r'^books/(?P<pk>d+)/$', views.BookAPIViews.as_view()),
    ]
    
    
    # 调用中APIView中的as_view()方法:
    # rest_framework>views.py>APIView(View)类>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.
            """
            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)  # 调用django中的as_view()
            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中的as_view方法
    class View(object):
        """
        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):
            """
            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 six.iteritems(kwargs):
                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("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):  # 接收路由的有名和无名分组的参数
                # print(request)
                # print(request.method)  # <WSGIRequest: DELETE '/api/books/1/'>
                # print(args,kwargs)    # () {'pk': '1'}
                self = cls(**initkwargs)
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
                self.request = request
                self.args = args
                self.kwargs = kwargs
                return self.dispatch(request, *args, **kwargs)  # APIView类中有dispatch方法,就不会走APIView类继承的View中(django中的View)的dispatch
            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
    
    # 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:
                # 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
                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:
                # 异常模块 - 处理请求异常分支的
                response = self.handle_exception(exc)
            # 二次封装response,处理了结果 渲染
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
        
        
    # views.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    class BookAPIViews(APIView):
        def get(self,*args,**kwargs):
            return Response({
                'msg': 'get BookAPIViews'
            })
    
        def post(self,*args,**kwargs):
            return Response({
                'msg': 'post BookAPIViews'
            })
    

    请求模块

    # 走drf的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
            # print(self.request) # <rest_framework.request.Request object at 0x000000000BCD6DA0>
            self.headers = self.default_response_headers  # deprecate?
    
            try:
                # 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
                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:
                # 异常模块 - 处理请求异常分支的
                response = self.handle_exception(exc)
            # 二次封装response,处理了结果 渲染
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
        
        
    def initialize_request(self, request, *args, **kwargs):
        """
            Returns the initial request object.
            """
        parser_context = self.get_parser_context(request)
    
        return Request(  # 初始化
            request,  # 将wsgi的request也出入Request的__init__内部进行初始化
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
    
    # rest_framework>request.py>Request类
    class Request:
        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  # 我们自定义的类内部只有通过self._request访问原始的request
            self.parsers = parsers or ()
            self.authenticators = authenticators or ()
            self.negotiator = negotiator or self._default_negotiator()
            self.parser_context = parser_context
            self._data = Empty
            self._files = Empty
            self._full_data = Empty
            self._content_type = Empty
            self._stream = Empty
    
            if self.parser_context is None:
                self.parser_context = {}
            self.parser_context['request'] = self
            self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
    
            force_user = getattr(request, '_force_auth_user', None)
            force_token = getattr(request, '_force_auth_token', None)
            if force_user is not None or force_token is not None:
                forced_auth = ForcedAuthentication(force_user, force_token)
                self.authenticators = (forced_auth,)
                
        @property
        def query_params(self):
            """
            More semantically correct name for request.GET.
            """
            return self._request.GET
    
        @property
        def data(self):
            if not _hasattr(self, '_full_data'):
                self._load_data_and_files()
            return self._full_data
    
    class BookAPIViews(APIView):
        def get(self,request,*args,**kwargs):
            return Response({
                'msg': 'get BookAPIViews'
            })
    
        def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
            
            #在内部将wsgi的request赋值给request._request
            print(type(request))  # <class 'rest_framework.request.Request'>  
          
        	# 就是通过__getattr__走的是request._request.method
            print(request.method)  # POST  这里request是Request的对象,对象.属性 触发类内部的__getattr__方法  Request类见最下面在最下方
            print(request._request.method)  # POST 等价于上面
            
            # 走的是方法属性,就是给request._request.GET重新命名
            print(request.query_params)  # <QueryDict: {'a': ['10']}>
            
            # 走的是方法属性,值依赖于request._full_data
            print(request.data)     # <QueryDict: {'{
    	"name":"zhang"
    }': ['']}>
            
            return Response({
                'msg': 'post BookAPIViews'
            })
        
    # ==========================================================================================
     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)
            except AttributeError:
                return self.__getattribute__(attr)
    
        @property
        def DATA(self):
            raise NotImplementedError(
                '`request.DATA` has been deprecated in favor of `request.data` '
                'since version 3.0, and has been fully removed as of version 3.2.'
            )
    
        @property
        def POST(self):
            # Ensure that request.POST uses our request parsing.
            if not _hasattr(self, '_data'):
                self._load_data_and_files()
            if is_form_media_type(self.content_type):
                return self._data
            return QueryDict('', encoding=self._request._encoding)
    
        @property
        def FILES(self):
            # Leave this one alone for backwards compat with Django's request.FILES
            # Different from the other two cases, which are not valid property
            # names on the WSGIRequest class.
            if not _hasattr(self, '_files'):
                self._load_data_and_files()
            return self._files
    
        @property
        def QUERY_PARAMS(self):
            raise NotImplementedError(
                '`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
                'since version 3.0, and has been fully removed as of version 3.2.'
            )
    """
    request.DATA
    request.FILES
    request.QUERY_PARAMS
    """
    # ===========================================================================================
    

    解析模块

    解析模块:只处理数据包参数 - form-data,urlencoded,json
    1)全局配置所有视图类的解析方式,解析配置可以配置三种
    2)局部配置当前视图类的解析方式,解析配置可以配置三种
    3)配置的查找顺序:局部(视图类的类属性) => 全局(settings文件的drf配置) => 默认(drf的默认配置)
    
    # 三种解析类解析数据所对应的数据编码格式:
        JSONParser---->media_type = 'application/json'
        FormParser---->'application/x-www-form-urlencoded'
        MultiPartParser---->'multipart/form-data'
    
    from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
    from rest_framework.views import APIView
    from rest_framework.response import Response
    class BookAPIViews(APIView):
    	# 视图类内部自己配置解析类属性(局部配置)************
        parser_classes = [JSONParser,FormParser,MultiPartParser]
    
        # def get(self,request,*args,**kwargs):
        #     return Response({
        #         'msg': 'get BookAPIViews'
        #     })
    
        def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
            print(request.query_params)  #  接口拼接的参数
            print(request.data)  # 前端三种编码格式数据解析后都在data内 ,form-data  form-urlencoded  raw(application/json)
            return Response({
                'msg': 'post BookAPIViews'
            })
    

    解析模块配置

    1) 局部配置:
     parser_classes = [JSONParser,FormParser,MultiPartParser]
        
    2) 全局配置(在api的settings文件内):# 全局配置解析类:适用于所有视图类
    REST_FRAMEWORK = {
        'DEFAULT_PARSER_CLASSES': [
            'rest_framework.parsers.JSONParser',  # json
            'rest_framework.parsers.FormParser',  # form 表单提交form-urlencoded编码格式的数据
           # 'rest_framework.parsers.MultiPartParser'  # form 表单提交form-data编码格式的数据
            ],
    }
    
    3) 系统配置
    rest-framework>settings>__init__()方法内加载DEFAULT
    

    解析类源码分析

    入口:APIVIew的dispatch方法的 request=self.initialize_request(request, *args, **kwargs)
    获取解析类:parsers=self.get_parsers(),
    进行局部全局默认配置查找顺序进行查找:return [parser() for parser in self.parser_classes]
    

    响应模块

    data:响应数据
    status:响应的网络状态码
    
    template_name:drf完成前后台不分离返回页面,但是就不可以返回data(不许用了解)
    headers:响应头,一般不规定,走默认
    exception:一般异常响应,会将其设置成True,默认False(不设置也没事)
    content_type:默认就是 application/json,不需要处理
    
    from rest_framework.response import Response
    from rest_framework import status  # 网络状态码
    
     def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
            print(request.query_params)  #  接口拼接的参数
            print(request.data)  # 前端三种编码格式数据解析后都在data内 ,form-data  form-urlencoded  raw(application/json)
    
            response = Response(
                data = {
                # 就是让Response类中的__init__的关键字形参data接受
                'msg': 'post BookAPIViews'
            },
            status= status.HTTP_400_BAD_REQUEST
            )
            print(response.data)  # {'msg': 'post BookAPIViews'}
            return response
    

    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)
    # ===========================================================================================
        # 把属性全部加载到Response类的对象的名称空间 
        	self.data = data  # self 是Response类的对象
            self.template_name = template_name
            self.exception = exception
            self.content_type = content_type
    
            if headers:
                for name, value in headers.items():
                    self[name] = value
    

    渲染模块

    全局配置渲染类

    REST_FRAMEWORK = {
        # 全局配置渲染类:适用于所有视图类
        'DEFAULT_RENDERER_CLASSES': [
            # 控制JSON数据在postman的显示
            'rest_framework.renderers.JSONRenderer',
            # 控制drf页面在浏览器页面渲染
            # 'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览器端能访问到drf页面,上线后尽量关闭
        ],
    }
    

    局部配置

    from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
     renderer_classes = [JSONRenderer,BrowsableAPIRenderer]
    

    系统配置

    # rest_framework>views.py>APIView类
     renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    

    异常模块

    源码分析

    	# rest_framework>views.py>APIView(View)类内部的方法
        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
            # print(self.request) # <rest_framework.request.Request object at 0x000000000BCD6DA0>
            self.headers = self.default_response_headers  # deprecate?
    
            try:
                # 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
                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:
                # 异常模块 - 处理请求异常分支的
                response = self.handle_exception(exc)
            # 二次封装response,处理了结果 渲染
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
        
        # rest_framework>views.py>APIView(View)类内部的方法
        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地址,就独立存在该文件内
            exception_handler = self.get_exception_handler()
            # 给异常处理提供额外的参数
            context = self.get_exception_handler_context()  # exc异常对象,context中有视图对象和请求对象
            response = exception_handler(exc, context)
            # 默认的exception_handler函数只处理客户端异常形成response对象,服务器异常不做处理,返回None
            if response is None:
                self.raise_uncaught_exception(exc)
    
            response.exception = True
            return response
    
    
    
    # 在rest_framework>views.py独立存在的函数
    def exception_handler(exc, context):
        """
        Returns the response that should be used for any given exception.
    
        By default we handle the REST framework `APIException`, and also
        Django's built-in `Http404` and `PermissionDenied` exceptions.
    
        Any unhandled exceptions may return `None`, which will cause a 500 error
        to be raised.
        """
        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)
    
        return None
    

    自定义异常处理函数

    # settings.py配置
    REST_FRAMEWORK = {
        # 异常模块:
        # 系统异常处理函数:
        # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
        # 自定义异常处理函数:指向自己定义的exception.py 文件中的exception_handler函数
        'EXCEPTION_HANDLER': 'api.exception.exception_handler',
    }
    
    # api>exception.py
    # 在settings文件中将异常模块配置自己的异常处理函数
    from rest_framework.response import Response
    from rest_framework.views import exception_handler as drf_exception_handler
    # 先交给drf处理客户端异常,如果结果response为None代表服务器异常,自己处理
    def exception_handler(exc,context):
        response = drf_exception_handler(exc,context)
        detail = '%s-%s-%s' % (context.get('view'), context.get('request').method, exc)
        if not response: # 服务器端错误
    
           response =  Response({'detail':detail})
        else:
           response.data = {'detail':detail}
    
        # 要将response.data.get('detail)信息记录下来
        # print(response.data.get('detail'))
       # < api.views.BookAPIViewsobjectat0x000000000B9FA3C8 > -PUT - 方法 “PUT” 不被允许。
        return response
    
  • 相关阅读:
    Qt中的SIGNAL和SLOT
    Android单个模块编译
    decoupling of objetctoriented systems
    设计模式之Objectifier
    代码示例:调用SPS提供的remoting服务,在线把Office文档转换成html文档
    利用WSS做后台存储设计一个统一的信息发布平台
    元数据(metadata)在企业应用开发中的作用
    面向对象的软件设计中应当遵守的原则
    使用NUnit在.Net编程中进行单元测试
    最近在使用sps类库过程中发现了一个让我比较疑惑的问题(有关items属性的)
  • 原文地址:https://www.cnblogs.com/zhangchaocoming/p/12116306.html
Copyright © 2020-2023  润新知