前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >APIview的请求生命周期源码分析

APIview的请求生命周期源码分析

作者头像
GH
发布2019-12-26 16:10:56
8160
发布2019-12-26 16:10:56
举报

APIview的请求生命周期源码分析

Django项目启动=>加载settings文件=>加载models、views、urls文件,执行urls文件,调用视图类的as_view()方法。

APIview的as_view()方法继承父类的as_view()方法,并增加了局部禁用csrf中间件的功能

代码语言:javascript
复制
 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.
        """
        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空间,并将其类名与参数添加到自己类的属性
        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.
        
        #
        #局部禁用csrf认证
        return csrf_exempt(view)

APIview的父类就是Django的视图类view,as_view()继承父类的as_view功能调用dispatch方法分发请求,下面是父类的as_view()

代码语言:javascript
复制
 @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        '''略去代码'''
       ........................................................
        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)
        # 这里的self是视图类实例化的对象,不要搞错了
        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

视图类实例化的对象在自己的名称空间找dispatch方法,如果没有就去基类APIview里面查找,APIview的dispatch方法是对view类的dispatch方法的重写,对view类的dispatch方法进行了优化,具体优化一起来看APIview的dispatch方法源码:

代码语言:javascript
复制
 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
        # initialize_request二次封装request对象,并对request对象的内容进行解析
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            #三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大得多
            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)
                #如果wsgi的request里面有对应的方法,就用wsgi的,如果没有就用自己的,源码中常用的方法
            else:
                handler = self.http_method_not_allowed
                #如果自己也没有就报http_method_not_allowed异常

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            #异常处理模块,处理异常分支
            response = self.handle_exception(exc)
        #二次封装response,处理响应的内容,并对处理的结果进行渲染
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

总结一下重写的dispatch里面所完成的功能:

1.二次封装request对象,并对request对象的内容进行解析

2.调用函数initial对请求进行三大认证,并在这个过程中进行异常捕获

3.通过反射的方法执行通过认证的自定义请求如get、post、patch、delete等

4.如果上面2、3步执行过程中有异常,就调用handle_exception方法处理捕获到的异常。

5.通过finalize_response方法进行处理响应的内容,以及是否对内容进行渲染

以上就是Django rest framework源码的请求流程,下面我们粗略看一下请求模块、解析模块、相应模块、异常处理模块、渲染模块的源码。

请求模块

请求模块大致的功能如下:

1.将wsgi的request对象转换成drf的request类的对象

2.封装后的request对象完全兼容wsgi的request对象,并且将原来request对象保存在新request._request

3.重新格式化请求数据存放位置

拼接参数:request.query_params

数据包参数:request.data

代码语言:javascript
复制
# 源码分析:
# 入口:APIVIew的dispatch方法的 request=self.initialize_request(request, *args, **kwargs)
# print(request._request.method)  # 在内部将wsgi的request赋值给request._request

class Request:
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.

    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers_classes(list/tuple). The parsers to use for parsing the
          request content.
        - authentication_classes(list/tuple). The authentications used to try
          authenticating the request's user.
    """

    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__)
            #将wsgi的request类的名称空间全部存入自己的名称空间中达到对wsgi的request完全兼容。
        )

        self._request = request
        #将父类的request存放在了自己的_request中,这样我们可以通过对象点属性的方法方法wsgi request的属性和方法也可以通过对象点_request直接操作wsgi 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,)

    def _default_negotiator(self):
        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()

    @property
    def content_type(self):
        meta = self._request.META
        return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
    
     @property
    def query_params(self):
        """
        More semantically correct name for request.GET.
        """
        #对_request.GET属性重新命名为query_params
        return self._request.GET

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
            #对_full_data进行重新命名
        return self._full_data
    
    
     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:#通过__getattr__的方法获得_request的属性和方法
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)

解析模块

解析模块只处理数据包参数

代码语言:javascript
复制
# 源码分析:
# 入口:APIVIew的dispatch方法的 request=self.initialize_request(request, *args, **kwargs)
# 获取解析类:parsers=self.get_parsers(),
# 进行局部全局默认配置查找顺序进行查找:return [parser() for parser in self.parser_classes]
    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就会到达self.parser_classes属性,
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#这里是可以看出解析器在api_settings配置里配置
    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
    
    #在settings里面可以看到默认的解析器配置如下,也就是默认的解析器支持的数据类型有form-data,urlencoded,json
    DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',#json
        'rest_framework.parsers.FormParser',#urlencoded
        'rest_framework.parsers.MultiPartParser'#文件 form-data
    ],
   #这里是全局配置,我们可以在项目的settings文件中自定义配置我们使用的解析器

全局配置解析器

当我们将drf settings文件中进行如下配置后再启动项目就会优先使用我们自己的配置。

代码语言:javascript
复制
REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',#json
        'rest_framework.parsers.FormParser',#urlencoded
        'rest_framework.parsers.MultiPartParser'#文件 form-data
    ]
}

局部配置解析器

我们还可以直接将解析器导入到自己的视图类中,直接使用这时会优先使用自己类中的parser_classes

代码语言:javascript
复制
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
class Book(APIView):
    parser_classes = [JSONParser,FormParser,MultiPartParser]

综上我们可以知道 解析器配置的查找顺序:局部(视图类的类属性) => 全局(settings文件的drf配置) => 默认(drf的默认配置)

响应模块

代码语言:javascript
复制
 class Response(SimpleTemplateResponse):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """

    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

    
    @property
    def status_text(self):
        """
        Returns reason text corresponding to our HTTP response status code.
        Provided for convenience.
        """
        return responses.get(self.status_code, '')

    def __getstate__(self):
        """
        Remove attributes from the response that shouldn't be cached.
        """
        state = super().__getstate__()
        for key in (
            'accepted_renderer', 'renderer_context', 'resolver_match',
            'client', 'request', 'json', 'wsgi_request'
        ):
            if key in state:
                del state[key]
        state['_closable_objects'] = []
        return state


# data:响应数据
# status:响应的网络状态码
# -------------------------
# template_name:drf完成前后台不分离返回页面,但是就不可以返回data(了解)
# headers:响应头,一般不规定,走默认
# exception:一般异常响应,会将其设置成True,默认False(不设置也没事)
# content_type:默认就是 application/json,不需要处理

异常处理模块

代码语言:javascript
复制
 def dispatch(self, request, *args, **kwargs):
        '''
       ...
       '''
        try:
            #三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大得多
            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)
                #如果wsgi的request里面有对应的方法,就用wsgi的,如果没有就用自己的,源码中常用的方法
            else:
                handler = self.http_method_not_allowed
                #如果自己也没有就报http_method_not_allowed异常

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            #异常处理模块,处理异常分支
            response = self.handle_exception(exc)
        #二次封装response,处理响应的内容,并对处理的结果进行渲染
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

从源码可以看出当请求进入三大认证时就已经引入了异常捕获的范围,但是这里的异常不会对于客户端的异常处理较好,而对于服务端异常就会返回前端一波代码,我们进入handle_exception,看看异常处理的代码。

代码语言:javascript
复制
 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
        exception_handler = self.get_exception_handler()

        context = self.get_exception_handler_context()
        
        #给异常处理提供额外的参数,其实就是获取抛异常的视图对象和请求的参数,可看下面get_exception_handler_context的源码
        response = exception_handler(exc, context)#异常对象、视图对象和请求的参数

        #默认的exception_handler函数只处理客户端异常形成的response对象,服务器异常不作处理,返回None
        if response is None:
            #当response为none时交给Django中间件处理
            self.raise_uncaught_exception(exc)

        response.exception = True
        return response
    
    
    #视图对象和请求的参数
     def get_exception_handler_context(self):
        """
        Returns a dict that is passed through to EXCEPTION_HANDLER,
        as the `context` argument.
        """
        return {
            'view': self,
            'args': getattr(self, 'args', ()),
            'kwargs': getattr(self, 'kwargs', {}),
            'request': getattr(self, 'request', None)
        }

重写异常处理函数

为了自定义服务器异常时系统所抛的异常的内容,我们需要重写异常处理函数,步骤:

1.在settings的drf配置中配置EXCEPTION_HANDLER,指向自定义的exception_handler函数

2.drf出现异常会回调exception_handler函数,携带异常对象和异常相关信息,在exception_handler函数中完成异常信息的返回以及异常信息的logging日志。

在Django的settings文件中进行配置:

代码语言:javascript
复制
   REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'api.exception_handler.exception_handler'}

在exception_handler文件中重写exception_handler

代码语言:javascript
复制
# 一定要在settings文件中将异常模块配置自己的异常处理函数
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response

# 先交个drf处理客户端异常,如果结果response为None代表服务器异常,自己处理
# 最终一定要在日志文件中记录异常现象
def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    detail = '%s - %s - %s' % (context.get('view'), context.get('request').method, exc)
    if not response:  # 服务端错误
        response =  Response({'detail': detail})
    else:
        response.data = {'detail': detail}

    # 核心:要将response.data.get('detail')信息记录到日志文件
    # logger.waring(response.data.get('detail'))

    return response

渲染模块

渲染模块在APIView中的导入方式renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES,它的作用是Postman请求返回结果是json,浏览器请求结果是经过渲染的页面,实际项目中应用场景不大可以像解析模块一样进行局部和全局配置。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-12-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • APIview的请求生命周期源码分析
  • 请求模块
  • 解析模块
    • 全局配置解析器
      • 局部配置解析器
      • 响应模块
      • 异常处理模块
        • 重写异常处理函数
        • 渲染模块
        相关产品与服务
        消息队列 TDMQ
        消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档