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

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

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

 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()

 @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方法源码:

 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

# 源码分析:
# 入口: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)

解析模块

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

# 源码分析:
# 入口: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文件中进行如下配置后再启动项目就会优先使用我们自己的配置。

REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',#json
'rest_framework.parsers.FormParser',#urlencoded
'rest_framework.parsers.MultiPartParser'#文件 form-data
]
}

局部配置解析器

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

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的默认配置)

响应模块

 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,不需要处理

异常处理模块

 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,看看异常处理的代码。

 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文件中进行配置:

   REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'api.exception_handler.exception_handler'}

在exception_handler文件中重写exception_handler

# 一定要在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,浏览器请求结果是经过渲染的页面,实际项目中应用场景不大可以像解析模块一样进行局部和全局配置。

APIview的请求生命周期源码分析的更多相关文章

  1. DRF框架(一)——restful接口规范、基于规范下使用原生django接口查询和增加、原生Django CBV请求生命周期源码分析、drf请求生命周期源码分析、请求模块request、渲染模块render

    DRF框架    全称:django-rest framework 知识点 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码 - 基于restful规范下的CBV接口 3.请求组件 ...

  2. drf复习(一)--原生djangoCBV请求生命周期源码分析、drf自定义配置文件、drf请求生命周期dispatch源码分析

    admin后台注册model  一.原生djangoCBV请求生命周期源码分析 原生view的源码路径(django/views/generic/base.py) 1.从urls.py中as_view ...

  3. APIView 的请求生命周期

    目录 APIView 的请求生命周期 请求解析模块 响应渲染模块 序列化组件 Django 配置 """ 1)应用是否需要在INSTALLED_APPS中注册 在没有使用 ...

  4. DRF 请求生命周期以及各模块解析

    目录 rest_framework框架的封装特点 原生Django与DRF比较 APIView 的请求生命周期 请求模块(request) 解析模块(parser_classes) 异常模块(exce ...

  5. drf框架,restful接口规范,源码分析

    复习 """ 1.vue如果控制html 在html中设置挂载点.导入vue.js环境.创建Vue对象与挂载点绑定 2.vue是渐进式js框架 3.vue指令 {{ }} ...

  6. drf框架概况-resful接口规范-请求模块-渲染模块-Postman-drf请求生命周期

    drf框架 全称:django-rest- framework 知识点: """ 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码-基于restful ...

  7. drf 简介以及部分源码分析

    目录 复习 drf框架 全称:django-rest framework 知识点 接口 restful接口规范 基于restful规范的原生Django接口 主路由:url.py api组件的子路由: ...

  8. Flask系列之源码分析(一)

    目录: 涉及知识点 Flask框架原理 简单示例 路由系统原理源码分析 请求流程简单源码分析 响应流程简单源码分析 session简单源码分析 涉及知识点 1.装饰器 闭包思想 def wapper( ...

  9. okHttp3 源码分析

    一, 前言 在上一篇博客OkHttp3 使用详解里,我们已经介绍了 OkHttp 发送同步请求和异步请求的基本使用方法. OkHttp 提交网络请求需要经过这样四个步骤: 初始化 OkHttpClie ...

随机推荐

  1. c# VirtualKeys

    /// <summary> /// Enumeration for virtual keys taken from http://www.pinvoke.net/default.aspx/ ...

  2. java:JQueryReview

    Important: 1.id选择器,class选择器,标签选择器: $("#id"); $(".class"); $("标签"); 2.j ...

  3. 意想不到的JavaScript(每日一题3)

    题目: 答案: 1 4 3 2 解析:

  4. 索引之----mysql单列索引失效的情况

    使用的索引名称: 1.隐式转换导致索引失效. 由于表字段定义为vachar类型,但在查询时把该字段作为number类型 以及where条件传给mysql. 2.对索引列进行任何操作(计算(+.-.*. ...

  5. property可以声明得位置

    property可以声明的位置有4处 1 @interface处 2 扩展处 3 protocol处 4 分类处 其中分类处的property不会合成实例变量,并且编译器也不会自动合成实例变量

  6. Til the Cows Come Home 最短路Dijkstra+bellman(普通+优化)

    Til the Cows Come Home 最短路Dijkstra+bellman(普通+优化) 贝西在田里,想在农夫约翰叫醒她早上挤奶之前回到谷仓尽可能多地睡一觉.贝西需要她的美梦,所以她想尽快回 ...

  7. POJ 2528 ——Mayor's posters(线段树+区间操作)

    Time limit 1000 ms Memory limit 65536 kB Description The citizens of Bytetown, AB, could not stand t ...

  8. Tarjan水题系列(5):最大半连通子图 [ZJOI2007 luogu P2272]

    题目 大意: 缩点后转为求最长链的长度和最长链的个数 思路: 看懂题就会做系列 长度和个数都可以拓扑排序后DP求得 毕竟是2007年的题 代码: 如下 #include <cstdio> ...

  9. git部分命令笔记

    目录 配置user信息 建Git仓库 清空暂存区 git变更文件名 查看暂存区状态 查看历史 查看本地分支 查看所有分支(包含远程) 创建分支 基于远程分支创建本地新分支 查看图形化分支日志 图形化界 ...

  10. [.net core]7 4种app key value的配置方法及优先顺序

    就是这货 点开查看内容 { "Logging": { "LogLevel": { "Default": "Warning" ...