Django 的中间件执行顺序
11月 12, 2017
中间件是 Django 用来处理请求和响应的钩子框架。它是一个轻量级的、底层级的“插件”系统,用于全局性地控制 Django 的输入或输出。
MIDDLEWARE #
Django 自带了一些已经内置的中间件,你可以直接使用,它们被记录在 built-in middleware reference 中。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'api_permission.middleware.APIPermCheckMiddleware',
]
为了能够尽可能使用 Django 自带的中间件的功能和一些安全性校验,一般我们自定义的中间件需要放到 settings.MIDDLEWARE
列表的最后一个,但也需要视实际情况而定。
Django Middlware 的执行顺序是按照 settings.py
里的 MIDDLEWARE
列表而定的,一个 HTTP 请求是从上到下顺序执行的,而在 View 层中处理完业务逻辑后,又从下往上倒序返回。下面就按照一个请求在中间件的执行顺序来依次介绍。
process_request #
请求在执行 view 层逻辑之前,会 Django 会先执行每个中间件的 process_request
,它有一个 request
参数,在这个过程中,如果遇到问题,中间件可能会直接报错并返回请求。
class APIPermCheckMiddleware(MiddlewareMixin):
def process_request(self, request):
path = request.path
method = request.method
header_token = request.META.get(AUTHORIZATION_HEADER, None)
user = request.user or AnonymousUser()
if header_token:
try:
token = header_token.strip().split(' ')
assert len(token) > 0, f"token maybe invalid: {header_token}"
token_obj = Token.objects.get(key=token[-1])
user = token_obj.user
except Token.DoesNotExist as e:
msg = f"api_permission checker: token not exists: {e}"
return self._return_403_res(msg)
except Exception as e:
msg = f"{e}"
raise APIPermissionException(msg)
api_prefix_list = []
if type(API_PREFIX) == str:
if API_PREFIX == '/':
api_prefix_list = ['/']
else:
if not API_PREFIX.startswith('/'):
api_prefix_list.append('/' + str(API_PREFIX))
else:
api_prefix_list = API_PREFIX
elif type(API_PREFIX) == list:
for prefix in API_PREFIX:
if not prefix.startswith('/'):
prefix = '/' + str(prefix)
api_prefix_list.append(prefix)
if not path.startswith(ADMIN_SITE_PATH) and not user.is_superuser:
for api_prefix in api_prefix_list:
if path.startswith(api_prefix):
if not self._has_permission(path, user, method):
return self._return_403_res(f'permission denied: user: {user}, method: {method}, path: {path}')
def _has_permission(self, path, user, method):
groups = user.groups.all()
queryset = APIPermissionModel.objects.filter(group__in=groups, method__in=[method, APIPermissionModel.ALL], active=True)
for api in queryset:
if re.match(api.pattern, path):
if api.method in [APIPermissionModel.ALL, method]:
return True
return False
def _return_403_res(self, msg):
res = {
'code': PERMISSION_DENIED_CODE,
'msg': msg,
}
return JsonResponse(res, status=status.HTTP_403_FORBIDDEN)
比如这个是一个校验请求的用户是否有权限访问相关路径的功能。如果没有报错(返回值为None),Django 就会执行下一个中间件,否则直接把方法 return 的内容返回作为 response,不再执行其他中间件。
process_view #
process_view(request, view_func, view_args, view_kwargs)
request
:HttpRequest
对象。view_func
:真正的业务逻辑视图函数。view_args
:位置参数列表view_kwargs
:关键字参数字典
方法的返回值可以是 None 或一个 HttpResponse 对象,如果是 None,则继续按照 django 定义的规则向后继续执行,如果是 HttpResponse 对象,则直接将该对象返回给用户。
process_view
在Django调用真正的业务视图之前被执行,并且以正序执行。当 process_request
正常执行完毕后,会进入 urlconf 路由阶段,并查找对应的视图,在执行视图函数之前,会先执行process_view
中间件钩子。
process_exception #
process_exception(request, exception)
如果一个请求在视图层的执行过程中引发了异常,并且视图代码没有捕获这个错误,就可以 process_exception
来捕获这个异常,process_exception()
要么返回一个 None
,要么返回一个 HttpResponse
对象。如果返回的是HttpResponse
对象 ,模板响应和响应中间件将被调用 ,否则进行正常的异常处理流程。
可以推断出,此时是以逆序的方式调用每个中间件的 process_exception
方法。
process_template_response #
process_template_response(request, response)
request 是一个 HttpRequest 对象,response 是 TemplateResponse 对象,它通过 Django 视图或中间件返回。process_template_response
必须返回一个实现了 render
方法的响应对象,否则不会触发此方法。它可以通过改变response.template_name
和 response.context_data
来改变给定的 response
,然后将 response
对象给到 process_response
。
process_response #
process_response(request, response)
process_response
有两个参数,request 和 response,request是请求内容,response 是视图函数返回的 HttpResponse 对象。该方法的返回值必须是一个 HttpResponse 对象,不能是None。process_response
不会中断整个流程,Django 会依次逆向执行完每个中间件的 process_response
方法。
总结 #
每到 Django 需要执行中间件的阶段,都是整个 MIDDLEWARE
列表中每个中间件的 process_xx 方法按顺序走一遍,不过在视图前执行的是正序,在视图后执行的是逆序。
最后顺便推荐一下我实现的一个基于 Django Middleware 的 API 权限校验器,可以根据正则表达式对各个用户组所请求的 URL 进行校验,只在 URL 对应的正则公式不通过时返回 403,不干预业务,非常实用,发在了 Github。