Django源码剖析(07)--全局异常处理

前几天学习了SpringBoot, 在SpringBoot中异常处理用起来相当的便利, 而Django中似乎并没有为我们提供这样的组件, 所以仍然需要自己分析源码, 造一个适合自身业务的轮子.

0. Define: Django-version: 1.11.13

1. 请求处理

Django的请求不会直接到我们编写的视图中, 而是首先经过Middleware的过滤, 进行一些组件的安装, 再进入到视图之中. 如果在Middleware层出现了异常, 请求会直接返回.
那么这个过程是如何以代码的形式体现的呢?

1.1 BaseHandler

django.core.handlers.base中只有BaseHandler这么一个类, 同时也是核心的处理类. 关键函数其实是_get_response方法, 该函数但并不是很长.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def _get_response(self, request):
response = None

if hasattr(request, 'urlconf'):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver()

# 在这里进行url的匹配, callback其实就是视图函数或者视图类的闭包函数
# callback_args, callback_kwargs也就是视图函数所接受的参数
resolver_match = resolver.resolve(request.path_info)
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match

# 非关键代码, 略去

if response is None:
wrapped_callback = self.make_view_atomic(callback)
try:
# 执行视图函数, 获取response对象
response = wrapped_callback(request, *callback_args, **callback_kwargs)
except Exception as e:
# 如果出现了异常, 交由self.process_exception_by_middleware进行处理
response = self.process_exception_by_middleware(e, request)

# 中间儿的代码也可以直接略去

return response


def process_exception_by_middleware(self, exception, request):
"""
self._exception_middleware为一个list, 在load_middleware函数中进行初始化.
但是, Django内置的中间中没有任何一个拥有process_exception方法, 所以该list为空, 直接
raise一个空的异常
"""
for middleware_method in self._exception_middleware:
response = middleware_method(request, exception)
if response:
return response
raise

那么这个空的异常又是有谁来接收的呢? 由convert_exception_to_response这个函数进行处理: 这里Django其实将_get_response函数进行一次闭包的封装

1
2
3
4
5
6
7
8
9
def convert_exception_to_response(get_response):
@wraps(get_response, assigned=available_attrs(get_response))
def inner(request):
try:
response = get_response(request)
except Exception as exc:
response = response_for_exception(request, exc)
return response
return inner

其中的get_response参数在load_middleware函数中将_get_response该函数进行传入了, 可以看到这里有一个try...except来处理异常. 在出现异常之后会调用response_for_exception函数, 将异常转换为response对象, 包括404, 500等错误返回.
整个流程:

其实这上面儿的一大堆都是为了解释self._exception_middleware这个列表的, 如果这个列表不为空, 那么程序就会处理列表中的函数, 该函数只需要一个response对象, 而后Django会自行处理.
所以, 在分析完源码之后我们得到的结论就是: 写一个中间件, 包含process_exception函数. 该函数接受request, exception两个参数, 处理后返回一个response对象即可.

2. 编写包含process_exception函数的中间件

2.1 Simple Demo

打代码由简至繁, 所以先写一个能用的中间件, 然后再进行拓展.

1
2
3
4
5
6
7
8
9
from django.http import JsonResponse
from django.utils.deprecation import MiddlewareMixin


class ExceptionProcessMiddleware(MiddlewareMixin):
def process_exception(self, request, exec):
message = "出错啦~"
response = JsonResponse({"message": message})
return response

将该中间件添加至配置文件的MIDDLEWARE中, 然后在任意的一个视图函数中抛一个异常出来. 就可以看到效果了.

2.2 Conflict Demo

在上面儿我们定义了一个非常简单的异常处理demo, 但是太简单了, 并不能满足我们的需求.
通常来讲, 我们的错误返回是这样的:

1
2
3
4
5
6
{
"code": "xxx",
"msg": "错误提示",
"status": "fail"
"data": None
}

其中code为错误码, 一般来讲会统一进行定义, 可以从10000开始, 也可以从0开始. 每一个错误码都是唯一的, 并且对应着一条错误信息. 这样的话其实就需要自定义一个异常, 然后主动的抛出这个异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 自定义的异常
class DemoException(Exception):
code = 10000
msg = "数据库连接发生异常"

# view
class MyView(View):
def get(self, request):
raise DemoException()

# 异常中间件
class ExceptionProcessMiddleware(MiddlewareMixin):
def process_exception(self, request, exec):
ret_json = {
"code": getattr(exec, "code", 0),
"msg": getattr(exec, "msg", "some error happened"),
"status": "fail",
"data": None
}
# 在这里还需要进行日志的记录, 以便排查.
logger.error(traceback.format_exec()) # traceback为python标准库, 可以直接import

return JsonResponse(ret_json)