异常模块
为什么要自定义异常模块
- 所有经过 drf APIView 视图类产生的异常,都可以提供异常处理方案(没有继承 APIVIew 的视图函数不会触发)
- drf 默认提供了异常处理方案(
rest_framework.views.exception_handler
),但是处理范围有限 - drf 提供的处理方案有两种
- 有对应处理,处理了返回异常信息
- 没有对应处理(处理范围之外),返回 None,直接服务器抛异常给前台
- 自定义异常的目的就是解决 drf 没有处理的异常,让前台得到合理的异常信息返回,后台记录异常具体的信息(方便事后排查)
如果程序报错了,我们应该尽可能的隐藏后台的错误,返回给前台就是服务器错误(你返回给用户用户也看不懂呀,如果是黑客,那可能还会利用报错袭击服务器)
常见的几种异常情况
- 像这种就比较可怕了,甚至连代码文件位置都暴露了
- drf 异常处理模块处理后的异常
- drf 异常处理模块处理后的异常
- 异常信息经汉化后的报错(django 配置了国际化后)
异常模块源码分析
视图函数执行出现异常会自动触发 handle_exception
函数
每个请求都会经历这么一个过程,走到 dispatch 函数
E:/python3-6-4/Lib/site-packages/rest_framework/views.py
源码
# ...
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
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
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) # 上面 try 代码体内代码出现异常会自动触发这个函数 <---------
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 = self.get_exception_handler() # 获取处理异常的句柄(方法) <---------
context = self.get_exception_handler_context()
# 异常处理的结果
# 自定义异常就是提供 exception_handler 异常处理函数,处理的目的就是让 response 一定有值
response = exception_handler(exc, context)
if response is None:
self.raise_uncaught_exception(exc) # 乱七八糟的异常就是这里抛出来的
response.exception = True
return response
如何获取异常类?
get_exception_handler_context
源码,异常处理类是从配置中拿来的
def get_exception_handler(self):
"""
Returns the exception handler that this view uses.
"""
return self.settings.EXCEPTION_HANDLER
# API policy implementation methods
E:/python3-6-4/Lib/site-packages/rest_framework/settings.py
# Exception handling
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
获取到异常类如何处理?
返回 None 就会触发
handle_exception
源码中的报错
E:/python3-6-4/Lib/site-packages/rest_framework/views.py
drf 自带的异常处理类
def exception_handler(exc, context):
"""
Returns the response that should be used for any given exception.
By default we handle the REST framework `APIException`, and also
Django's built-in `Http404` and `PermissionDenied` exceptions.
Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
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 # 其他的异常 drf 未处理,返回 None,让其报错(最上面的那种报错)
自定义 drf 异常处理
自定义异常处理模块就是提供
exception_handler
异常处理函数,处理的目的就是让 response 一定有值显而易见,我们只需要自定义一个异常处理方法,先调用系统自带的那个异常处理函数,然后把 drf 自带那个异常函数没有处理的情况处理了就好了(处理后返回一个 Response 对象即可,一定要有返回值,否则没多大意义)
歩鄹
- 先将异常处理交给 rest_framework.views 的 exception_handler 去处理
- 判断处理的结果(返回值)response,有值代表 drf 已经处理了,None 需要自己处理
- 可以根据 exc 的类型再细化处理
if isinstance(exc, '哪个异常'): # 再怎么处理
- 可以根据 exc 的类型再细化处理
api/exception.py
记得自己把报错信息记到日志里面去
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.views import Response
from rest_framework import status
def exception_handler(exc, context):
# drf的exception_handler做基础处理
response = drf_exception_handler(exc, context)
# 为空,说明 drf 中没有对应的处理,咱们自定义二次处理
if response is None:
# print(exc)
# # Book matching query does not exist
# print(context)
# # {'view': <api.views.Book object at 0x000001FED29DD860>}, 'args': (), 'kwargs': {'pk': '4'}, 'request': <rest_framework.request.Request object at 0x000001FED2CD9EF0>
# 这里后期应该写成系统日志才对(这只是演示的伪代码)
print('%s - %s - %s' % (context['view'], context['request'].method, exc))
# <api.views.Book object at 0x000002505A2A9A90> - GET - Book matching query does not exits.
return Response({
'detail': '服务器错误'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
return response
配置上,让其生效
dg_proj/settings.py
# 1.确保已注册 drf
INSTALLED_APPS = [
# ...
'api.apps.ApiConfig',
'rest_framework', # 注册 drf
]
# 2.在 restframework 的配置中配置该自定义异常模块
REST_FRAMEWORK = {
# ...
'EXCEPTION_HANDLER': 'api.exception.exception_handler', # 全局配置异常模块
}