zoukankan      html  css  js  c++  java
  • DRF安装配置-源码分析

    DRF安装配置

    什么是DRF

    Django REST framework 的简写,主要是为前后端分离服务的,用来写api,为前端提供数据接口。

    为什么要有DRF

    虽然我们即使不适用DRF,一样能够写出满足RESTful规范的接口,但是选择使用DRF作为工具可以提高开发效率,因为他不仅能够快速帮我们设计出符合规范的接口,还提供了 权限、认证等强大的功能。

    DFF安装命令

    cnpm install djangoframework
    
    

    DRF的使用

    导入模块,让类继承APIView

    class BookAPIView(APIView):
        # 渲染模块的局部配置
        # 局部禁用就是配置空list:[]
        renderer_classes = [JSONRenderer, BrowsableAPIRenderer]
    
        # 解析模块的局部配置
        # parser_classes = [JSONParser, MultiPartParser, FormParser]
    
        def get(self, request, *args, **kwargs):
            print(request._request.GET)
            print(request.GET)
            print(request.META)
    
    
            return Response({
                'status': 0,
                'msg': 'get ok'
            })
    
        def post(self, request, *args, **kwargs):
            # print(request._request.POST)
            # print(request.POST)
            # print(request.META.get('HTTP_AUTH'))
    
    
            # QueryDict转化为原生dict
            # print(request.query_params.dict())
            # print(type(request.data))
            # if isinstance(request.data, QueryDict):
            #     request.data.dict()
    
    
            print(request.data)
    
            print(a)
    
            return Response({
                'status': 0,
                'msg': 'post ok',
            })
    
    
    
    
    

    源码分析

    drf 需要我们写的类去继承 APIView 类,进入APIView看一下源码。

    因为我们要调用的方式就是以下(CBV)

    from django.conf.urls import url
    
    from . import views
    urlpatterns = [
        url(r'^books/$', views.BookAPIView.as_view()),
    ]
    

    直接调用 as_view( ),所以我们就直接看APIView的as_view方法。

    class APIView(View):
    
        # The following policies may be set at either globally, or per-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
    
        # Allow dependency injection of other settings to make testing easier.
        settings = api_settings
    
        schema = DefaultSchema()
    
        @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.
            """
            # 这里判断 cls(我们写的继承了 APIView 的类)是否是queryset的子类,答案是“不是” ,所以这里就不用看他,直接看下面跳到下面的 2
            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
     # 2 这里调用了父类的 as_view方法。
            view = super().as_view(**initkwargs)
            view.cls = cls
            view.initkwargs = initkwargs
    
            # Note: session based authentication is explicitly CSRF validated,
            # all other authentication is CSRF exempt.
            return csrf_exempt(view)
    
    

    毫无疑问的是 APIView 是继承View写的。我们可以看到 as_view 方法中调用了 父类的 as_view方法,这就啥都不用说了,肯定是在原来的方法上做了一些升级,从注释中都可以看出,drf 重写了 as_view 做的唯一的升级就是 “csrf_exempt(view)” ,让 view 避开csrf校验,可见drf也觉得csrf这个校验不太好用。

    进入父类as_view方法

    然后我们就进入这个方法看看有什么不一样(其实一样,只不过查找到的东西不一样了)。

        @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("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):
                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)
            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
    

    在上面代码中我表明了 self.dispatch 方法是最关键的一步,也是 drf最核心的部分。

    这里的 dispatch 一定不能用ctrl加左键点进去,因为他会找到 View 的dispatch,但其实我们drf自己写了,也就是在 APIView 中有这个方法,按照查找顺序,我们是会先去找我们自己写的类中有没有,然后再去父类 APIView 中找,再去 View中找,所以这里找到的是 APIView中的 dispatch。

    进入 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:
                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)
    
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
    

    这一部分的代码,就是 drf 最核心的代码了。

    一步一步来看,先看我标注的 对request进行了二次封装,进入源码来看一下。

    一、请求模快

    进入 self.initialize_request()方法

    进入这个方法看一下是怎么实现对 request 进行二次封装的。

        def initialize_request(self, request, *args, **kwargs):
            """
            Returns the initial request object.
            """
            # 这是解析模块,我们后面会讲。
            parser_context = self.get_parser_context(request)
    		
            #这里返回了一个 Request类实例化的对象,把我们的request丢了进去。
            return Request(
                request,
                parsers=self.get_parsers(),
                authenticators=self.get_authenticators(),
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
    

    想必对 request 进行二次封装的步骤就一定是 Request() 做的了,看一下他实例化的对象是怎么样的。

    进入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__)
            )
    		# 这里用 _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,)
    

    然后, 无论你是 request.a 或者 request.b 随便点什么,是不是都会走 request 的getattr方法?

    看一下他的 getattr 方法

     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)
    

    发现他会先去 _request 里面找,然后找不到了,再去自己里面找,__getattribute__方法是用 C 写的,总之是返回自己的属性的。所以就有了以下结论:

     drf的请求模块
    1、drf的request是在wsgi的request基础上再次封装
    2、wsgi的request作为drf的request一个属性:_request
    3、新的request对旧的request做了完全兼容
    4、新的request对数据解析更规范化:所有的拼接参数都解析到query_params中,所有数据包数据都被解析到data中
            query_params和data属于QueryDict类型,可以 .dict() 转化成原生dict类型
    

    小总结:

    """ 源码分析
    1、drf的APIView类:重写了as_view(),但主体逻辑还是调用父类View的as_view(),局部禁用了csrf认证
        重点:所有继承drf的基本视图类APIView的视图类,都不在做csrf认证校验
    2、drf的APIView类:重写了dispatch(),在内部对request进行了二次封装:self.initialize_request(request, *args, **kwargs)
        内部核心:
            走drf的Request初始化方法__init__:self._request = request
            drf的Request的getter方法__getattr__:先从self._request反射取属性,没取到再冲drf的request中取
    """
    """
    核心:request除了可以访问原wsgi协议的request所有内容,还可以访问 query_params、data
    

    这就是drf请求模块的流程,接下来讲一下drf渲染模块的流程。

    二、渲染模块

    """ drf的渲染模块(了解)
    这个模块就是决定最后你是可以用浏览器来访问还是json等等
    1、可以在视图类中通过renderer_classes类属性对该视图的数据响应渲染做配置 - 局部配置
    2、可以在项目的配置文件的drf配置中通过DEFAULT_RENDERER_CLASSES对该视图的数据响应渲染做配置 - 全局配置
    注:如果一个视图类在有全局配置下,还进行了局部配置,优先走自己的局部配置
    """
    

    源码分析:

    这次就不用再从头开始找了,因为之前讲过了最重要的部分就是重写的 dispatch。所以直接从 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:
                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了
                response = self.handle_exception(exc)
    		# 这里对response进行了二次处理响应,内部完成了多种结果的渲染方式。
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
    

    把已经得到的response结果和request丢进这个方法里进行二次处理,然后再返回。

    进入finalize_response()方法

        def finalize_response(self, request, response, *args, **kwargs):
            """
            Returns the final response object.
            """
            # Make the error obvious if a proper response is not returned
            # 这是断言,判断你的response 是不是 HttpResponseBase的子类,断言通过的话就会继续往下走代码,没通过就会抛出异常,这里的意思就相当于是,如果你的视图函数没有返回 HttpResponse 类的对象,就会报错,也叫作响应基类。这里能通过,所以往下走。
            assert isinstance(response, HttpResponseBase), (
                'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
                'to be returned from the view, but received a `%s`'
                % type(response)
            )
    		# 这里继续走,因为我们的response 是Response的子类。
            if isinstance(response, Response):
                # 第二个参数代表是允许的渲染类,去request里面拿,默认我们是没有写的,所以这里会进这个if。
                if not getattr(request, 'accepted_renderer', None):
                    #内部解析了配置的渲染类
                    neg = self.perform_content_negotiation(request, force=True)
                    #这一步是解压赋值,就是解压缩,前面接受的是允许的渲染类,后面接受的是允许的渲染头的类型,所以说上一步的 neg 一定会被赋值一个元组,而且元组里面一定会有一个渲染类,进去看一下这个方法做了什么 ,accepted_media_type里面就是表示到底是渲染 json 还是 标签 还是页面
                    request.accepted_renderer, request.accepted_media_type = neg
    #  2        这里把一个一个东西都丢给了response,格式化他,然后要怎么渲染?这里是交给中间件来完成的,中间件会做这件事 response.accepted_renderer.render(response.renderer_context),来进行渲染,到此为止,再往下就会有很多很多东西,不用在看了。
                response.accepted_renderer = request.accepted_renderer
                response.accepted_media_type = request.accepted_media_type
            	#这个就是他的内容,有view,有args,有kwargs,和request请求相对应的信息。
                response.renderer_context = self.get_renderer_context()
    
            # Add new vary headers to the response instead of overwriting.
            vary_headers = self.headers.pop('Vary', None)
            if vary_headers is not None:
                patch_vary_headers(response, cc_delim_re.split(vary_headers))
    
            for key, value in self.headers.items():
                response[key] = value
    
            return response
    

    进入perform_content_negotiation( )方法

     def perform_content_negotiation(self, request, force=False):
            """
            Determine which renderer and media type to use render the response.
            """
            #进入这个方法看一下 renderers 是什么东西。
            renderers = self.get_renderers()
            conneg = self.get_content_negotiator()
    
            try:
                return conneg.select_renderer(request, renderers, self.format_kwarg)
            except Exception:
                if force:
                    #这里确实返回了一个元组,就看看是什么东西吧,看上面
                    return (renderers[0], renderers[0].media_type)
                raise
    

    进入 get_renderers 方法

     def get_renderers(self):
            """
            Instantiates and returns the list of renderers that this view can use.
            """
            return [renderer() for renderer in self.renderer_classes]
    

    是一个列表推导式,要从 self.renderer_classes 里面拿,诶,这里就可以和我开始写的红字里的第一条对上了 *** “可以在视图类中通过renderer_classes类属性对该视图的数据响应渲染做配置 - 局部配置”***

    他是怎么查找的呢,先从自己找,这里的self就是我们自己写的类本身,然后如果我们没有设置的话,就会去找父类的,父类,也就是 APIView 中的。

    # 第一行就是,父类也是先去自己的配置文件中找,配置文件又先走自己的配置文件,然后再去找默认的配置文件。
    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
    

    查找流程:从自己的视图类(局部配置) > APIView的类属性(从配置文件中找) >自己的项目配置文件(全局配置) > drf磨人的配置文件

    在回去看刚才的列表推导式,在renderer后面加了一个 括号,这就是实例化对象了,这个这个列表里面存放的就是一个一个的渲染类的实例化对象。

    继续回到上面 finalize_response() 方法 中看 2

    小总结:

    """ 渲染模块源码分析
    1、二次处理响应对象:APIView的dispatch方法 - self.finalize_response(request, response, *args, **kwargs)
    2、获取渲染类对象:进入finalize_response方法 - self.perform_content_negotiation(request, force=True)
    3、从配置文件中得到渲染类对象:perform_content_negotiation -> self.get_renderers() -> [renderer() for renderer in self.renderer_classes]
    """
    """
    核心:可以全局和局部配置视图类支持的结果渲染:默认可以json和页面渲染,学习该模块的目的是开发可以全局只配置json方式渲染
    """
    
    

    解析模块

    """ drf的解析模块(了解) - 服务的对象是数据包数据
    这个模块的作用是来解析你传来的数据的,因为有可能你传来的是 formdata类型,也有可能是json类型,它需要解析。
    1、可以在视图类中通过parser_classes类属性对该视图的数据包解析做配置 - 局部配置
    2、可以在项目的配置文件的drf配置中通过DEFAULT_PARSER_CLASSES对该视图的数据包解析做配置 - 全局配置
    """
    

    解析数据是在哪里完成的呢?

     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 进行了二次封装,就是在这里面完成的,然后丢尽了data和query_params里面,再次进入这个方法看一下
            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 = 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):
            """
            Returns the initial request object.
            """
            #准备要解析的内容,看一下这个方法返回的是什么。
            parser_context = self.get_parser_context(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', {})
            }
    
    

    所以这个方法返回的就是一个字典。那么parser_context就是一个字典了。

    进入get_parsers()方法

        def get_parsers(self):
            """
            Instantiates and returns the list of parsers that this view can use.
            """
            #看到这,就知道他能完成局部配置和全局配置
            return [parser() for parser in self.parser_classes]
    

    所以,可以在自己写的类里面写上

    parser_classes = [JSONParser, MultiPartParser, FormParser],这就会允许解析 json类的数据、form-data、urlencoding。

    # JSONParser: json数据
    # FormParser: urlencoded
    # MultiPartParser:form-data
    

    至于这个在哪里可以看到呢,在parsers里面

    from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
    
    #drf默认配置了下面前三个
    class JSONParser(BaseParser)
    class FormParser(BaseParser)
    class MultiPartParser(BaseParser)
    class FileUploadParser(BaseParser)
    

    查找顺序和渲染模块一样。 也可以在settings文件里面自己配置

    REST_FRAMEWORK = {
        # 渲染模块的全局配置:开发一般只配置json
        'DEFAULT_RENDERER_CLASSES': [
            'rest_framework.renderers.JSONRenderer',
        ],
        # 解析模块的全局配置
        'DEFAULT_PARSER_CLASSES': [
            'rest_framework.parsers.JSONParser',
            'rest_framework.parsers.FormParser',
            'rest_framework.parsers.MultiPartParser'
        ],
    
        # 异常模块
        # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
        'EXCEPTION_HANDLER': 'api.utils.exception_handler',
    }
    

    小总结

    """ 解析模块源码分析
    1、APIView的dispatch方法:self.initialize_request(request, *args, **kwargs)内部还提供了数据解析
    2、self.get_parser_context(request)提供要解析的数据,self.get_parsers()提供解析的类对象(内部从配置中找解析类)
    """
    """
    核心:请求的数据包格式会有三种(json、urlencoded、form-data),drf默认支持三种数据的解析,可以全局或局部配置视图类具体支持的解析方式
    """
    

    所以 渲染模块和解析模块 最重要的就是局部配置和全局配置。会配置就行了。

    异常模块

    """ 异常模块(重点):重写异常模块目的是记录异常信息(项目上线)
    1、在settings的drf配置中配置EXCEPTION_HANDLER,指向自定义的exception_handler函数
    2、drf出现异常了,都会回调exception_handler函数,携带 异常对象和异常相关信息内容,
        在exception_handler函数完成异常信息的返回以及异常信息的logging日志
    """
    

    先讲怎么去使用异常模块,然后再分析源码

    先在自己的应用下配置一个文件 utils,然后写一个 exception_handler.py 文件。这个文件里面写 exception_handle(exc, context) 函数,然后在settings里面配置,有异常的时候走这个函数。

    settings.py

    REST_FRAMEWORK = {
        # 渲染模块的全局配置:开发一般只配置json
        'DEFAULT_RENDERER_CLASSES': [
            'rest_framework.renderers.JSONRenderer',
        ],
        # 解析模块的全局配置
        'DEFAULT_PARSER_CLASSES': [
            'rest_framework.parsers.JSONParser',
            'rest_framework.parsers.FormParser',
            'rest_framework.parsers.MultiPartParser'
        ],
    
        # 异常模块
        # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
        'EXCEPTION_HANDLER': 'api.utils.exception_handler',
    }
    
    

    exception_handle(exc, context) 函数最初的版本

    from rest_framework.response import Response
    
    def exception_handler(exc, context):
        # 开发阶段一定要记录日志
        # logging.error(exc)
        #因为只能返回字符串,所以要用这种形式来写
        return Response('%s - %s' % (context['view'].__class__.__name__, exc))
    
    

    接下来分析源码,我们知道的,所有的入口都是 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 = 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 = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
                else:
                    
                    handler = self.http_method_not_allowed
    			
                #也有可能在这里,函数执行的时候。
                response = handler(request, *args, **kwargs)
    		#捕获到了一个异常,命名为 exc
            except Exception as exc:
                #然后调用这个方法,我们配置的叫做 exception_handle,所以对应的还不是我们写的那个。   这个方法就是异常模块
                response = self.handle_exception(exc)
    
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
    

    然后我们去看一下handle_exception(exc)方法

        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()
    		#这个就是内容,view 啊 args啊 kwargs啊这些的,和上面得到的异常对象一起丢给下面的异常处理函数,由于上面的那个步骤,这里的异常处理函数已经是我们自己写的那个 exception_handler了。
            context = self.get_exception_handler_context()
            #异常函数:接收 exc, context,返回response
            response = exception_handler(exc, context)
    		
            #如果是response不是none,就代表drf自己处理了。那什么时候会为none呢?看下面,当我们不自己配,用drf的异常处理的时候会怎么样。
            if response is None:
                #交给中间件处理,原生django处理
                self.raise_uncaught_exception(exc)
    		
            #告诉前台是异常返回
            response.exception = True
            return response
    
        def get_exception_handler(self):
            """
            Returns the exception handler that this view uses.
            """
            #再熟悉不过了,你有,就找你的,没有就找系统的
            return self.settings.EXCEPTION_HANDLER
    

    当我们不自己配,用drf的异常处理的时候,先看一下drf自己的 exception_handler函数

    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.
        """
        #这里判断异常是不是 属于 404 这种类型的
        if isinstance(exc, Http404):
            exc = exceptions.NotFound()
        #这里判断异常是不是关于权限的
        elif isinstance(exc, PermissionDenied):
            exc = exceptions.PermissionDenied()
    	#这里判断异常是不是属于基类的,就是drf最大的异常收集范围了,如果超出了这个,就说明你的异常比这个基类还要大,就是原生的异常了,类似于什么没定义就调用某个变量这种的。
        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()
            #如果处理的了的话,就返回一个response对象
            return Response(data, status=exc.status_code, headers=headers)
        #上面这个过程就相当于是drf在处理他能处理的范围,超出了能处理的范围的话,就返回none
    	#处理不了的时候就返回none,让django来处理。
        return None
    

    以上是drf处理的时候

    接下来再看一下我们自己处理得时候

    升级版的自定义异常处理

    升级版
    from rest_framework.response import Response
    from rest_framework.views import exception_handler as drf_exception_handler
    def exception_handler(exc, context):
        #默认是drf可以解决的异常
        response = drf_exception_handler(exc, context)
    	#当drf不能解决时,就会是none,这时候就直接返回一个响应
        if response is None: # drf没有处理的异常
            response = Response({'detail': '%s' % exc})
    
        # 项目阶段,要记录到日志文件
        return response
    

    小总结

    """ 源码分析
    1、在APIView的dispatch方法中,有一个超大的try...except...,将代码运行异常都交给异常处理模块处理self.handle_exception(exc)
    2、从配置中映射出配置处理异常的函数(自定义异常模块就是自定义配置指向自己的函数):self.get_exception_handler()
    3、异常函数exception_handler(exc, context)处理异常,就会走自己的:
        先交给系统处理(客户端的异常),系统没处理(服务器异常),再自己处理
    """
    """
    核心:异常信息都需要被logging记录,所以需要自定义;drf只处理客户端异常,服务器异常需要手动处理,统一处理结果
    """
    

    响应模块

    响应模块其实就是response,所以直接去看response的源码就好了

    class Response(SimpleTemplateResponse):
        """
        An HttpResponse that allows its data to be rendered into
        arbitrary media types.
        """
    	#从升级版可以看出,这里的data就是你传进来的exc,status就是状态码
        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
    

    所以我们为什么要搞出一个终极版,因为之前的版本你出错了,状态码都是200,这不对,所以要自己给他配置响应状态码。

    终极版:

    """ 究极版
    response = {
        'status': 7,
        'exc': '异常信息'
    }
    """
    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没有处理的异常(服务器异常)
            #第二个参数就是500,
            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')
        })
    
    
    

    好了,结束。

  • 相关阅读:
    App更新之dialog数字进度条
    Android app启动是出现白屏或者黑屏如何解决?
    Tensorflow报错:AttributeError: module 'tensorflow._api.v1.io' has no attribute 'gfile'
    《Python深度学习》第三章阅读笔记
    在Ubuntu 18.04上配置CPU深度学习环境
    《Python深度学习》第二章阅读笔记
    《Python深度学习》第一章阅读笔记
    POJ 1118 Lining Up
    蓝桥杯-地宫取宝(动态规划)
    洛谷P2280[HNOI2003] 激光炸弹(二维前缀和)
  • 原文地址:https://www.cnblogs.com/chanyuli/p/11898933.html
Copyright © 2011-2022 走看看