中间件介绍


1. 官方说法: 

  • 中间件是一个用来处理Django的请求和响应的框架级别的钩子
  • 它是一个轻量、低级别的插件系统,
  • 用于在全局范围内改变Django的输入和输出。
  • 每个中间件组件都负责做一些特定的功能

2. 通俗理解:

  • 中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作(类似于装饰器,只不过就是所有的视图函数都自动使用上了这个装饰器)
  • 本质上就是一个自定义类,类中定义了几个方法
  • Django框架会在请求的特定的时间去执行这些方法

3. 什么时候使用中间件:

  • 当所有的视图函数在执行前后需要做一些额外的功能的时候(如: 登陆验证),这样就无需编写装饰器,然后给每一个视图函数添加装饰器

4. 注意事项:

  • 中间件是作用于全局的,所以需要谨慎使用,使用不当会影响性能

5. 中间件的执行顺序:

  • 在 settings.py 中的 MIDDLEWARE 配置项里面所注册的中间件会从上往下执行

自定义中间件


1. 自定义一个中间件示例

# my_middleware.py

from django.utils.deprecation import MiddlewareMixin


class MD1(MiddlewareMixin):

    def process_request(self, request):
        print('MD1里面的 process_request')

    def process_response(self, request, response):
        print('MD1里面的 process_response')
        return response

# 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',
    'my_middleware.MD1'  # 注册(添加)自定义的中间件
]

中间件的五个方法


中间件可以定义五个方法,分别是:(常用的是process_request、process_response、process_view)

  • 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)

参数说明: 

  • request: 和视图函数中 request 参数一样,可以获取到请求的相关信息
  • response: 视图函数的返回值(即: response对象,三件套)
  • view_func: Django即将执行的视图函数 
  • view_args: Django即将执行的视图函数所接收的位置参数,不包含第一个视图参数 response
  • view_kwargs: Django即将执行的视图函数所接收的关键字参数,不包含第一个视图参数 response
  • exception: 视图函数异常产生的Exception对象

1.process_request(self, request)

  • 何时执行: 当WSGI拿到浏览器发送过来的相关请求之后

  • 执行顺序: 按照中间件的注册顺序(在 settings.py 中的 MIDDLEWARE 配置项里面所注册的中间件顺序),从上往下执行

  • 返回值:

    • 返回None,继续执行后续的中间件的 process_request 方法
    • 返回response对象(即: 三件套),不执行后续中间件的 process_request 方法,直接将 response对象 返回给浏览器

  • 参数说明: 
    • request: 和视图函数中 request 参数一样,可以获取到请求的相关信息

# my_middleware.py

from django.shortcuts import HttpResponse, render, redirect
from django.utils.deprecation import MiddlewareMixin

# 执行顺序: 先执行 MD1 再执行 MD2,如果MD1返回了response对象,那么MD2就不会执行

class MD1(MiddlewareMixin):

    def process_request(self, request):
        print('MD1里面的 process_request')
# return None  # 如果返回 None 继续往下执行
        return HttpResponse('MD1')  # 如果返回 response对象,不执行后续的中间件直接将 response对象 返回给浏览器


class MD2(MiddlewareMixin):

    def process_request(self, request):
        print('MD2里面的 process_request')
# return None
        return HttpResponse('MD2')

# settings.py

MIDDLEWARE = [
……
    'my_middleware.MD1',
    'my_middleware.MD2',
]

2.process_response(self, request, response)

  • 何时执行: 当得到response对象(即: 三件套)的时候

  • 执行顺序: 按照中间件的注册倒序(在 settings.py 中的 MIDDLEWARE 配置项里面所注册的中间件顺序),从下往上执行

  • 返回值:

    • 返回response对象(即: 三件套),必须返回,继续执行后续的中间件的 process_response 方法,返回response对象不会直接返回给浏览器,而是传递给下一个中间件,直到传递到最后一个中间件才返回给浏览器

  • 参数说明: 

    • request: 和视图函数中 request 参数一样,可以获取到请求的相关信息
    • response: 视图函数的返回值(即: response对象,三件套)

# my_middleware.py

from django.shortcuts import HttpResponse, render, redirect
from django.utils.deprecation import MiddlewareMixin


# 执行顺序: 先执行 MD2 再执行 MD1

class MD1(MiddlewareMixin):

    def process_response(self, request, response):
        print('MD1里面的 process_response')
# return response  # 可以返回得到的 response对象
        return HttpResponse('MD1')  # 也可以返回三件套的 response对象


class MD2(MiddlewareMixin):

    def process_response(self, request, response):
        print('MD2里面的 process_response')
# return response  # 可以返回得到的 response对象
        return HttpResponse('MD2')  # 也可以返回三件套的 response对象

# settings.py

MIDDLEWARE = [
……
    'my_middleware.MD1',
    'my_middleware.MD2',
]

3.process_view(self, request, view_func, view_args, view_kwargs)

  • 何时执行: 在urls.py中找到对应关系之后,在执行真正的视图函数之前

  • 执行顺序: 按照中间件的注册顺序(在 settings.py 中的 MIDDLEWARE 配置项里面所注册的中间件顺序),从上往下执行

  • 返回值:

    • 返回None,继续执行后续的中间件的 process_view 方法
    • 返回response对象(即: 三件套),不执行后续中间件的 process_view 方法,直接将 response对象 返回给浏览器

  • 参数说明: 

    • request: 和视图函数中 request 参数一样,可以获取到请求的相关信息
    • view_func: Django即将执行的视图函数 
    • view_args: Django即将执行的视图函数所接收的位置参数,不包含第一个视图参数 response
    • view_kwargs: Django即将执行的视图函数所接收的关键字参数,不包含第一个视图参数 response

# my_middleware.py

from django.shortcuts import HttpResponse, render, redirect
from django.utils.deprecation import MiddlewareMixin


# 执行顺序: 先执行 MD1 再执行 MD2,如果MD1返回了response对象,那么MD2就不会执行

class MD1(MiddlewareMixin):

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('MD1里面的 process_view')
        print(view_func, view_args, view_kwargs)  # <function index at 0x0000022C75F49598> () {}

# return None  # 如果返回 None 继续往下执行
        return HttpResponse('MD1')  # 如果返回 response对象,不执行后续的中间件直接将 response对象 返回给浏览器


class MD2(MiddlewareMixin):

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('MD2里面的 process_view')
        print(view_func, view_args, view_kwargs)

# return None
        return HttpResponse('MD2')

# settings.py

MIDDLEWARE = [
……
    'my_middleware.MD1',
    'my_middleware.MD2',
]

4.process_exception(self, request, exception)

  • 何时执行: 当视图函数中抛出异常的时候才执行

  • 执行顺序: 按照中间件的注册倒序(在 settings.py 中的 MIDDLEWARE 配置项里面所注册的中间件顺序),从下往上执行

  • 返回值:

    • 返回None,继续执行后续的中间件的 process_exception 方法
    • 返回response对象(即: 三件套),不执行后续中间件的 process_exception 方法,直接将 response对象 返回给浏览器

  • 参数说明: 

    • request: 和视图函数中 request 参数一样,可以获取到请求的相关信息
    • exception: 视图函数异常产生的Exception对象

# my_middleware.py

from django.shortcuts import HttpResponse, render, redirect
from django.utils.deprecation import MiddlewareMixin


# 执行顺序: 先执行 MD2 再执行 MD1,如果MD2返回了response对象,那么MD1就不会执行

class MD1(MiddlewareMixin):

    def process_exception(self, request, exception):
        print('MD1里面的 process_exception')
        print(exception)

# return None
        return HttpResponse('MD1 抛出了一个异常')


class MD2(MiddlewareMixin):

    def process_exception(self, request, exception):
        print('MD2里面的 process_exception')
        print(exception)  # 抛出了一个异常

# return None  # 如果返回 None 继续往下执行
        return HttpResponse('MD2 抛出了一个异常')  # 如果返回 response对象,不执行后续的中间件直接将 response对象 返回给浏览器

# views.py

from django.shortcuts import render, HttpResponse


def index(request):
    raise ValueError('抛出了一个异常')
    return HttpResponse('index.html')

# settings.py

MIDDLEWARE = [
……
    'my_middleware.MD1',
    'my_middleware.MD2',
]

5.process_template_response(self, request, response)

  • 何时执行: 视图函数执行完后,在执行视图函数返回 response对象 之前(即: process_response方法之前)

  • 执行顺序: 按照中间件的注册倒序(在 settings.py 中的 MIDDLEWARE 配置项里面所注册的中间件顺序),从下往上执行

  • 返回值:

    • 返回response对象(即: 三件套),且该对象里面必须要有 render() 方法,必须返回,继续执行后续的中间件的 process_template_response 方法,返回response对象不会直接返回给浏览器,而是传递给下一个中间件,直到传递到最后一个中间件才返回给浏览器

  • 参数说明: 

    • request: 和视图函数中 request 参数一样,可以获取到请求的相关信息
    • response: 视图函数的返回值(即: response对象,三件套)

  • 注意事项: process_template_response 接受的response对象和返回的response对象里面必须要有render方法

# my_middleware.py

from django.utils.deprecation import MiddlewareMixin


# 执行顺序: 先执行 MD2 再执行 MD1

class MD1(MiddlewareMixin):

    def process_template_response(self, request, response):
        print('MD1里面的 process_template_response')
        return response


class MD2(MiddlewareMixin):

    def process_template_response(self, request, response):
        print('MD2里面的 process_template_response')
        return response

# views.py

from django.shortcuts import render, HttpResponse


def index(request):
    ret = HttpResponse('index.html')

    def render():
        return HttpResponse('render_fn')

    ret.render = render

    return ret

# settings.py

MIDDLEWARE = [
……
    'my_middleware.MD1',
    'my_middleware.MD2',
]

中间件中的request


  • 当我们在中间件中给 request 对象添加额外的属性参数的时候,视图函数可以通过 request 对象获取到该参数

# my_middleware.py

from django.shortcuts import HttpResponse, render, redirect
from django.utils.deprecation import MiddlewareMixin


class MD1(MiddlewareMixin):

    def process_request(self, request):
        request.m_data = '在中间件中给request对象添加新的属性参数'
        return None

# settings.py

MIDDLEWARE = [
 ……
    'my_middleware.MD1',
]

# views.py

def index(request):
    m_data = request.m_data
    print(m_data)  # 在中间件中给request对象添加新的属性参数
    return HttpResponse('index')

中间件的执行流程


1. process_request 和 process_response 方法

  • 实线的蓝色箭头表示,当返回了一个response对象(即: 三件套)的时候,不执行后续中间件的 process_request 方法


2. process_view 和 process_response 方法

  • 虚线的蓝色箭头表示,当返回了一个response对象(即: 三件套)的时候,不执行后续中间件的 process_view 方法


3.process_request 、 process_view 、process_response 方法

  • 虚线的蓝色箭头表示,当返回了一个response对象(即: 三件套)的时候,不执行后续中间件的 process_request 或 process_view 方法


4. 中间件方法的执行顺序

  • 虚线的箭头代表只有在特定情况下才能执行,且是不常用的
  • SecurityMiddleware 等都是 settings.py 中的 MIDDLEWARE 配置项里面中间件


Django的请求流程图



  • 数字代表的是请求顺序,字母代表的是响应顺序


中间件版登录验证 


middleware_login.rar

# middlewares.py

from django.shortcuts import render, HttpResponse, redirect
from django.utils.deprecation import MiddlewareMixin


class AuthMD(MiddlewareMixin):
    white_list = ['/login/', '/home/', '/logout/']  # 白名单
    black_list = ['/news/', ]  # 黑名单
    no_login_list = ['/login/', '/logout/']  # 不跳转到登陆页面

    def process_request(self, request):
        is_login = request.session.get('is_login', '0')  # 获取session

        url = request.path_info
        next_url = request.get_full_path()

        if url in self.black_list:  # 判断是否在黑名单中
            return HttpResponse('这是一个非法的网址')
        elif url in self.white_list and is_login == '1':  # 判断是否在白名单中,且已经进行了登陆
            return
        elif url not in self.no_login_list:
            return redirect('/login/?next={}'.format(next_url))

# views.py

from django.shortcuts import render, redirect


# 登录
def login(request):
    if request.method == 'POST':
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        next_url = request.GET.get('next')

        if user == 'Kevin' and pwd == '123':
            if next_url:
                ret = redirect(next_url)
            else:
                ret = redirect('/home/')

            request.session['is_login'] = '1'  # 设置session
            request.session.set_expiry(10)
            return ret
    return render(request, 'login.html')


# 注销
def logout(request):
    request.session.flush()  # 清空当前请求的所有session,和与该session相关的cookie
    return redirect('/login/')


def home(request):
    return render(request, 'home.html')


def news(request):
    return render(request, 'news.html')

# urls.py

from django.conf.urls import url
from django.contrib import admin
from app01.views import *

urlpatterns = [
    url(r'^login/', login),
    url(r'^home/', home),
    url(r'^news/', news),
    url(r'^logout/', logout),
]

# settings.py

MIDDLEWARE = [
……
    'middlewares.AuthMD'  # 注册(添加)自定义中间件 AuthMD
]