APIview的请求生命周期源码分析
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的请求生命周期源码分析的更多相关文章
- DRF框架(一)——restful接口规范、基于规范下使用原生django接口查询和增加、原生Django CBV请求生命周期源码分析、drf请求生命周期源码分析、请求模块request、渲染模块render
DRF框架 全称:django-rest framework 知识点 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码 - 基于restful规范下的CBV接口 3.请求组件 ...
- drf复习(一)--原生djangoCBV请求生命周期源码分析、drf自定义配置文件、drf请求生命周期dispatch源码分析
admin后台注册model 一.原生djangoCBV请求生命周期源码分析 原生view的源码路径(django/views/generic/base.py) 1.从urls.py中as_view ...
- APIView 的请求生命周期
目录 APIView 的请求生命周期 请求解析模块 响应渲染模块 序列化组件 Django 配置 """ 1)应用是否需要在INSTALLED_APPS中注册 在没有使用 ...
- DRF 请求生命周期以及各模块解析
目录 rest_framework框架的封装特点 原生Django与DRF比较 APIView 的请求生命周期 请求模块(request) 解析模块(parser_classes) 异常模块(exce ...
- drf框架,restful接口规范,源码分析
复习 """ 1.vue如果控制html 在html中设置挂载点.导入vue.js环境.创建Vue对象与挂载点绑定 2.vue是渐进式js框架 3.vue指令 {{ }} ...
- drf框架概况-resful接口规范-请求模块-渲染模块-Postman-drf请求生命周期
drf框架 全称:django-rest- framework 知识点: """ 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码-基于restful ...
- drf 简介以及部分源码分析
目录 复习 drf框架 全称:django-rest framework 知识点 接口 restful接口规范 基于restful规范的原生Django接口 主路由:url.py api组件的子路由: ...
- Flask系列之源码分析(一)
目录: 涉及知识点 Flask框架原理 简单示例 路由系统原理源码分析 请求流程简单源码分析 响应流程简单源码分析 session简单源码分析 涉及知识点 1.装饰器 闭包思想 def wapper( ...
- okHttp3 源码分析
一, 前言 在上一篇博客OkHttp3 使用详解里,我们已经介绍了 OkHttp 发送同步请求和异步请求的基本使用方法. OkHttp 提交网络请求需要经过这样四个步骤: 初始化 OkHttpClie ...
随机推荐
- nova计算服务
1. nova介绍 Nova 是 OpenStack 最核心的服务,负责维护和管理云环境的计算资源.OpenStack 作为 IaaS 的云操作系统,虚拟机生命周期管理也就是通过 Nova 来实现的. ...
- C#学习笔记一(概念,对象与类型,继承)
一.基础 1.CLR为公共语言运行库,类似于JVM 2..NET Framwork是一个独立发布的程序包,其包含了CLR,类库及相关的语言编辑器等工具,类似于JDK,除了C#,还有其他几种语言在CLR ...
- HCL试验2
PC端配置:配置ip地址 交换机1配置:①创建VLAN system-view vlan 10 vlan 20 ②配置PC端接口 interface gi 1/0/1 port link-type a ...
- ubuntu系统熄屏无法唤醒
ubuntu系统熄屏无法唤醒 解决办法:重启后,安装laptop-mode-tools工具包. 1.检查是否安装了grep laptop-mode-tools 工具包 $ dpkg -l | grep ...
- Java学习开发第三阶段总结
第三阶段的学习总结: 在这次学习我学习了面向对象和封装的知识. ①类的定义 package day01; public class student { //成员变量 String name; //姓名 ...
- C# 字符串、字节数组互相转换
/// <summary> /// MD5加密 /// </summary> /// <param name="sender"></par ...
- 面试题 | 数据库笔试题集合·之·SQL语句(2)
第2章 SQL 语句 2.1 选择2.1.1 DELETE FROM S WHERE 年龄>60 语句的功能是( A ) A.从 S 表中彻底删除年龄大于 60 岁的记录B.S 表中年龄大于 6 ...
- P1550打井
这是USACO2008年的一道最小生成树题,感谢dzj老师那天教的图论. 要引渠让每一个村庄都可以接到水,然后从某一个村庄到另一个村庄修剪水道要花费w元,并且还要打井(至少一个)(而输入数据也包括了在 ...
- Python爬虫之简单的爬取百度贴吧数据
首先要使用的第类库有 urllib下的request 以及urllib下的parse 以及 time包 random包 之后我们定义一个名叫BaiduSpider类用来爬取信息 属性有 url: ...
- Java Script 基本知识点
JavaScript是一种基于对象和事件驱动的脚本语言,它提供了一些专有的类.对象及函数 1.基本数据类型 JavaScript提供了4种基本的数据类型用来 ...