day 73 APIView请求生命周期

APIView请求生命周期

  • APIView类继承View类,重写了as_view和dispatch方法
  • 重写的as_view方法,主体还是View的as_view,只是在返回视图view函数地址时,局部禁用csrf认证
# 继承了View
class APIView(View):
    @classmethod
    def as_view(cls, **initkwargs)
        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的as_view
        view = super().as_view(**initkwargs)
        
        view.cls = cls
        view.initkwargs = initkwargs
 
        # 就增加的一个功能, 就是局部禁用crsf认证
        return csrf_exempt(view)
  • 重写的dispatch方法
    • 在执行请求逻辑前:请求模块(二次封装request)、解析模块(三种数据包格式的数据解析
    • 在执行请求逻辑中:异常模块(执行出现任何异常交给异常模块处理)
    • 在执行请求逻辑后:响应模块(二次封装request)、渲染模块(响应的数据能在json和页面两种形式进行渲染)
"""
1.在执行请求逻辑前:
    请求模块(二次封装request), 解析模块(三种数据包格式的数据解析)
2.在执行请求逻辑中: 
    异常模块(处理执行过程中的任何异常)
3.在执行请求逻辑后: 
    响应模块(二次封装response), 渲染模块(响应的数据能通过JSON和页面两种形式进行渲染)
"""
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
        # 1.请求模块和解析模块: initialize_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)
        # 2.异常模块, 捕获并处理异常: handle_exception
        except Exception as exc:
            response = self.handle_exception(exc)
        
        # 3.响应模块和渲染模块: finalize_response
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

请求模块

  • 将wsgi的request对象转化成drf的Request类的对象
  • 封装后的request对象完全兼容wsgi的request对象,并且将原request保存在新request._request
  • 重写格式化请求数据存放位置
    • 拼接参数:request.query_params
    • 数据包参数:request.data
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):
      
        # drf的request兼容wsgi的request
        self._request = request

            
# drf的Request类下面的__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.
    """
    # drf的request中没有的属性和方法, 会通过反射去wsgi中的request对象中寻找
    try:
        return getattr(self._request, attr)
    except AttributeError:
        return self.__getattribute__(attr)
# 可见, 这个query_params就是_request.GET
@property
def query_params(self):
    """
    More semantically correct name for request.GET.
    """
    return self._request.GET


# request.data是方法属性, 返回request._full_data的值
@property
def data(self):
    if not _hasattr(self, '_full_data'):
        self._load_data_and_files()
        return self._full_data

解析模块

只处理数据包参数 - form-data, urlencoded, json

  • 全局配置所有视图类的解析方式,解析配置可以配置三种
# 在项目的settings.py文件中进行如下配置

from rest_framework.parsers import JSONParser, FormParser, MultiPartParser

# drf框架自定义配置
REST_FRAMEWORK = {
    # 全局配置解析类, 适用于所有视图类
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ],
}
  • 局部配置当前视图类的解析方式,解析配置可以配置三种
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser


# drf下的视图类继承APIView
class BookApiView(APIView):
    # 为当前视图类配置解析类
    parser_classes = [JSONParser, FormParser, MultiPartParser]
  • 配置的查找顺序:局部(视图类的类属性) =》全局(settings文件的drf配置) =》默认(drf的默认配置)
# 因此我们的视图类可以自定义parser_classes属性, 来覆盖父类的属性
class APIView(View):
    # APIView的默认的解析属性
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    
'DEFAULT_PARSER_CLASSES': [
    'rest_framework.parsers.JSONParser',
    'rest_framework.parsers.FormParser',
    'rest_framework.parsers.MultiPartParser'
],

注意:该模块仅供了解,但是全局与局部配置是重点

响应模块

  • data:响应数据
  • status:响应的网络状态码
  • template_name:drf完成前后端不分离返回页面,但是就不可以返回data
  • headers:响应头,一般不规定,默认
  • exception:一般异常响应,会将其设置成True,默认False
  • content_type:默认是 appliacation/json,不需要处理
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

渲染模块

  • Postman请求结果是json, 浏览器请求的结果是页面
  • 同解析模块一样, render渲染模块也可以进行局部配置和全局配置
  • 默认配置
'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
  • 局部配置
# views.py
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer


# drf下的视图类继承APIView
class BookApiView(APIView):
    
    # 为当前视图类配置渲染类
    renderer_classes = [JSONRenderer, BrowsableAPIRenderer]
  • 全局配置
# settings.py
REST_FRAMEWORK = {
    # 全局配置渲染类:适用于所有视图类
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',  # 上线后尽量关闭
    ],

异常模块

  • drf默认的异常处理模块只处理客户端请求异常
# dispatch方法中的异常捕获 
except Exception as exc:
    response = self.handle_exception(exc)
# APIView下面的handle.exception异常处理方法
    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': 'rest_framework.views.exception_handler',
        exception_handler = self.get_exception_handler()
        
        # 给异常处理提供额外的参数
        context = self.get_exception_handler_context()
        
        response = exception_handler(exc, context)  # exc: 异常对象, content: 视图对象和请求对象

        if response is None:
            self.raise_uncaught_exception(exc)

        response.exception = True
        return response
# 默认的异常处理函数只处理客户端的请求异常
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.
    """
    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
  • 自定义异常处理方法
# 1.在app文件夹下新建exception.py文件

# 2.在exception.py书写自定义异常处理方法
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response


def exception_handler(exc, context):
    # 主体还是drf默认的异常处理方法
    response = drf_exception_handler(exc, context)
    
    
    # 按照一定格式返回异常信息
    detail = f"{context.get('view')}-{context.get('request')}-{exc}"
    # 服务端错误
    if not response:
        return Response({
            'detail': detail
        })

    else:
        response.data = {'detail': detail}
    return response



# 3.在项目settings.py文件下进行如下配置
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}
posted @ 2019-12-25 08:55  colacheng  阅读(121)  评论(0编辑  收藏  举报
Live2D