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')
    })


好了,结束。

posted @ 2019-11-20 17:03  chanyuli  阅读(223)  评论(0编辑  收藏  举报