• 【Django】DRF源码分析之五大模块


    纸上得来终觉浅,绝知此事要躬行。

    前言

    在之前的DRF源码分析对比原生Django介绍了二者的区别,最后分析得出DRF对原生的dispatch方法做了很多改进,本章就接着分析APIView下的dispatch到底做了那些事?

    通过上次的分析主要分为以下几个模块:

    • 请求模块
    • 认证模块(本次不做介绍)
      • 认证
      • 权限
      • 限流
    • 响应模块
    • 异常模块
    • 渲染模块

    请求模块

    1. 源码入口,rest_framework/views.py下的APIView类中的dispatch方法:request = self.initialize_request(request, *args, **kwargs)ctrl+b进入:
        def initialize_request(self, request, *args, **kwargs):
            """
            Returns the initial request object.
            """
            parser_context = self.get_parser_context(request)
            
            # 返回Request类的对象
            return Request(
                request,
                parsers=self.get_parsers(),
                authenticators=self.get_authenticators(),
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
    
    1. 进入Request类的源码,查看实例化的过程简化版:
    class Request:
        def __init__(self, request, parsers=None, authenticators=None,
                     negotiator=None, parser_context=None):
            ......
            # 二次封装request,将原生request作为drf Request 对象的 _request属性
            self._request = request
            ......
    
        @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
    
        def __getattr__(self, attr):
            try:
                return getattr(self._request, attr)
            except AttributeError:
                return self.__getattribute__(attr)
    

    分析:

    1. 将wsgi的request对象转化成drf的Request类的对象
    2. 封装后的request对象完全兼容wsgi的request对象,并且将原request保存在新request._request
    3. 重写格式化请求数据存放位置

    例如获取请求参数:

    print(request._request.method)  # 在内部将wsgi的request赋值给request._request
    print(request.method)  # 就是通过__getattr__走的是request._request.method
    print(request.query_params)  # 走的是方法属性,就是给request._request.GET重新命名
    print(request.data)  # 走的是方法属性,值依赖于request._full_data
    

    通过源码和分析,我们了解到在dispatch中走到request = self.initialize_request(request, *args, **kwargs)调用initialize_request方法返回的是rest_framework/request.py下的Request类的对象,这个类在实例化的过程中对原生参数request做了进一步封装和兼容,最终在返回到dispath中的request,整个请求模块彻底走完。

    响应模块

    1. 接下来dispatch运行到响应模块,代码如下:

    源码路径(rest_framework/views.py) dispatch()方法

    关键代码实现以及变量定义如下:

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
    
    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)
    

    通过判断类视图是否定义过http_method_names中的方法,不存设置handler=http_method_not_allowed,而http_method_not_allowed内部是直接抛出异常。

    1. 条件满足时,通过getattr反射机制,handler就是我们类视图定义的方法,执行response = handler(request, *args, **kwargs),返回response,原生的Django也是这样做的。

    异常模块

    1. 当上面的步骤出现错误时,异常捕获就会走到异常模块self.handle_exception(exc),点击参看源码,我做了一定的简化:
        def handle_exception(self, exc):
            ......
            # 获取处理异常的方法
            exception_handler = self.get_exception_handler()
            
            context = self.get_exception_handler_context()
            # 异常处理的结果
            response = exception_handler(exc, context)
    
            if response is None:
                # 没有异常内容,抛出异常信息
                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
    
    1. 最后其实返回的是self.settings.EXCEPTION_HANDLER,点击进入源码,我们可以发现:
    settings = api_settings
    
    api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
    
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    

    所以真正的异常处理是在rest_framework.views.exception_handler这个函数

    源码路径(rest_framework/views.py) exception_handler()方法

    1. 接下来异常处理走完,判断exception_handler返回的结果进行处理:
    if response is None:
        # 没有异常内容,抛出异常信息
        self.raise_uncaught_exception(exc)
    # 有异常内容,返回异常内容
    response.exception = True
    return response
    
    1. 至此异常模块也已经走完,通过上面的源码分析我们可以得知,如果我们想要自定义异常,只需要重新实现exception_handler函数,并且在settings.py文件中指定exception_handler函数的位置。

    例如:

    # 自定义drf配置
    REST_FRAMEWORK = {
        # 全局配置异常模块
        'EXCEPTION_HANDLER': 'api.exception.exception_handler',
    }
    
    ========================下面就是导入原始的函数,然后先实现其原有的逻辑,之后在处理自己的逻辑====================
    from rest_framework.views import exception_handler as drf_exception_handler # 防止与自定义的重名,起一个别名
    
    def exception_handler(exc, context): # 注意名称必须为`exception_handler`
        response = drf_exception_handler(exc, context)
        if response is None:
    
            view = context['view']
            if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
                # logger.error('[%s] %s' % (view, exc))
                response = Response({'detail': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
        return response
    

    渲染模块

    1. 从前面的请求===>响应===>异常,到现在的渲染,也就是dispatch的最后一部分,源码如下:
    def dispatch(self, request, *args, **kwargs):
        ......
        # 渲染模块
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    
    1. 点击finalize_response源码查看,进行一定的简化:
        def finalize_response(self, request, response, *args, **kwargs):
            """
            Returns the final response object.
            """
            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 # 拆包
            ......
    
    1. 点击进入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
    
    1. 进入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 = api_settings.DEFAULT_RENDERER_CLASSES
    
    1. api_settings中获取渲染模块的配置:
    'DEFAULT_RENDERER_CLASSES': [
            'rest_framework.renderers.JSONRenderer', # 只显示出json数据
            'rest_framework.renderers.BrowsableAPIRenderer', # 渲染出页面
        ],
    

    至于渲染类我们就不做过多的分析,分析到这我们发现渲染类和异常类很相似,同样我们可以在settings.py中配置,例如:项目上线之后我们只希望返回json格式的数据,因此可以定义如下配置:

    # 自定义drf配置
    REST_FRAMEWORK = {
        # 全局渲染类配置
        'DEFAULT_RENDERER_CLASSES': [
            'rest_framework.renderers.JSONRenderer',
            # 'rest_framework.renderers.BrowsableAPIRenderer',
        ]
    }
    

    当然我们在settings.py中配置都是全局配置,也可以在CBV的类视图中定义,因为我们继承了APIView,下面我们可以查看一下源码的配置:

    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
    

    这些配置项都在 rest_framework/settings.py下,有兴趣可以去查看,而且DRF的封装也比较规范,可以导入特定的类:

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.request import Request
    from rest_framework.serializers import Serializer
    from rest_framework.settings import APISettings
    from rest_framework.filters import SearchFilter     #过滤
    from rest_framework.pagination import PageNumberPagination  #分页
    from rest_framework.authentication import TokenAuthentication   #认证
    from rest_framework.permissions import IsAuthenticated   #权限(是否登录)
    from rest_framework.throttling import SimpleRateThrottle  #频率
    from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer # 渲染
    

    所以我们在类视图可以如下定义:

    from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
    
    class User(APIView):
        # 局部渲染配置
        renderer_classes = [JSONRenderer,BrowsableAPIRenderer]
    
        def get():
            pass
        ......
    

    OK,看到这里,差不多整个dispatch的代码执行流程全部走完了,除了一个三大认证以外,我打算用另一篇博文分析,休息一下继续肝。

    相关参考:http://www.manongjc.com/detail/14-zbmsyiqdjwviznh.html

  • 相关阅读:
    Linux下的”锁“事儿
    拿得起,放得下,想得开
    关于TCP协议握手的那些事儿

    C++中的RTTI机制解析
    C/C++中产生随机数
    数据库-事务和锁
    JS 数组Array常用方法
    C# 压缩 SharpZipLib
    正则表达式学习3-负向零宽断言
  • 原文地址:https://www.cnblogs.com/ydongy/p/13125065.html
Copyright © 2020-2023  润新知