从0学DRF(源码剖析)(一)—请求响应组件源码剖析

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
     # 请求模块,封装了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
         # 去http_method_names找请求的方法,如果没有找到,触发异常
         # http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
         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)

     # 进行请求的渲染,为什么postman中测试返回字符串,
     # 浏览器中测试返回一个Django rest framework那个漂亮的页面呢,就是这里实现的
     self.response = self.finalize_response(request, response, *args, **kwargs)
     return self.response

请求模块:request对象
request = self.initialize_request(request, *args, **kwargs)

点进去。self.initialize_request(request, *args, **kwargs)

 def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(  # 原生的request被封装成了Request,点进去看看
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

点进去看看Request,Request里面写了非常的多,主要有两个地方值得我们关注。

  • 第一:self._request = request,这句代码表示Django原生的request就是drf的_request。
  • 第二:Django rest framework它的__getattr__方法使得他兼容了Django原生的request,这样我们可以直接使用request.POST拿数据,也可以使用request._request.POST拿数据,具体看下面源码
    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)
基本使用

在view里边书写如下代码,使用postman分别发送post和get请求做测试

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
# Create your views here.
# APIView.dispatch()
class Book(APIView):
    def get(self, request):
        print(request.GET)      # 兼容原来的方式
        print(request._request.GET)    # 二次封装
        print(request.query_params)  # 扩展
        return Response('drf get is ok')

    def post(self, request):
        print(request._request.POST)
        print(request.POST)
        print(request.data)
        print(request.query_params)  # 扩展
        return Response('drf post is ok')

总结的规律

1) drf 对原生request做了二次封装,设置request._request等于原生request
2) 原生request对象的属性和方法都可以被drf的request对象直接访问(兼容)
3) drf请求的所有url拼接参数均被解析到query_params中,所有数据包数据都被解析到data中 (******)

get请求:url中拼接的参数通过request.query_params获取

post请求:所有请求方式所携带的数据包都是通过request.data获取

请求模块最终的结论就是:获取数据有两种方式 query_params和data(通过parser解析数据传到data)

解析模块

看完请求模块后,为了方便大家,我再贴一次initialize_request的源码,细心的读者可能刚刚看initialize_request的时候看到还有个parser,这是干什么的呢,有什么用?归纳为三点

1)drf给我们提供了多种解析数据包方式的解析类  form-data/urlencoded/json
2)我们可以通过配置来控制前台提交的哪些格式的数据后台在解析,哪些数据不解析
3)全局配置就是针对每一个视图类,局部配置就是针对指定的视图来,让它们可以按照配置规则选择性解析数据
     def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        返回初始化后的request对象。
        """
        """
        下面这行代码返回一个dict,该dict被传递给Parser.parse(),
        作为‘parser_context’关键字参数。
        """
        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
        )

我们点进去self.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的类里边实例化得来的,parser_classes是一个可迭代的对象,我们点进去parser_classes

    parser_classes = api_settings.DEFAULT_PARSER_CLASSES

点击api_settings

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

点击 DEFAULTS,会发现这里边其实写了好多类,因为有100多行,我就不全部贴出来了,因为实际上看到后面大家会发现里面drf的源码流程,很多都是根据列表生成式,去找到默认的类并实例化,返回一个实例列表,所以我们从这个源码里边就可以分析到,如果我们在我们自己的类里边去自定义列表生成式的那个类的列表,就可以实现局部的自定义了,因为这个列表是默认先去我们的类里面找,找不到再去drf的默认的类里面找,其实我们后面看权限,认证等源码也是基本这个流程,所以它这个局部配置都是去我们的类里边自定义一个相应的列表,下面给出作解析的默认的类列表

'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser', # json数据包
        'rest_framework.parsers.FormParser',  # urlcoding数据包
        'rest_framework.parsers.MultiPartParser'  # form-data数据包
    ],

如果想实现全局的配置,就去我们的配置文件里边,把这个列表copy过去,然后改成我们自己想要的就行

看完self.initialize_request之后,下面贴一份dispatch源码看看我们看到哪里了。因为版本,认证,权限,限流等功能都在initial里边,我们先不啃它

 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
     # 请求模块,封装了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
         # 去http_method_names找请求的方法,如果没有找到,触发异常
         # http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
         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)

     # 进行请求的渲染,为什么postman中测试返回字符串,
     # 浏览器中测试返回一个Django rest framework那个漂亮的页面呢,就是这里实现的
     self.response = self.finalize_response(request, response, *args, **kwargs)
     return self.response

initial,但是因为版本,认证,权限,限流等功能都在initial里边,我们先不啃它,以后再讲。
initial方法后有个判断,如果请求的方法与http_method_names里面的请求方法都不匹配,那就抛出异常,因为这里边有所有合法的请求方法,可看上面源码注释,看一下http_method_not_allowed,果然是抛异常的。

def http_method_not_allowed(self, request, *args, **kwargs):
    """
    If `request.method` does not correspond to a handler method,
    determine what kind of exception to raise.
    如果请求方法不对应于处理程序方法,
    确定要引发哪种异常。
    """
    raise exceptions.MethodNotAllowed(request.method)
异常模块

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
     # 请求模块,封装了Django原生的请求
     request = self.initialize_request(request, *args, **kwargs)
     self.request = request
     self.headers = self.default_response_headers  # deprecate?

     try:
         # 版本,认证,权限,限流等功能都在initial里边
         self.initial(request, *args, **kwargs)

         # Get the appropriate handler method
         # 去http_method_names找请求的方法,如果没有找到,触发异常
         # http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
         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)

     # 进行请求的渲染,为什么postman中测试返回字符串,
     # 浏览器中测试返回一个Django rest framework那个漂亮的页面呢,就是这里实现的
     self.response = self.finalize_response(request, response, *args, **kwargs)
     return self.response

接下来进入handle_exception查看

 def handle_exception(self, exc):
     """
     Handle any exception that occurs, by returning an appropriate response,
     or re-raising the error.
     处理任何发生的异常,通过返回适当的响应,
     或重新引发错误。
     """
     # 这里使用isinstance,进行三大认证的异常捕获,我们常常看到的403异常就是在这里
     if isinstance(exc, (exceptions.NotAuthenticated,
                         exceptions.AuthenticationFailed)):
         # WWW-Authenticate header for 401 responses, else coerce to 403
         # 认证头为401响应,否则强制到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': 'rest_framework.views.exception_handler',
     所以我们可以自定义get_exception_handler实现自定义异常
     """
     # 获取处理异常的句柄(方法)
     exception_handler = self.get_exception_handler()

     # context是一个字典,把它传给response作为参数
     context = self.get_exception_handler_context()
     # response是处理异常后得到的结果
     response = exception_handler(exc, context)

     if response is None:
         # 如果response为空,即没有拿到处理异常的结果,就直接抛出异常(中间件等捕获)
         self.raise_uncaught_exception(exc)

     response.exception = True
     # 如果response有结果,就返回response,response就是处理异常后得到的结果
     return response

先进入看看content返回的是啥,后面自定义异常用的着

 def get_exception_handler_context(self):
     """
     Returns a dict that is passed through to EXCEPTION_HANDLER,
     as the `context` argument.
     返回一个dict,该dict被传递给EXCEPTION_HANDLER,
     作为“context”参数。
     """
     return {
         'view': self,
         'args': getattr(self, 'args', ()),
         'kwargs': getattr(self, 'kwargs', {}),
         'request': getattr(self, 'request', None)
     }

可以看到get_exception_handler_context返回了一个字典,我们自定义异常的时候可以用字典的键来获取到异常信息。
接下来get_exception_handler()是获取处理异常的方法,那么我们点进去看看。

 def get_exception_handler(self):
     """
     Returns the exception handler that this view uses.
     返回此视图使用的异常处理程序。
     """
     return self.settings.EXCEPTION_HANDLER

它就只有一句话,可见,又是去默认的类中找到处理异常的方法。和之前其他功能的源码类似。我们一层层点进去

'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',

其实我们研究异常处理模块的目的是去自定义异常,为啥要自定义异常?以及如何自定义?且听我慢慢道来哈。

为啥要自定义异常?

1)所有经过drf的APIView视图类产生的异常,都可以提供异常处理方案
2)drf默认提供了异常处理方案(rest_framework.views.exception_handler),但是处理范围有限
3)drf提供的处理方案两种,处理了返回异常现象,没处理返回None(后续就是服务器抛异常给前台)
4)自定义异常的目的就是解决drf没有处理的异常,让前台得到合理的异常信息返回,后台记录异常具体信息

怎样自定义?

1)首先,从刚刚的源码中我们知道如果我们自己不自定义,drf是使用它默认的异常处理方法的。
2)这个方法我们没有必要重新写一遍,我们先交给drf的异常处理方法帮我们处理
3)然后如果response为空,表示drf没有处理到,这时候我们自己来处理
4)我们自己处理的话可以使用content这个字典来处理,上面源码也分析了它。

下面我们就来搞一下,先简单返回个server error
先在项目目录下创建exception.py文件

from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
from rest_framework import status
def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    if response == None:
        print(exc)
        print(context)
        return Response({
            'detail': 'server error.',
        })
    return response

修改自己的配置文件,导入我们写的异常处理方法的路径

# 修改自己的配置文件setting.py
REST_FRAMEWORK = {
    # 全局配置异常模块
    'EXCEPTION_HANDLER': 'api.exception.exception_handler',  #设置自定义异常文件路径,在api应用下创建exception文件,exception_handler函数
}

这样就OK了,我们只返回了很简单的server error信息等我们后面讲了请求模块后,再具体做处理

渲染模块

最后先说一下渲染模块,我们都知道我们的API返回一个字符串等数据的时候,postman返回的也仅仅是一个字符串,但是如果使用浏览器来测试的话会返回一个很漂亮的django rest framework的页面,这个就是通过对请求进行渲染实现的。再进入一下dispatch源码,看倒数第二行代码:

self.response = self.finalize_response(request, response, *args, **kwargs)
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
    # 请求模块,封装了Django原生的请求
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        # 版本,认证,权限,限流等功能都在initial里边
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        # 去http_method_names找请求的方法,如果没有找到,触发异常
        # http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
        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)

    # 进行请求的渲染,为什么postman中测试返回字符串,
    # 浏览器中测试返回一个Django rest framework那个漂亮的页面呢,就是这里实现的
    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

点进去self.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
        # 执行断言,如果没有返回正确的响应,则使错误变得明显
        assert isinstance(response, HttpResponseBase), (
            'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
            'to be returned from the view, but received a `%s`'
            % type(response)
        )

        if isinstance(response, Response):
            if not getattr(request, 'accepted_renderer', None):
                # 拿到运行的解析类,这里点进去重点看
                neg = self.perform_content_negotiation(request, force=True)
                request.accepted_renderer, request.accepted_media_type = neg

            response.accepted_renderer = request.accepted_renderer
            response.accepted_media_type = request.accepted_media_type
            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 = 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

点进去看self.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]

接下来去找到renderer_classes

'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',  # 只显示json数据
        'rest_framework.renderers.BrowsableAPIRenderer',  # 显示数据,并且渲染出页面
    ],

所以我们想要配置,就copy这段内容来改就行,比如我们在setting.py里面copy这段过去,把第二个类注释掉,浏览器就不会在返回页面了。
注意:Django rest framework这里还有个比较坑人的地方
请看它的注释

"""
Settings for REST framework are all namespaced in the REST_FRAMEWORK setting.
For example your project's `settings.py` file might look like this:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
    ],
}

This module provides the `api_setting` object, that is used to access
REST framework settings, checking for user settings first, then falling
back to the defaults.
"""

如果只看了它的注释就直接把那段内容拷贝到我们的settings里面,看源码不够仔细,就会以为渲染页面是使用TemplateHTMLRenderer这个类,但其实看源码我们知道它叫BrowsableAPIRenderer,博主一开始就中招了,发现浏览器报错说找不到那个模板文件,然后postman正常,找了半个小时才发现这里错了,哈哈哈。

drf请求生命周期

一个请求过来之后,需要先穿过中间件,依次执行中间件里边的process_request()方法,执行完process_request()之后,如果中间件里边实现了process_view()方法,再依次执行中间件各个process_view()方法,接下来就到了路由系统进行路由匹配,如果是FBV,路由匹配成功后,执行对应的视图函数,视图函数可以基于ORM去数据库中拿数据,也可以渲染模板,返回字符串给浏览器,服务器在返回响应给浏览器时,需要再次经过中间件,依次去执行process_response()方法,如果是CBV,那么会先执行源码里边的dispatch方法,dispatch()方法根据请求的类型找到对应的函数,比如我们的请求是GET形式,它会找到get()函数,执行里边的代码,然后把结果返回给浏览器,返回的过程中也要经过中间件,执行中间件的process_response方法。
未完待续

原创文章 85 获赞 120 访问量 4万+

猜你喜欢

转载自blog.csdn.net/happygjcd/article/details/105316944