Django之中间件

中间件

什么是中间件

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

但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。

我们一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,能够找到如下图的MIDDLEWARE配置项。

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',
]

MIDDLEWARE配置项是一个列表,列表中的一个个元素就是一个中间件

中间件可以定义五个方法,分别是:

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

上面的方法中其实重要的主要是process_request和process_response方法

以上的方法的返回值可以是None或者是一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。

 自定义中间件

# 在manage.py的同目录下创建一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

class MD2(MiddlewareMixin):
    def process_request(self, request):
        print('MD2里面的process_request')

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

上面我们需要在setting的MIDDLEWARE中把我们自定义的中间件加到里面去

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',
    'my_middleware.MD2',
]

我们在路由系统写好相关的路由和视图函数中写好相应的函数当我们访问的时候就出现以下的打印情况

MD1里面的 process_request
MD2里面的process_request
views的视图函数
MD2里面的process_response
MD1里面的process_response

当我们把MD1和MD2在setting的位置互换的时候打印的结果也是互换的。

process_request和process_response

process_request有一个参数,就是request,这个request和视图函数中的request是一样的。

它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行下一个中间件和视图函数,而将相应对象返回给浏览器。

还是上面的例子

from django.shortcuts import HttpResponse, redirect
from django.utils.deprecation import MiddlewareMixin
"""
自定义的中间件
"""

class MD1(MiddlewareMixin):
    def process_request(self,request):
        print('MD1里面的 process_request')
        return HttpResponse('MD1')

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

class MD2(MiddlewareMixin):
    def process_request(self, request):
        print('MD2里面的process_request')

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

执行结果

MD1里面的 process_request
MD1里面的process_response


页面显示的是"MD1"

从上面我们可以知道当process_request直接返回的是HttpResponse对象的时候他就不会走下一个中间件,直接跳转到该中间件的process_response然后一步步向上执行中间件的process_response

下图就是中间件的process_request和process_response的执行顺序图

process_request(self, request)
  执行顺序:
    按照注册的顺序(在settings.py里面设置中 从上到下的顺序)
  何时执行:
    请求从wsgi拿到之后
  返回值:
    返回None,继续执行后续的中间件的process_request方法
  返回response , 不执行后续的中间件的process_request方法

process_response
  执行顺序:
    按照注册顺序的倒序(在settings.py里面设置中 从下到上的顺序)
  何时执行:
    请求有响应的时候
  返回值:
    必须返回一个response对象

process_view

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

参数:

  • request是HttpRequest对象。
  • view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
  • view_args是将传递给视图的位置参数的列表.
  • view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。

Django会在调用路由系统没写之后与视图函数之前调用process_view方法。

它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中process_response方法并将应用到该HttpResponse并返回结果。

from django.shortcuts import HttpResponse, redirect
from django.utils.deprecation import MiddlewareMixin
"""
自定义的中间件
"""

class MD1(MiddlewareMixin):
    def process_request(self,request):
        print('MD1里面的 process_request')
        # return HttpResponse('MD1')

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

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("=" * 40)
        print("MD1 中的 process_view")
        print(view_func, view_func.__name__)

class MD2(MiddlewareMixin):
    def process_request(self, request):
        print('MD2里面的process_request')

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

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("=" * 40)
        print("MD2 中的 process_view")
        print(view_func, view_func.__name__)

执行的时得到的输出

MD1里面的 process_request
MD2里面的process_request
========================================
MD1 中的 process_view
<function test at 0x0000027A817F0400> test
========================================
MD2 中的 process_view
<function test at 0x0000027A817F0400> test
views的视图函数
MD2里面的process_response
MD1里面的process_response

 执行的顺序图:

process_view(self, request, view_func, view_args, view_kwargs):
  执行顺序:
    按照注册的顺序(在settings.py里面设置中 从上到下的顺序)
  何时执行:
    在urls.py中找到对应关系之后 在执行真正的视图函数之前
  返回值:
    返回None,继续执行后续的中间件的process_view方法
    返回Httpresponse,不会继续后面的process_view函数,直接执行中间件的逆序的第一个process_response,再往上执行其他中间件的process_response,不会执行视图函数,页面返回response

process_exception

process_exception(self, request, exception)

参数:

  • 一个HttpRequest对象
  • 一个exception是视图函数异常产生的Exception对象。

这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。

from django.shortcuts import HttpResponse, redirect
from django.utils.deprecation import MiddlewareMixin
"""
自定义的中间件
"""

class MD1(MiddlewareMixin):
    def process_request(self,request):
        print('MD1里面的 process_request')
        # return HttpResponse('MD1')

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

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("=" * 40)
        print("MD1 中的 process_view")
        print(view_func, view_func.__name__)
        # return HttpResponse('process_view')

    def process_exception(self, request, exception):
        print(exception)
        print("MD1 中的process_exception")
        return HttpResponse('异常')

class MD2(MiddlewareMixin):
    def process_request(self, request):
        print('MD2里面的process_request')

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

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("=" * 40)
        print("MD2 中的 process_view")
        print(view_func, view_func.__name__)

    def process_exception(self, request, exception):
        print(exception)
        print("MD2 中的process_exception")
        return HttpResponse('异常')

上面的process_exception在视图函数没有异常的时候是不会执行的,

views视图函数中的代码

from django.shortcuts import render,HttpResponse

# Create your views here.
def test(request):
    print('views的视图函数')
    raise ValueError("views异常")
    return HttpResponse('ok')

执行的输出结果

MD1里面的 process_request
MD2里面的process_request
========================================
MD1 中的 process_view
<function test at 0x000002CA53C00400> test
========================================
MD2 中的 process_view
<function test at 0x000002CA53C00400> test
views的视图函数
views异常
MD2 中的process_exception
MD2里面的process_response
MD1里面的process_response

有上面我们可以知道当我们抛出异常的时候执行该函数,同时只执行了MD2的process_exception因为MD2返回了一个响应对象。

process_exception(self, request, exception)
  执行顺序:
    按照注册顺序的倒序(在settings.py里面设置中 从下到上的顺序)
  何时执行:
    视图函数中抛出异常的时候才执行
  返回值:
    返回None,继续执行后续中间件的process_exception
    返回response,

process_template_response(用的比较少)

process_template_response(self, request, response)

参数:

  • 一个HttpRequest对象,
  • response是TemplateResponse对象(由视图函数或者中间件产生)

process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。

中间件的代码

from django.shortcuts import HttpResponse, redirect
from django.utils.deprecation import MiddlewareMixin
"""
自定义的中间件
"""

class MD1(MiddlewareMixin):
    def process_request(self,request):
        print('MD1里面的 process_request')
        # return HttpResponse('MD1')

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

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("=" * 40)
        print("MD1 中的 process_view")
        print(view_func, view_func.__name__)
        # return HttpResponse('process_view')

    def process_exception(self, request, exception):
        print(exception)
        print("MD1 中的process_exception")
        return HttpResponse('异常')

    def process_template_response(self, request, response):
        print("MD1 中的process_template_response")
        return response

class MD2(MiddlewareMixin):
    def process_request(self, request):
        print('MD2里面的process_request')

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

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("=" * 40)
        print("MD2 中的 process_view")
        print(view_func, view_func.__name__)

    def process_exception(self, request, exception):
        print(exception)
        print("MD2 中的process_exception")
        return HttpResponse('异常')

    def process_template_response(self, request, response):
        print("MD2 中的process_template_response")
        return response

视图函数的代码

from django.shortcuts import render,HttpResponse

# Create your views here.
def test(request):
    print('views的视图函数')

    def render():
        print("in index/render")
        return HttpResponse("render")

    rep = HttpResponse("OK")
    rep.render = render  # 给rep对象添加一个render方法
    return rep

执行输出的结果

MD1里面的 process_request
MD2里面的process_request
========================================
MD1 中的 process_view
<function test at 0x000002AE61680400> test
========================================
MD2 中的 process_view
<function test at 0x000002AE61680400> test
views的视图函数
MD2 中的process_template_response
MD1 中的process_template_response
in index/render
MD2里面的process_response
MD1里面的process_response

视图函数执行完之后,立即执行了中间件的process_template_response方法,顺序是倒序,先执行MD2的,在执行MD1的,接着执行了视图函数返回的HttpResponse对象的render方法,返回了一个新的HttpResponse对象,接着执行中间件的process_response方法。url页面显示的是render

process_template_response(self, request, response)
  执行顺序:
    按照注册顺序的倒序(在settings.py里面设置中 从下到上的顺序)
  何时执行:
    视图函数执行完,在执行视图函数返回的响应对象的render方法之前
  返回值:
    返回None,继续执行后续中间件的process_exception
    返回response,

process_request、process_response 和 process_view执行的流程

process_template_response和process_exception有触发条件,同时这2个也是倒序执行他们的执行流程

 使用中间件实现登陆白名单

urls.py的代码

from django.conf.urls import url
from django.contrib import admin
from midd import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^mdtest/', views.test),
    url(r'^login/', views.login, name='login'),

]

 视图函数的代码

from django.shortcuts import render,HttpResponse,redirect

# Create your views here.
def test(request):
    return HttpResponse('这是一个测试页')


def login(request):
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")

        if user == "admin" and pwd == "admin":
            # 设置session
            request.session["user"] = user
            # 获取跳到登陆页面之前的URL
            next_url = request.GET.get("next")
            # 如果有,就跳转回登陆之前的URL
            if next_url:
                return redirect(next_url)
            # 否则默认跳转到test页面
            else:
                return redirect("/test/")
    return render(request, "login.html")

login.html 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>登录页面</title>
</head>
<body>
<form action="{% url 'login' %}">
    <p>
        <label for="user">用户名:</label>
        <input type="text" name="user" id="user">
    </p>
    <p>
        <label for="pwd">密 码:</label>
        <input type="text" name="pwd" id="pwd">
    </p>
    <input type="submit" value="登录">
</form>
</body>
</html>

  

自己写的一个中间件my_middlewares.py

class AuthMD(MiddlewareMixin):
    white_list = ['/login/', ]  # 白名单
    balck_list = ['/black/', ]  # 黑名单

    def process_request(self, request):
        from django.shortcuts import redirect, HttpResponse

        next_url = request.path_info
        print(request.path_info, request.get_full_path())

        if next_url in self.white_list or request.session.get("user"):
            return
        elif next_url in self.balck_list:
            return HttpResponse('This is an illegal URL')
        else:
            return redirect("/login/?next={}".format(next_url)

setting.py中的MIDDLEWARE

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',
    'my_middlewares.AuthMD',
]

AuthMD中间件注册后,所有的请求都要走AuthMD的process_request方法。

访问的URL在白名单内或者session中有user用户名,则不做阻拦走正常流程;

如果URL在黑名单中,则返回This is an illegal URL的字符串;

正常的URL但是需要登录后访问,让浏览器跳转到登录页面。

注:AuthMD中间件中需要session,所以AuthMD注册的位置要在session中间的下方。

原文地址:https://www.cnblogs.com/yang-China/p/9347492.html