Django 15 Django进阶-中间件

Django 15 Django进阶-中间件

中间件 Middleware 官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。 简单的说,中间件就是HTTP请求来了以后,到达自行编写的逻辑之前经过的处理部分

中间件 Middleware

官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。 简单的说,中间件就是HTTP请求来了以后,到达自行编写的逻辑之前经过的处理部分.回想一下CSRF的相关内容:如果在打开CSRF中间件但不在模板内增加CSRF_TOKEN的时候,浏览器也能够收到HTTPResponse 403 Forbidden,但查看后端代码可以发现,请求并未发送到自行编写的视图.那么浏览器是从哪里收到的HTTP响应呢,答案就是中间件,也就是中间件处理了浏览器发来的HTTP请求,发现其中有错误,就没有转交视图,而是直接就给浏览器返回了响应. 在Django里,所有使用的中间件都在settings.py里进行注册.每一个中间件都是全局的,意味着在HTTP请求发过来的时候,这个请求都要经过所有的在settings.py里注册的中间件.全部没有问题之后,才会被交给url分发器和视图.如果中间件设置不当或者数量过多,则会影响性能. settings.py里的默认内置中间件配置如下:
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',
]
这个配置是一个字符串列表,表明了一个个类,在请求类的时候,Django就会将请求转发的所有的中间件里进行处理.所有的middleware都接受请求并且返回响应,就像视图一样,中间件也有着特别的规定.

中间件规则与请求处理

中间件至少要有如下的5个方法:
  • process_request(self,request)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_template_response(self,request,response)
  • process_exception(self, request, exception)
  • process_response(self, request, response)
上边所有方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。 中间件传递HttpResponse的顺序非常重要.典型的HTTP请求进来然后给浏览器发送响应的流程是:
  1. 当配置多个中间件时,中间件的顺序是MIDDLEWARE中的注册顺序,也就是列表的索引值.对应不同的操作,顺序会与注册顺序相反或相同
  2. 不同中间件之间传递的request都是同一个对象
  3. 请求进来的时候,依次被各个中间件的process_request按照注册顺序执行,如果此时process_request返回响应,直接交给当前中间件的process_response方法
  4. process_request出现异常,会调用当前中间件的process_exception方法处理异常.process_exception返回响应则交给process_response逆向处理,返回None则将异常逆向交给上一层的process_exception方法
  5. process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法
  6. process_view方法执行出问题产生响应,直接从最后一个中间件的process_response开始走
  7. 全部process_view方法执行完毕之后,交给视图函数处理
  8. 视图函数处理请求返回响应
  9. 视图函数如果带有render方法,则倒序执行所有中间件的process_template_response
  10. 所有中间件的process_template_response执行完毕,倒序执行所有中间件的process_response方法
  11. 最终的HTTP响应在执行完第一个注册的中间件的process_response方法之后返回给浏览器
图示如下: 中间件工作流程示例 简单的说,HTTP发来的请求就像是要流入视图函数的水,需要从外到内(中间件注册的顺序)一层层穿过过滤膜,如果在穿过的过程没有引发错误或者让中间件拒绝(即中间件产生响应),水就会到达视图函数. 视图函数发出的响应,以及执行过程中的错误,就像是要出去的水,必须从响应或者错误发生处反向一层层过滤流出去,如果没有错误,这这个响应就会发出去,如果触发了中间件响应,中间件就会以自己的响应或者错误处理一层层发回去返回给浏览器. 当然这只是个简单的例子,实际上同一层中间件在请求进来的时候会两次穿过,第一次是process_request,第二次是process_view. 为了方便理解,再放上一个Django的处理请求全图: Djanog请求处理全过程 现在可以知道,如果需要对所有的HTTP请求做一些操作(典型的操作安全性相关的认证比如用户登录等),那么相比视图函数,更应该把这些操作组成一个中间件来处理请求.

自定义中间件

先从简单的中间件做起.中间件和视图一样,Django是不限定代码写在哪里的,但是一般都做成一个独立的包或者模块用于引入. Django中为自定义中间件提供了基类MiddlewareMixin,继承该基类就可以得到一个默认没有进行任何处理的中间件类.在此基础就可以自定义中间件. 先来定义一系列中间件:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse


class Myware1(MiddlewareMixin):
    def process_request(self, request):
        print("我是1号中间件 里面的 process_request")

    def process_exception(self, request, exception):
        print("我是1号中间件的process_exception")
        return HttpResponse("来自一号中间件错误处理")

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("我是1号中间件的process_view,我的参数是 {} | {} | {}".format(view_func, view_args, view_kwargs))

    def process_response(self, request, response):
        print("我是1号中间件 里面的 process_response")
        return response

    def process_template_response(self, request, response):
        print("我是1号中间件的template函数")
        return response


class Myware2(MiddlewareMixin):
    def process_request(self, request):
        print("我是2号中间件 process_request")

    def process_exception(self, request, exception):
        print("我是2号中间件的process_exception")
        # return HttpResponse("来自2号中间件错误处理响应")

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("我是2号中间件的process_view,我的参数是 {} | {} | {}".format(view_func, view_args, view_kwargs))

    def process_response(self, request, response):
        print("我是2号中间件 里面的 process_response")
        # return HttpResponse("来自二号中间件的Response函数")
        return response


    def process_template_response(self, request, response):
        print("我是2号中间件的template函数")
        return response


class Myware3(MiddlewareMixin):
    def process_request(self, request):
        print("我是3号中间件 process_request")
        # return HttpResponse("来自三号中间件的request函数")

    def process_exception(self, request, exception):
        print("我是3号中间件的process_exception")
        # return HttpResponse("来自3号中间件错误处理响应")

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("我是3号中间件的process_view,我的参数是 {} | {} | {}".format(view_func, view_args, view_kwargs))

    def process_response(self, request, response):
        print("我是3号中间件 里面的 process_response")
        # return HttpResponse("来自三号中间件的Response函数")
        return response

    def process_template_response(self, request, response):
        print("我是3号中间件的template函数")
        return response
定义了三个最基础的中间件,包含全部5个方法. 然后写一个特别的视图对应/login/地址:
class Login(views.View):

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        print("我是视图函数login,我的地址是 {}".format(self))
        return super(Login, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        def render():
            return HttpResponse("函数内定义的render方法")
        ret = HttpResponse("OK")
        ret.render = render
        return ret
在访问了/login/之后,在python console里可以看到如下结果:
我是1号中间件 里面的 process_request
我是2号中间件 process_request
我是3号中间件 process_request
我是1号中间件的process_view,我的参数是  | () | {}
我是2号中间件的process_view,我的参数是  | () | {}
我是3号中间件的process_view,我的参数是  | () | {}
我是视图函数login,我的地址是 
我是3号中间件的template函数
我是2号中间件的template函数
我是1号中间件的template函数
我是3号中间件 里面的 process_response
我是2号中间件 里面的 process_response
我是1号中间件 里面的 process_response
这个图很好的说明了实际的执行顺序,HTTP请求先按注册顺序穿过所有中间件的process_request方法,再按注册顺序穿过所有中间件的process_view方法,到达视图函数,响应反向穿过所有中间件的template方法,再反向穿过所有中间件的response方法,最后返回给浏览器. 然后在视图函数中故意引发一个错误,看一下处理过程:
我是1号中间件 里面的 process_request
我是2号中间件 process_request
我是3号中间件 process_request
我是1号中间件的process_view,我的参数是  | () | {}
我是2号中间件的process_view,我的参数是  | () | {}
我是3号中间件的process_view,我的参数是  | () | {}
我是视图函数login,我的地址是 
我是3号中间件的process_exception
我是2号中间件的process_exception
我是1号中间件的process_exception
我是3号中间件 里面的 process_response
我是2号中间件 里面的 process_response
我是1号中间件 里面的 process_response
在视图函数中引发了错误之后,按照图里的处理顺序,中间件2和3的process_exception方法返回的是None,所以交给下一层处理,到了中间件1的process_exception方法,返回了一个响应,然后这个响应再逐层通过所有中间件的process_response返回. 下边来详细看一下这五个方法:

中间件的5个方法

process_request(self,request)
  • 只有一个参数,就是请求,这个和视图函数最终接到的request参数是同一个
  • 返回None,就继续将请求交给下一个中间件
  • 返回HttpResponse,则交给当前中间件的process_response处理,开始向上返回
process_response(self, request, response)
  • 两个参数,一个是request,一个是response.response是视图函数或者其他中间件方法返回的HttpResponse对象
  • 该方法必须要有返回值,而且必须是HttpResponse对象
process_view(self, request, view_func, view_args, view_kwargs)
    • 该方法有四个参数:
    • request是HttpRequest对象
    • view_func是Django即将使用的视图函数
    • view_args是将传递给视图的位置参数的列表
    • view_kwargs是将传递给视图的关键字参数的字典
    • view_args和view_kwargs都不包含第一个视图参数request
  • 返回None,就按顺序继续将请求交给下一个中间件
  • 返回HttpResponse,则交给最底层的中间件的process_response处理,开始向上返回
process_exception(self, request, exception)
  • 两个参数,exception是视图函数异常产生的Exception对象
  • 返回None,就继续将错误交给下一个中间件处理
  • 返回HttpResponse,则交给最底层的中间件的process_response方法,开始向上返回
process_template_response(self,request,response)
  • response必须是TemplateResponse对象才会触发这个方法,在视图函数执行完成后就执行该方法
  • 该方法必须要有返回值,而且必须是HttpResponse对象
通过比较,可以发现,除了请求进来的时候,被某个中间件的process_request方法返回一个响应,是从当前中间件的process_response方法开始向上返回之外,其他的方法一旦返回HttpResponse,都要从最下层的中间件开始走. 可以发现,中间件的层级存在,为按照一定次序合理的控制所有与站点往来的HTTP请求和响应提供了逻辑基础,只要采取编写得当的中间件,则很多全局性的工作可以很方便的完成.视图函数可以专注于具体逻辑处理.
LICENSED UNDER CC BY-NC-SA 4.0
Comment