文章目录
1. 引子
- 在三大认证以及视图函数中的错误在源码中会被 try 捕获,如下所示
def dispatch(self, request, *args, **kwargs):
...
try:
...
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
- 其异常返回的格式已经在 DRF 配置文件中配置了,但是并不符合我们的要求,我们可以重写其方法,看源码可以发现其执行了 handle_exception 方法,该方法源码如下
def handle_exception(self, exc):
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 = self.get_exception_handler()
context = self.get_exception_handler_context()
response = exception_handler(exc, context)
if response is None:
self.raise_uncaught_exception(exc)
response.exception = True
return response
exception_handler = self.get_exception_handler()
response = exception_handler(exc, context)
上面俩句源码说明处理的是 get_exception_handler 方法,查看其源码如下
def get_exception_handler(self):
"""
Returns the exception handler that this view uses.
"""
return self.settings.EXCEPTION_HANDLER
- 在 get_exception_handler 方法中返回异常处理程序。
这里默认返回的是 settings 下的 EXCEPTION_HANDLER 如下所示
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
-
也就是说,我们只要重写 exception_handler 方法即可。
-
exception_handler 源码如下
def exception_handler(exc, context):
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
if isinstance(exc, exceptions.APIException):
isinstance 方法判断 ecx 是否为 APIException 的实例对象,所有继承了 APIException 的异常都会被处理。也就是说,DRF 只处理自己的异常。REST framework定义的异常如下表格所示
异常 | 解释 |
---|---|
APIException | 所有异常的父类 |
ParseError | 解析错误 |
AuthenticationFailed | 认证失败 |
NotAuthenticated | 尚未认证 |
PermissionDenied | 权限决绝 |
NotFound | 未找到 |
MethodNotAllowed | 请求方式不支持 |
NotAcceptable | 要获取的数据格式不支持 |
Throttled | 超过限流次数 |
ValidationError | 校验失败 |
- 注意点:
drf 默认只处理自己的异常:所有 drf 中抛的异常,都有 detail
而 django 的异常,抛出很长的 xml 数据
2. 自定义异常处理
方法
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
response = exception_handler(exc, context)
if response:
res = Response(data={'code': 10001, 'msg': response.data.get('detail')})
else:
# res = Response({'code': 10002, 'msg': '服务器内部错误'})
res = Response({'code': 10002, 'msg': str(exc)})
return res
配置文件
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.tool.exception.common_exception_handler'
}
- 首先编写函数,参数为 exc、context,exc 为错误对象。
- 调用原来的 exception_handler 方法,因为直接使用 DRF 对异常的处理,只不过将异常返回的格式改变而已
- 进行判断,除了 DRF 的错误还有 Django 的错误,Django 错误的详细信息直接用 exc 表示,但是返回给前端不需要这么详细。在上面我对 Django 和 DRF 的异常 code 区分开来。
- 在 setting 配置文件中声明自定义的异常处理,不声明的话默认使用的是 ‘rest_framework.views.exception_handler’
在自定义异常处理中,还需要记录日志,并且日志越详细越好,例如某个用户在什么时间执行了什么视图函数出现了什么异常以及请求的地址
在自定义异常处理方法中还有一个参数 context
,出现异常的时候打印如下所示
{
'view': <app01.view.ErrorView.Error object at 0x000001EC7F55D940>,
'args': (),
'kwargs': {},
'request': <rest_framework.request.Request: GET '/test/error/'>
}
- context[‘view’] :视图类的对象
- context[‘request’]:当前请求的对象
我们可以利用这些属性来生成日志,例如:
'ip地址为:%s的用户,访问:%s 视图类,报错了,请求地址是:%s'%(request.META.get('REMOTE_ADDR'),str(view),request.path)