一、全局异常处理
1、drf默认异常处理源码分析
在DRF中,继承APIView后,它的执行流程是首先去除了所有请求的csrf认证,然后把视图类的request对象变成了新的request对象,新的reqeust对象是DRF的,但是以前Django的request对象用起来是一样的,同时把新的reqeust对象放到了视图类的对象中,然后在执行视图类的方法之前,又执行了三大认证。最后就是在执行三大认证或视图类方法的过程中只要报错了,就会被全局异常捕获处理。而在这里我们要说的就是这个DRF执行流程中最后一个环节,全局异常处理。
- 只要三大认证,视图类的方法出了异常,都会执行一个函数:
rest_framework.views import exception_handler
- 查看dispatch源码:如下
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 三大认证执行
self.initial(request, *args, **kwargs)
# 视图类的方法开始执行
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)
'''
这里self是视图类的对象,现在在它的父类APIView里面,所以我们从APIView中找找有没有
这个handle_exception方法,另外这个括号里面的exc就是异常对象
这个handle_exception方法也是我们主要分析的方法
'''
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
- 执行handle_exception 方法时就会执行方法中
exception_handler = self.get_exception_handler()
这一行代码,但 - 是这一行代码就是前面
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler’
中的rest_framework.views.exception_handle
函数
# 在APIView里面找到了handle_exception方法
def handle_exception(self, exc):
# 如果没有认证通过,http响应状态码变成403,并且往响应头中放入了数据
if isinstance(exc, (exceptions.NotAuthenticated,
exceptions.AuthenticationFailed)):
# WWW-Authenticate header for 401 responses, else coerce to 403
# 如果异常是 NotAuthenticated 或 AuthenticationFailed,处理授权相关的逻辑
auth_header = self.get_authenticate_header(self.request)
# 如果存在认证头,将其添加到异常中
if auth_header:
exc.auth_header = auth_header
else:
# 如果不存在认证头,将状态码更改为 403 FORBIDDEN
exc.status_code = status.HTTP_403_FORBIDDEN
# 这里的exception_handler就是我们自定义异常处理重写的函数
'''
self是视图类对象,已知视图类对象中没有get_excpetion_handler这个方法,然后去父类APIView中查找到了
def get_exception_handler(self):
return self.settings.EXCEPTION_HANDLER
可以看到,它是去配置文件中拿这个EXCEPTION_HANDLER了,这也是为什么我们自定义的异常处理需要配置,如果在配置文件中没有拿到,就会去DRF的配置文件中拿到它
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
'''
exception_handler = self.get_exception_handler()
# 获取异常处理上下文
context = self.get_exception_handler_context()
'''
这里的exception_handler就是我们自定义异常处理重写的函数,这也就是为什么我们需要传两个参数的原因
exc就是错误对象,context就是上下文,它是一个对象,并且里面存着当次请求的request和视图类
这个response就是最后返回的异常结果,这就是为什么我们重写后最后需要返回一个响应对象
'''
response = exception_handler(exc, context)
'''
如果响应对象是None就是抛出异常,就不会捕获,直接返回给前端,所以这个全局异常只能捕获DRF的异常
这也是为什么我们需要重写,就是为了返回全局异常无法捕获的异常
'''
if response is None:
self.raise_uncaught_exception(exc)
'''
也可以看一下这个方法,还是一样self是视图类对象,然后在父类APIView中找到了这个方法
def raise_uncaught_exception(self, exc):
if settings.DEBUG: 在调试模式下处理未捕获的异常
request = self.request 获取当次请求
请求接受的渲染格式
renderer_format = getattr(request.accepted_renderer, 'format')
然后根据渲染格式渲染到页面
use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin')
request.force_plaintext_errors(use_plaintext_traceback)
最后将未捕获的异常直接渲染给页面
raise exc
'''
# 这里将响应对象的exception属性设置为True,表示异常已被处理
response.exception = True
# 最后返回处理后的响应对象
return response
-
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
-
接下来我们单独去读读DRF默认配置的异常处理
def exception_handler(exc, context):
# 看到DRF默认配置的全局异常处理,就可以知道为什么我们要那么配置自定义的了,其实就是照着这个写的
# 这里就是自己的异常
if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
# 这里是DRF的所有异常,只要是DRF的异常都会走这里,因为DRF的异常都继承了APIException
if isinstance(exc, exceptions.APIException):
# 如果异常的详细信息是列表或字典,直接使用该信息
if isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
# 否则,将详细信息包装在 detail 键下
data = {'detail': exc.detail}
# 如果想要验证drf出现异常是否会执行上面exception_handler函数的话,可以在这打印一句话
# 然后再视图类中抛出raise APIException('我出错误了')
# 返回处理后的响应
'''
所以这里有两个情况
(1) Response(data:{'detail': exc.detail})
(2) Response(data:错误信息(字典或列表))
所以这种情况,这也是为什么我们自定义的时候,取detail时,怕取不到设置了自定义的一个异常信息或者在or一个data这样会 更好一些,例如:
detail = res.data.get('detail') or res.data or "drf异常,请联系系统管理员"
return Response({'code':666,'message':detail})
'''
return Response(data, status=exc.status_code, headers=headers)
# 对于其他类型的异常,返回 None,让 DRF 使用默认的异常处理机制,就是不处理直接返回给前端页面
return None
2、自定义异常处理
针对上面的源码,我们也可以自定义异常处理,DRF默认的异常处理,只处理APIException及其子类的异常,处理不了的会返回None,我们可以判断异常是否属于APIException及其子类的异常,如果是则返回错误信息,如果不是,那么我们则返回服务器错误。
- 自定义异常处理
from rest_framework.views import exception_handler
from rest_framework.response import Response
'''
需求:
0 执行原来的exception_handler
判断返回值是否为 None:
如果是None,说明是非drf异常,自己包装一个Response
如果不是None,说明是drf异常,包装一个Response
1 错误状态码可能会有很多类型: 通过exc 错误对象,判断具体是哪个类的对象(是什么错误)--》更细粒度区分不同的错误
991
992
993
996
998
999
2 只要程序走到common_exception_handler ,就说明有异常,通常做法
-返回给前端固定错误描述: 服务器异常,请稍后再试
-使用日志,记录错误:越详细越好
-时间,请求方式,请求的地址,客户端ip,用户id。。。。
'''
class PasswordException(Exception):
def __init__(self,msg):
self.msg=msg
def common_exception_handler(exc, context):
# 日志记录放这里即可:请求方式,请求的地址,客户端ip,用户id
request=context.get('request')
view=context.get('view') # 哪个视图类出的错
user_id=request.user.pk or '匿名用户'
print(f'请求方式是:{request.method},请求地址是:{request.get_full_path()},客户端ip:{request.META.get("REMOTE_ADDR")},用户id:{user_id}, 错误的视图类是{view.__class__.__name__}')
# 1 返回 Response:说明是drf的异常 2 返回None:说明不是drf的异常
response = exception_handler(exc, context)
if response:
if isinstance(response.data,dict):
err=response.data.get('detail','系统错误,请联系系统管理员')
elif isinstance(response.data,list):
err = response.data[0]
else:
err ='系统错误,请联系系统管理员'
response = Response({'code': 998, 'msg': f'drf的异常:{err}'})
pass
else:
if isinstance(exc,ZeroDivisionError):
response = Response({'code': 991, 'msg': '不能除以0'})
elif isinstance(exc,Exception):
response = Response({'code': 992, 'msg': 'Exception错误'})
else:
# 表明是 非drf的异常
err=str(exc)
response = Response({'code': 999, 'msg': f'django的异常:{err}'})
return response
- 视图类
from rest_framework.views import APIView
from utils.exceptions import PasswordException
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError, AuthenticationFailed, APIException
class UserView(APIView):
def get(self, request, *args, **kwargs):
'''
这个接口用来查询xx
'''
# 1 drf异常,主动抛
# raise ValidationError('小伙子,出错了')
# raise AuthenticationFailed('小伙子,出错了,没认证通过吖')
raise APIException('错误了!!!')
# 2 django的异常或python的异常
# res = 9 / 0
# raise PasswordException('ccccc')
# l=[2,3,4]
# print(l[9])
# raise Exception('错误')
# return Response('正常返回xx')
- 配置文件
# 自定义异常配置文件中要加入
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
}
- 总结使用步骤
#1 定义一个函数
def common_exception_handler(exc, context):
# 加入日志的逻辑,错误中记录日志---> 越详细越好
...日志逻辑...
response = exception_handler(exc, context)
if response:
return Response(data={'code': 9998, 'msg': response.data})
else:
return Response(data={'code': 9999, 'msg': '服务器异常,请联系系统管理员'})
# 2 在配置文件中配置
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
}
3、总结
-
对于前端来讲,无论后端什么情况,前端收到的都是统一的格式
-
正常响应:
-
不正常响应:
-
出了错:{code:999,msg:错误信息}
-
-
只要三大认证,视图类的方法出了异常,都会执行一个函数:
rest_framework.views import exception_handler
-
注意:
exception_handler
- 如果异常对象是drf的APIException对象,就会返回Response
- exception_handler只处理了drf的异常,其它的异常需要我们自己处理
- 如果异常对象不是drf的APIException对象,就会返回None
-
补充:
- isinstance() 判断一个对象是不是某个类的对象 isinstance(对象,类)
- ssubclass() 判断一个类,是不是另一个类的子类
二、接口文档
1、为什么要写接口文档
在我们后端写完接口之后,应该清楚
- 地址
- 携带参数
- 请求方式
- 编码格式
- 返回数据格式
- ...
并将这些内容必须写成接口文档,方便前端参照,而前端的话在拿到接口文档,应该参考接口文档进行前端的相关工作开发。
2、接口文档展示形式
-
md、word等共享文档写接口文档
-
接口文档开放平台参考----> 微博开放平台
-
第三方平台
- 例如:showDoc 但是需要花钱
- https://www.showdoc.com.cn/item/index
-
公司自研
-
开源的接口文档平台
- Yapi:百度开源的
-
通过项目,自动生成接口文档平台
- coreapi
- drf-yasg:可以看着文档自己集成到项目里
3、接口文档规范
这要取决你所在的公司有着什么样的规范,毕竟是自己公司内部员工看的,所以需要统一
4、coreapi使用
(1)安装
pip3 install coreapi
(2)路由中配置
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='coreapi自动生成接口文档'))
]
(3)本地配置文件中配置
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
(4)正常写视图类
-
注意:在方法中加的注释,会在接口文档中体现。在类里面但是在方法外的注释是不会体现的
-
在序列化类中写required,help_text等参数也会在接口文档中体现