文章目录
- APIview的dispatch方法分析
- 请求模块:request对象
- 基本使用
- 解析模块
- 异常模块
- 渲染模块
- drf请求生命周期
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
# 请求模块,封装了Django原生的请求
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
# 去http_method_names找请求的方法,如果没有找到,触发异常
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
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)
# 进行请求的渲染,为什么postman中测试返回字符串,
# 浏览器中测试返回一个Django rest framework那个漂亮的页面呢,就是这里实现的
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
请求模块:request对象
request = self.initialize_request(request, *args, **kwargs)
点进去。self.initialize_request(request, *args, **kwargs)
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request( # 原生的request被封装成了Request,点进去看看
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
点进去看看Request,Request里面写了非常的多,主要有两个地方值得我们关注。
- 第一:
self._request = request
,这句代码表示Django原生的request就是drf的_request。 - 第二:Django rest framework它的
__getattr__
方法使得他兼容了Django原生的request,这样我们可以直接使用request.POST
拿数据,也可以使用request._request.POST
拿数据,具体看下面源码
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)
基本使用
在view里边书写如下代码,使用postman分别发送post和get请求做测试
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
# Create your views here.
# APIView.dispatch()
class Book(APIView):
def get(self, request):
print(request.GET) # 兼容原来的方式
print(request._request.GET) # 二次封装
print(request.query_params) # 扩展
return Response('drf get is ok')
def post(self, request):
print(request._request.POST)
print(request.POST)
print(request.data)
print(request.query_params) # 扩展
return Response('drf post is ok')
总结的规律
1) drf 对原生request做了二次封装,设置request._request等于原生request
2) 原生request对象的属性和方法都可以被drf的request对象直接访问(兼容)
3) drf请求的所有url拼接参数均被解析到query_params中,所有数据包数据都被解析到data中 (******)
get请求:url中拼接的参数通过request.query_params获取
post请求:所有请求方式所携带的数据包都是通过request.data获取
请求模块最终的结论就是:获取数据有两种方式 query_params和data(通过parser解析数据传到data)
解析模块
看完请求模块后,为了方便大家,我再贴一次initialize_request
的源码,细心的读者可能刚刚看initialize_request
的时候看到还有个parser,这是干什么的呢,有什么用?归纳为三点
1)drf给我们提供了多种解析数据包方式的解析类 form-data/urlencoded/json
2)我们可以通过配置来控制前台提交的哪些格式的数据后台在解析,哪些数据不解析
3)全局配置就是针对每一个视图类,局部配置就是针对指定的视图来,让它们可以按照配置规则选择性解析数据
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
返回初始化后的request对象。
"""
"""
下面这行代码返回一个dict,该dict被传递给Parser.parse(),
作为‘parser_context’关键字参数。
"""
parser_context = self.get_parser_context(request)
return Request( # 可以看到返回了Request,点进去
request,
parsers=self.get_parsers(), # 实例化并返回此视图可以使用的解析器列表。
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
我们点进去self.get_parsers()
看看具体做了啥
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
的类里边实例化得来的,parser_classes
是一个可迭代的对象,我们点进去parser_classes
,
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
点击api_settings
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
点击 DEFAULTS,会发现这里边其实写了好多类,因为有100多行,我就不全部贴出来了,因为实际上看到后面大家会发现里面drf的源码流程,很多都是根据列表生成式,去找到默认的类并实例化,返回一个实例列表,所以我们从这个源码里边就可以分析到,如果我们在我们自己的类里边去自定义列表生成式的那个类的列表,就可以实现局部的自定义了,因为这个列表是默认先去我们的类里面找,找不到再去drf的默认的类里面找,其实我们后面看权限,认证等源码也是基本这个流程,所以它这个局部配置都是去我们的类里边自定义一个相应的列表,下面给出作解析的默认的类列表,
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser', # json数据包
'rest_framework.parsers.FormParser', # urlcoding数据包
'rest_framework.parsers.MultiPartParser' # form-data数据包
],
如果想实现全局的配置,就去我们的配置文件里边,把这个列表copy过去,然后改成我们自己想要的就行
看完self.initialize_request
之后,下面贴一份dispatch
源码看看我们看到哪里了。因为版本,认证,权限,限流等功能都在initial
里边,我们先不啃它
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
# 请求模块,封装了Django原生的请求
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
# 去http_method_names找请求的方法,如果没有找到,触发异常
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
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)
# 进行请求的渲染,为什么postman中测试返回字符串,
# 浏览器中测试返回一个Django rest framework那个漂亮的页面呢,就是这里实现的
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
到initial
,但是因为版本,认证,权限,限流等功能都在initial
里边,我们先不啃它,以后再讲。initial
方法后有个判断,如果请求的方法与http_method_names
里面的请求方法都不匹配,那就抛出异常,因为这里边有所有合法的请求方法,可看上面源码注释,看一下http_method_not_allowed
,果然是抛异常的。
def http_method_not_allowed(self, request, *args, **kwargs):
"""
If `request.method` does not correspond to a handler method,
determine what kind of exception to raise.
如果请求方法不对应于处理程序方法,
确定要引发哪种异常。
"""
raise exceptions.MethodNotAllowed(request.method)
异常模块
在dispatch
方法中可以看到有处理异常的代码,现在我们具体研究一下这一块的源码。再点进去看一下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
# 请求模块,封装了Django原生的请求
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 版本,认证,权限,限流等功能都在initial里边
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
# 去http_method_names找请求的方法,如果没有找到,触发异常
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
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)
# 进行请求的渲染,为什么postman中测试返回字符串,
# 浏览器中测试返回一个Django rest framework那个漂亮的页面呢,就是这里实现的
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.
处理任何发生的异常,通过返回适当的响应,
或重新引发错误。
"""
# 这里使用isinstance,进行三大认证的异常捕获,我们常常看到的403异常就是在这里
if isinstance(exc, (exceptions.NotAuthenticated,
exceptions.AuthenticationFailed)):
# WWW-Authenticate header for 401 responses, else coerce to 403
# 认证头为401响应,否则强制到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',
所以我们可以自定义get_exception_handler实现自定义异常
"""
# 获取处理异常的句柄(方法)
exception_handler = self.get_exception_handler()
# context是一个字典,把它传给response作为参数
context = self.get_exception_handler_context()
# response是处理异常后得到的结果
response = exception_handler(exc, context)
if response is None:
# 如果response为空,即没有拿到处理异常的结果,就直接抛出异常(中间件等捕获)
self.raise_uncaught_exception(exc)
response.exception = True
# 如果response有结果,就返回response,response就是处理异常后得到的结果
return response
先进入看看content返回的是啥,后面自定义异常用的着
def get_exception_handler_context(self):
"""
Returns a dict that is passed through to EXCEPTION_HANDLER,
as the `context` argument.
返回一个dict,该dict被传递给EXCEPTION_HANDLER,
作为“context”参数。
"""
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None)
}
可以看到get_exception_handler_context
返回了一个字典,我们自定义异常的时候可以用字典的键来获取到异常信息。
接下来get_exception_handler()
是获取处理异常的方法,那么我们点进去看看。
def get_exception_handler(self):
"""
Returns the exception handler that this view uses.
返回此视图使用的异常处理程序。
"""
return self.settings.EXCEPTION_HANDLER
它就只有一句话,可见,又是去默认的类中找到处理异常的方法。和之前其他功能的源码类似。我们一层层点进去
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
其实我们研究异常处理模块的目的是去自定义异常,为啥要自定义异常?以及如何自定义?且听我慢慢道来哈。
为啥要自定义异常?
1)所有经过drf的APIView视图类产生的异常,都可以提供异常处理方案
2)drf默认提供了异常处理方案(rest_framework.views.exception_handler),但是处理范围有限
3)drf提供的处理方案两种,处理了返回异常现象,没处理返回None(后续就是服务器抛异常给前台)
4)自定义异常的目的就是解决drf没有处理的异常,让前台得到合理的异常信息返回,后台记录异常具体信息
怎样自定义?
1)首先,从刚刚的源码中我们知道如果我们自己不自定义,drf是使用它默认的异常处理方法的。
2)这个方法我们没有必要重新写一遍,我们先交给drf的异常处理方法帮我们处理
3)然后如果response为空,表示drf没有处理到,这时候我们自己来处理
4)我们自己处理的话可以使用content这个字典来处理,上面源码也分析了它。
下面我们就来搞一下,先简单返回个server error
先在项目目录下创建exception.py文件
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
from rest_framework import status
def exception_handler(exc, context):
response = drf_exception_handler(exc, context)
if response == None:
print(exc)
print(context)
return Response({
'detail': 'server error.',
})
return response
修改自己的配置文件,导入我们写的异常处理方法的路径
# 修改自己的配置文件setting.py
REST_FRAMEWORK = {
# 全局配置异常模块
'EXCEPTION_HANDLER': 'api.exception.exception_handler', #设置自定义异常文件路径,在api应用下创建exception文件,exception_handler函数
}
这样就OK了,我们只返回了很简单的server error信息等我们后面讲了请求模块后,再具体做处理
渲染模块
最后先说一下渲染模块,我们都知道我们的API返回一个字符串等数据的时候,postman返回的也仅仅是一个字符串,但是如果使用浏览器来测试的话会返回一个很漂亮的django rest framework
的页面,这个就是通过对请求进行渲染实现的。再进入一下dispatch
源码,看倒数第二行代码:
self.response = self.finalize_response(request, response, *args, **kwargs)
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
# 请求模块,封装了Django原生的请求
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 版本,认证,权限,限流等功能都在initial里边
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
# 去http_method_names找请求的方法,如果没有找到,触发异常
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
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)
# 进行请求的渲染,为什么postman中测试返回字符串,
# 浏览器中测试返回一个Django rest framework那个漂亮的页面呢,就是这里实现的
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
点进去self.finalize_response
,
def finalize_response(self, request, response, *args, **kwargs):
"""
Returns the final response object.
返回最终的响应对象。
"""
# 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)
)
if isinstance(response, Response):
if not getattr(request, 'accepted_renderer', None):
# 拿到运行的解析类,这里点进去重点看
neg = self.perform_content_negotiation(request, force=True)
request.accepted_renderer, request.accepted_media_type = neg
response.accepted_renderer = request.accepted_renderer
response.accepted_media_type = request.accepted_media_type
response.renderer_context = self.get_renderer_context()
# 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
点进去看self.get_renderers()
,这里大家肯定已经猜到了,又是基于列表推导式去搞的。
def get_renderers(self):
"""
Instantiates and returns the list of renderers that this view can use.
实例化并返回此视图可以使用的渲染器列表
"""
return [renderer() for renderer in self.renderer_classes]
接下来去找到renderer_classes
,
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer', # 只显示json数据
'rest_framework.renderers.BrowsableAPIRenderer', # 显示数据,并且渲染出页面
],
所以我们想要配置,就copy这段内容来改就行,比如我们在setting.py里面copy这段过去,把第二个类注释掉,浏览器就不会在返回页面了。
注意:Django rest framework这里还有个比较坑人的地方
请看它的注释
"""
Settings for REST framework are all namespaced in the REST_FRAMEWORK setting.
For example your project's `settings.py` file might look like this:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
],
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
],
}
This module provides the `api_setting` object, that is used to access
REST framework settings, checking for user settings first, then falling
back to the defaults.
"""
如果只看了它的注释就直接把那段内容拷贝到我们的settings里面,看源码不够仔细,就会以为渲染页面是使用TemplateHTMLRenderer
这个类,但其实看源码我们知道它叫BrowsableAPIRenderer
,博主一开始就中招了,发现浏览器报错说找不到那个模板文件,然后postman正常,找了半个小时才发现这里错了,哈哈哈。
drf请求生命周期
一个请求过来之后,需要先穿过中间件,依次执行中间件里边的process_request()
方法,执行完process_request()
之后,如果中间件里边实现了process_view()
方法,再依次执行中间件各个process_view()
方法,接下来就到了路由系统进行路由匹配,如果是FBV,路由匹配成功后,执行对应的视图函数,视图函数可以基于ORM去数据库中拿数据,也可以渲染模板,返回字符串给浏览器,服务器在返回响应给浏览器时,需要再次经过中间件,依次去执行process_response()
方法,如果是CBV,那么会先执行源码里边的dispatch
方法,dispatch()
方法根据请求的类型找到对应的函数,比如我们的请求是GET形式,它会找到get()函数,执行里边的代码,然后把结果返回给浏览器,返回的过程中也要经过中间件,执行中间件的process_response
方法。
未完待续