DRF简单配置使用和源码解析

什么是DRF?

Django REST framework的简写,主要是因为前后端分离出现的,主要用来写API,为前端提供数据接口。

为啥要有DRF?

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

DRF安装

pip intsall djangorestframework

DRF的使用

导入模块,让类继承APIView

from rest_framework.views import APIView
class Book(APIView):
	pass

APIView

APIView是DRF的核心,DRF所有的组件都是通过APIView分发的

源码部分其实和CBV的源码类似,可以参考https://www.cnblogs.com/zx125/p/11891794.html

源码解析

和之前的源码分析一样,路由匹配那个as_view一定返回的是个函数,只是这个as_view调用的是APIView的

APIView的as_view粗看就是去除csrf验证,其实不然

    @classmethod
    #这个cls就是CBV的C类对象,参数为空
    def as_view(cls, **initkwargs):
    	#将原始类存储在view函数上。
		当我们做url的路由反向查找时,我们能够在查看发现有关view的信息。
		用于view对象生成。
		#通过翻译我们可以知道下面部分代码的大致功能
        """
        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明显没有queryset这个属性,所以取None,这个if就进不去
        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
		#所以就执行这里,这里我们发现竟然是调用父类的as_view!
        view = super().as_view(**initkwargs)
        #给vue对象加入了自定义的类对象信息(比如book类)
        view.cls = cls
        #initkwargs为空
        view.initkwargs = initkwargs
		
		#调用父类的view是装上scrf验证的,这里把他们全部去掉,因为前后端分离设计,不需要这个
        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)

核心-dispatch

view

        #当路由匹配之后将执行这个函数
        def view(request, *args, **kwargs):
        	#self就是book类的实例了
            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
            #注意这个dispatch是APIView的,这个是核心!!!!!!!!!!!!!!!!!!!!
            return self.dispatch(request, *args, **kwargs)

APIView的dispatch

这里只做大致描述,细节看下面分块解析

主要功能分块

    def dispatch(self, request, *args, **kwargs):
    	#.dispatch()`与Django的常规调度几乎相同,
         但带有用于启动,完成和异常处理的额外挂钩。
        """
        `.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

请求模块新request

特点

drf的request是在wsgi的request基础上再次封装,wsgi的request作为drf的request一个属性:_request

新的request对旧的request做了完全兼容,新的request对数据解析更规范化:

​ 1.所有拼接url的参数都解析到query_params中

​ 2.所有数据包数据都被解析到data中

query_params和data属于QueryDict类型,可以 .dict() 转化成原生dict类型

源码分析

核心代码延展

        #函数传入一个原始的request返回新的request,这里就是一个二次封装,self为book实例
        #下面查看initialize_request源码
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request

initialize_request

    def initialize_request(self, request, *args, **kwargs):
    	#返回初始请求对象。
        """
        Returns the initial request object.
        """
        
        #准备要解析的内容字典
        parser_context = self.get_parser_context(request)
		
		#返回的request是由这个Request生成的
		#下面查看Request的源码
        return Request(
            request,
            #解析模块,在封装原生request时,将数据一并解析了
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

Request

1.Request对原生的request进行了二次封装

2.Request还兼容了request

二次封装

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
        	#'request`参数必须是“django.http.HttpRequest”的实例
             而不是“ {},{}”。
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )
        
        #把原生的request作为新的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找是否有这个方法,没有再从新的request里面找,以此完成兼容

    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)

response

from rest_framework.views import Response

渲染数据模块

1、可以在视图类中通过renderer_classes类属性对该视图的数据响应渲染做配置 - 局部配置
2、可以在项目的配置文件的drf配置中通过DEFAULT_RENDERER_CLASSES对该视图的数据响应渲染做配置 - 全局配置
注:如果一个视图类在有全局配置下,还进行了局部配置,优先走自己的局部配置

渲染模式

1.JSONRenderer(返回数据json渲染)

2.BrowsableAPIRenderer(返回数据界面渲染,注意使用这个需要注册组件,因为用到了组件的视图)

    # drf必须注册
    'rest_framework',

局部配置

在view中配置

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

全局配置

# drf的配置
REST_FRAMEWORK = {
    # 渲染模块的全局配置:开发一般只配置json,为了安全和保密技术
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ]
}

源码解析

渲染在view中的代码

		#响应的渲染
		#和request相似,也进行了封装
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

finalize_response

     def finalize_response(self, request, response, *args, **kwargs):
     	#返回最终的response对象
        """
        Returns the final response object.
        """
        #判断response是不是HttpResponseBase类的实例,不是就不往下执行
        # 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)
        )
		
		#判断是不是使用了DRF的Response
        if isinstance(response, Response):
        	#判断request是否配置了accepted_renderer,没有的话,进入方法
            if not getattr(request, 'accepted_renderer', None):
            	#查看下面解析
                neg = self.perform_content_negotiation(request, force=True)
                request.accepted_renderer, request.accepted_media_type = neg
                
			#这个accepted_renderer.render()会在中间件触发,去渲染renderer_context
            response.accepted_renderer = request.accepted_renderer
            response.accepted_media_type = request.accepted_media_type
            #渲染数据
            response.renderer_context = self.get_renderer_context()
		
		#向响应中添加新的variable标头,而不是覆盖。
        # 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

get_renderers

    def get_renderers(self):
    	#实例化并返回此视图可以使用的渲染器列表。
        """
        Instantiates and returns the list of renderers that this view can use.
        """
        #这里要注意renderer_classes的查找顺序
        #1.view类->APIView类->项目的配置文件->drf的默认配置APIsettings
        #列表生成式返回渲染器对象列表
        return [renderer() for renderer in self.renderer_classes]

核心

可以全局和局部配置视图类支持的结果渲染:默认可以json和页面渲染,使用模块的目的是开发可以全局只配置json方式渲染

解析模块

服务的对象是数据包数据

注意

解析模块和,渲染模块类似,不进行源码详细分析

使用

from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
# JSONParser: json数据
# FormParser: urlencoded
# MultiPartParser:form-data

1、可以在视图类中通过parser_classes类属性对该视图的数据包解析做配置 - 局部配置

# 解析模块的局部配置
# parser_classes = [JSONParser, MultiPartParser, FormParser]

2、可以在项目的配置文件的drf配置中通过DEFAULT_PARSER_CLASSES对该视图的数据包解析做配置 - 全局配置

# drf的配置
REST_FRAMEWORK = {
    # 解析模块的全局配置
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ]
}

源码解析

其实和request请求是一起执行的

self.get_parser_context(request)提供要解析的数据,self.get_parsers()提供解析的类对象(内部从配置中找解析类)

    def initialize_request(self, request, *args, **kwargs):
    	#返回初始请求对象。
        """
        Returns the initial request object.
        """
        
        #准备要解析的内容字典
        parser_context = self.get_parser_context(request)
		
		#返回的request是由这个Request生成的
		#下面查看Request的源码
        return Request(
            request,
            #解析模块,在封装原生request时,将数据一并解析了
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

核心

请求的数据包格式会有三种(json、urlencoded、form-data),drf默认支持三种数据的解析,可以全局或局部配置视图类具体支持的解析方式

异常模块

作用

主要是项目上线的日志记录,日志级别为error,否则太占资源

自定义异常的使用

drf出现异常了,都会回调exception_handler函数,携带并异常对象和异常相关信息内容,在exception_handler函数完成异常信息的返回以及异常信息的logging日志和其他操作

1.settings配置

# drf的配置
REST_FRAMEWORK = {
    # 异常模块
    # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'EXCEPTION_HANDLER': 'api.utils.exception_handler',
}

2.编写异常类

from rest_framework.response import Response

def exception_handler(exc, context):
    # 开发阶段一定要记录日志
    # logging.error(exc)
    return Response('%s - %s' % (context['view'].__class__.__name__, exc))

源码解析

		#异常处理
        except Exception as exc:
        	#捕获异常,将异常对象传给handle_exception
        	#源码看下面
            response = self.handle_exception(exc)

handle_exception

    def handle_exception(self, exc):
    	#透过返回数据或者抛出异常来处理任何发生的错误
        """
        Handle any exception that occurs, by returning an appropriate response,
        or re-raising the error.
        """
        #如果是这两个异常,就直接drf处理了,如果不是就交给下面处理
        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()
		
		#异常信息字典
        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

默认异常处理函数

APIException是所有drf异常的基类,如果不是drf能处理的异常,就返回None,交给原生的异常处理

def exception_handler(exc, context):
	#返回应用于任何给定异常的响应。
     默认情况下,我们处理REST框架APIException,
     Django的内置`Http404`和`PermissionDenied`异常。
     任何未处理的异常都可能返回“ None”,这将导致500错误
     被抛出
    """
    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

升级版自定义异常

思路,先交给drf默认的异常处理,处理不了,在我们进行处理

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)
    
	# drf没有处理的异常(服务器异常)
    if response is None: 
        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')
    })

响应模块

Response类生成对象需要的参数,以及Response类的对象可以使用的属性
1、参数:Response(data=响应的数据, status=响应的网络状态码, headers=想通过响应头再携带部分信息给前端),content_type默认为json
2、属性:response.data response.status_code response.status_text

源码解析

作为最后Response,只要注意几个参数的含义就行了

    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

参考链接

https://www.cnblogs.com/haitaoli/p/10293367.html

https://www.jianshu.com/p/fc603f48f100

posted @ 2019-11-19 23:49  zx125  阅读(551)  评论(0编辑  收藏  举报