Django 的中间件执行顺序

Django 的中间件执行顺序

11月 12, 2017
Python, Django

中间件是 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)
  • requestHttpRequest 对象。
  • 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_nameresponse.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

本文共 1431 字,上次修改于 Feb 25, 2024,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。

相关文章

» Django 的软删除设计

» 浅谈 Django-REST-Framework 的设计与源码

» Ubuntu 下部署 Django 应用