1-Django - 中间件

before

示例运行环境:

django3.2 + python3.9 + win10 + sqlite3 + pycharm
project name:demo
app name: app01

django请求的生命周期
本篇主要围绕下图中的中间件(middleware)部分展开学习。

如上图,所谓的中间件,就是在整个django请求和响应之间的特定节点实现的一组组的框架级别的、轻量级的全局钩子,并且在全局上改变django的输入与输出,因为是全局级别,使用时需要谨慎,以免影响性能。
从我们学习django的开始,从django项目运行起来之后,中间件就一直在默默的起作用,下面就是django的配置文件中的默认的中间件:

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又给我们提供了5个不同功能的钩子方法,这样,我们编写一个自定义类,然后重写指定的方法, 就写好了一个自定义的中间件,然后想要该自定义中间件生效的话,还要注册到配置文件中的MIDDLEWARE列表中,该列表中存放的是各个中间件类所在的路径,请求和响应会依次穿过这些中间件。

接下来,一起来看看这五大钩子方法吧。

五大方法

首先把项目各个部分的代码贴一贴。

urls.py

from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index)
]

views.py

from django.shortcuts import render, HttpResponse, redirect

def index(request):
    """ 主页 """
    print('视图函数 index 执行啦')  # 这一行打印主要测试后续的方法谁先执行谁后执行的
    return HttpResponse("index page")

setttings.py,目前MIDDLEWARE列表还是都是默认的中间件,后续我们自定义的中间件也会放到该列表中:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01'  # 别忘了注册app
]

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

五大方法小结

学习每个方法之前,先看看这里的总结,更加好理解。

PS:前两个方法用的最多!

1. process_request

  • 执行时间节点:在视图函数执行之前执行。
  • 多个中间件执行顺序:按照注册顺序,顺序执行。
  • 参数:
    • request,这个request和视图中的request是同一个。
  • 返回值:
    • 如果返回None,也就是不写返回值,就表示正常执行完该中间件,然后请求往后继续。
    • 如果返回一个render、redirect、HttpResponse或者其他对象,当前中间件后面的中间件的process_request和process_response方法、视图函数都不执行,但会执行当前中间件的process_response方法以及该中间前之前的中间件的process_response方法。
  • 重要程度:五星,用的较多。

2. process_response

  • 执行时间节点:在视图函数执行之后。
  • 多个中间件执行顺序:按照注册顺序,倒叙执行。
  • 参数:
    • request:和视图函数中的request是同一个对象。
    • response:视图函数返回的response对象。
  • 返回值:必须返回,否则报错:AttributeError: 'NoneType' object has no attribute 'get'
    • 返回视图函数返回的response,即根据需要对视图函数返回的response经过处理后,再返回。
    • 返回新的response,用新的response对象,替代视图函数返回的response。
  • 重要程度:4星,用的也较多。

3. process_view

  • 执行时间节点:路由匹配之后,视图函数执行之前。
  • 多个中间件执行顺序:按照注册顺序,顺序执行。
  • 参数:
    • request:和视图函数中的request是同一个对象。
    • view_func:视图函数。
    • view_args:视图函数接收的普通位置参数。
    • view_kwargs:视图函数接收的关键字参数。
  • 返回值:
    • 返回None,正常返回视图函数返回的response。
    • 或者返回新的response对象。
  • 重要程度:3星,用的不多。

4. process_exception

  • 执行时间节点:视图函数执行之后,process_resopnse执行之前。
    • 触发条件:必须在视图函数报错后,才执行。
  • 多个中间件执行顺序:按照注册顺序,倒叙执行。
  • 参数:
    • request:和视图函数中的request是同一个对象。
    • exception:错误信息对象。
  • 返回值:
    • 返回None,正常返回视图函数返回的response。
    • 或者返回新的response对象。
  • 重要程度:2星,用的不多。

5. process_template_response

  • 执行时间节点:视图函数执行后,process_response之前。
    • 触发条件:视图函数返回的response对象必须具有render方法。
  • 多个中间件执行顺序:按照注册顺序,倒叙执行。
  • 参数:
    • request:和视图函数中的request是同一个对象。
    • response:视图函数返回的response对象。
  • 返回值:必须返回response对象,否则报错xxxxmiddleware.process_template_response didn't return an HttpResponse object. It returned None instead.
  • 重要程度:1星,用的很少。

process_request

一般自定义的中间件都会放在各自的app下面的包内,我们这里为了简单,直接在app下创建中间件文件了。

views.py

from django.shortcuts import render, HttpResponse, redirect

def index(request):
    """ 主页 """
    print('视图函数index:', id(request))
    return HttpResponse("index page")

app01\CustomMiddleware.py

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

class CustomMiddleware1(MiddlewareMixin):
    """
    自定义的中间件类必须继承MiddlewareMixin
    """
    def process_request(self, request):
        """
        处理请求的方法
        处理完之后,如果返回None,则请求继续往后走
        :param request:这个request参数是wsgi封装好的request对象,在当前方法中
        :return:
        """
        print('CustomMiddleware1.precess_request:', id(request))
        # 如果在当前方法中返回请求对象,那么请求就直接被返回了,后续的中间件和视图都不走了
        # return HttpResponse("CustomMiddleware1.precess_request")

class CustomMiddleware2(MiddlewareMixin):
    """
    自定义的中间件类必须继承MiddlewareMixin
    """
    def process_request(self, request):
        """
        处理请求的方法,这个request参数是wsgi封装好的request对象,在当前方法中
        处理完之后,如果返回None,则请求继续往后走
        :param request:
        :return:
        """
        print('CustomMiddleware2.precess_request:', id(request))

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',
    'app01.CustomMiddleware.CustomMiddleware1',  # 自定义的中间件
    'app01.CustomMiddleware.CustomMiddleware2',  # 自定义的中间件
]

如果浏览器访问index路由的话,就会发现两个中间件被触发执行了,并且在视图之前执行:

CustomMiddleware1.precess_request: 1674878170832
CustomMiddleware2.precess_request: 1674878170832
视图函数index: 1674878170832
[08/Jan/2022 10:03:19] "GET /index/ HTTP/1.1" 200 10

由打印的id结果可以看到,中间件中的request和视图函数中的request对象是同一个;而且经过和中间件的流程是从前到后依次经过中间件。

process_responose

views.py

from django.shortcuts import render, HttpResponse, redirect

def index(request):
    """ 主页 """
    response = HttpResponse("index page")
    print('视图函数index, request', id(request), 'response', id(response))
    return response

app01\CustomMiddleware.py

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

class CustomMiddleware1(MiddlewareMixin):
    """
    自定义的中间件类必须继承MiddlewareMixin
    """
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        return None
		
    def process_response(self, request, response):
        """
        在请求经过视图函数处理后,由视图函数返回的response,经过该方法,可以进一步对响应对象进行处理
        但是处理之后,该方法必须返回response对象,否则报错:AttributeError: 'NoneType' object has no attribute 'get'
        :param request: 和视图函数中的request是同一个对象
        :param response: 视图函数返回的response对象
        :return: response
        """
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        # 必须返回response
        return response

class CustomMiddleware2(MiddlewareMixin):
    """
    自定义的中间件类必须继承MiddlewareMixin
    """
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(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',
    'app01.CustomMiddleware.CustomMiddleware1',  # 自定义用户认证中间件
    'app01.CustomMiddleware.CustomMiddleware2',  # 自定义用户认证中间件
]

浏览器访问index路由,打印结果:

CustomMiddleware1.precess_request: 1328954751824
CustomMiddleware2.precess_request: 1328954751824
视图函数index, request 1328954751824 response 1328954751200
CustomMiddleware2.process_response, request: 1328954751824 response: 1328954751200
CustomMiddleware1.process_response, request: 1328954751824 response: 1328954751200

两个precess_request顺序执行,在执行视图函数,然后两个process_response倒叙执行,如下图所示:

其他情况我们也来看看。

如果这两个中间件是这样的,且在MIDDLEWARE列表中的位置不变:

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

class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        # 必须返回response是视图处理的response
        return response

class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        return HttpResponse("CustomMiddleware1.precess_request")
        # return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

那请求的执行流程是这样的:

CustomMiddleware1.precess_request: 2481922657536
CustomMiddleware2.precess_request: 2481922657536
CustomMiddleware2.process_response, request: 2481922657536 response: 2481922655760
CustomMiddleware1.process_response, request: 2481922657536 response: 2481922655760

请求会直接从当前中间件类的precess_request方法,经过当前中间件类的process_response方法,通过当前中间件类的前一个中间件类的process_response方法返回给客户端,不再经过视图函数处理了。

如果这两个中间件是这样的,且在MIDDLEWARE列表中的位置不变:

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


class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        return HttpResponse("CustomMiddleware1.precess_request")
        # return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        return response

class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

请求的执行流程是这样的:

CustomMiddleware1.precess_request: 1901241286464
CustomMiddleware1.process_response, request: 1901241286464 response: 1901241282864

请求会直接经过当前中间件类的process_response方法直接返回,当前中间件后面的中间件的process_request和process_response方法、视图函数都不执行,但会执行当前中间件的process_response方法以及该中间前之前的中间件的process_response方法。

其他情况就不再一一列举了,你可以自己尝试运行查找运行规律。

process_view

settings.pyurls.py代码不变。

views.py

from django.shortcuts import render, HttpResponse, redirect

def index(request):
    """ 主页 """
    response = HttpResponse("index page")
    print('视图函数index, request', id(request), 'response', id(response))
    return response

app01\CustomMiddleware.py

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


class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        """
        在经过路由匹配之后,视图函数执行之前执行该方法,主要对视图进行相关处理
        :param request:和视图函数中的request是同一个对象
        :param view_func:视图函数
        :param view_args:视图函数接收的普通位置参数
        :param view_kwargs:视图函数接收的关键字参数
        :return:
            返回None,正常返回视图函数返回的response
            或者新的response对象
        """
        print('CustomMiddleware1.process_view, request: {}, view_func: {}, view_args: {}, view_kwargs: {}'.format(
            id(request), id(view_func), view_args, view_kwargs
        ))
        return None

class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print(
            'CustomMiddleware2.process_view, request: {}, view_func: {}, view_args: {}, view_kwargs: {}'.format(
                id(request), id(view_func), view_args, view_kwargs
            ))
        return None

浏览器访问index路由,打印结果:

CustomMiddleware1.precess_request: 2131577016960
CustomMiddleware2.precess_request: 2131577016960
CustomMiddleware1.process_view, request: 2131577016960, view_func: 2131575945824, view_args: (), view_kwargs: {}
CustomMiddleware2.process_view, request: 2131577016960, view_func: 2131575945824, view_args: (), view_kwargs: {}
视图函数index, request 2131577016960 response 2131576350464
CustomMiddleware2.process_response, request: 2131577016960 response: 2131576350464
CustomMiddleware1.process_response, request: 2131577016960 response: 2131576350464

执行流程如下图:

至于view_args和view_kwargs这两个参数:

# 如果urls.py路由是这样的:
	re_path('index/(\d+)/', views.index),
# views.py:
    def index(request, num):
        return HttpResponse("index page")
# 前端访问:
	http://127.0.0.1:8000/index/123/
# process_view打印的结果:
	view_args: ('123',), view_kwargs: {}
	
# ---------------------------------------------
# 如果urls.py路由是这样的:
	re_path('index/(?P<num>\d+)/', views.index),
# views.py:
    def index(request, num):
        return HttpResponse("index page")
# 前端访问:
	http://127.0.0.1:8000/index/123/
# process_view打印的结果:
	view_args: (), view_kwargs: {'num': '123'}

如果这两个中间件是这样的,且在MIDDLEWARE列表中的位置不变:

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


class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware1.process_view')
        # return None
        return HttpResponse("CustomMiddleware1.process_view")


class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware2.process_view')
        return None

请求的执行流程是这样的:

CustomMiddleware1.precess_request: 2792290605712
CustomMiddleware2.precess_request: 2792290605712
CustomMiddleware1.process_view
CustomMiddleware2.process_response, request: 2792290605712 response: 2792290776784
CustomMiddleware1.process_response, request: 2792290605712 response: 2792290776784

执行流程如下图:

如果这两个中间件是这样的,且在MIDDLEWARE列表中的位置不变:

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


class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware1.process_view')
        return None
        # return HttpResponse("CustomMiddleware1.process_view")


class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware2.process_view')
        # return None
        return HttpResponse("CustomMiddleware2.process_view")

请求的执行流程是这样的:

CustomMiddleware1.precess_request: 1886439673664
CustomMiddleware2.precess_request: 1886439673664
CustomMiddleware1.process_view
CustomMiddleware2.process_view
CustomMiddleware2.process_response, request: 1886439673664 response: 1886439671888
CustomMiddleware1.process_response, request: 1886439673664 response: 1886439671888

执行流程如下图:

process_exception

这个方法,需要注意的是:

  • 如果视图函数中没有错误,则所有的process_exception都不执行;如果某个视图函数中出现了错误,则所有的process_exception方法倒叙执行。
  • 只有当视图函数中有异常时process_exception才执行,其他中间件方法中如果出现问题,process_exception方法不管,即不执行。

settings.py不变,urls.py也不变。

views.py,故意搞出来一个错误:

from django.shortcuts import render, HttpResponse, redirect

def index(request):
    """ 主页 """
    response = HttpResponse("index page")
    print('视图函数index, request', id(request), 'response', id(response))
    # 手动搞出来一个错误
    int("xxoo")
    return response

app01\CustomMiddleware.py

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


class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware1.process_view')
        return None
        # return HttpResponse("CustomMiddleware1.process_view")

    def process_exception(self, request, exception):
        """
        只有请求期间出现错误才执行该方法,并且该方法捕获错误信息,可以给前端返回
        :param request: 根视图函数的request对象是同一个
        :param exception: 错误信息对象
        :return:
            None,正常给前端抛出异常
            或者返回一个新的response对象
        """
        print('CustomMiddleware1.process_exception, exception: ', exception)
        return None


class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        # 在这里模拟错误,人家process_exception方法压根不管
        # int('xxoo')
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware2.process_view')
        return None
        # return HttpResponse("CustomMiddleware2.process_view")

    def process_exception(self, request, exception):
        print('CustomMiddleware2.process_exception, exception: ', exception)
        return None

浏览器访问index路由,打印结果:

CustomMiddleware1.precess_request: 3188306227152
CustomMiddleware2.precess_request: 3188306227152
CustomMiddleware1.process_view
CustomMiddleware2.process_view
视图函数index, request 3188306227152 response 3188305688896
CustomMiddleware2.process_exception, exception:  invalid literal for int() with base 10: 'xxoo'
CustomMiddleware1.process_exception, exception:  invalid literal for int() with base 10: 'xxoo'
Internal Server Error: /index/
Traceback (most recent call last):
	............
  File "D:\tmp\demo\app01\views.py", line 25, in index
    int("xxoo")
ValueError: invalid literal for int() with base 10: 'xxoo'
[08/Jan/2022 15:31:49] "GET /index/ HTTP/1.1" 500 69184
CustomMiddleware2.process_response, request: 3188306227152 response: 3188306127312
CustomMiddleware1.process_response, request: 3188306227152 response: 3188306127312

执行流程如下图:

如果在第一个中间件类中的process_exception方法中返回了新的response对象,那么:

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


class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware1.process_view')
        return None
        # return HttpResponse("CustomMiddleware1.process_view")

    def process_exception(self, request, exception):
        print('CustomMiddleware1.process_exception, exception: ', exception)
        # return None
        return HttpResponse(str(exception))


class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        # 在这里模拟错误,人家process_exception方法压根不管
        # int('xxoo')
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware2.process_view')
        return None
        # return HttpResponse("CustomMiddleware2.process_view")

    def process_exception(self, request, exception):
        print('CustomMiddleware2.process_exception, exception: ', exception)
        return None

请求的执行流程是这样的,并且前端页面不会报错,而是展示的是process_exception方法返回的response内容。

CustomMiddleware1.precess_request: 2706436998288
CustomMiddleware2.precess_request: 2706436998288
CustomMiddleware1.process_view
CustomMiddleware2.process_view
视图函数index, request 2706436998288 response 2706435990480
CustomMiddleware2.process_exception, exception:  invalid literal for int() with base 10: 'xxoo'
CustomMiddleware1.process_exception, exception:  invalid literal for int() with base 10: 'xxoo'
CustomMiddleware2.process_response, request: 2706436998288 response: 2706437775904
CustomMiddleware1.process_response, request: 2706436998288 response: 2706437775904

执行流程也不变。

如果在第二个中间件类中的process_exception方法中返回了新的response对象,第一个中间件类的process_exception方法中返回None,那么,执行流程就有点变化了:

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


class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware1.process_view')
        return None
        # return HttpResponse("CustomMiddleware1.process_view")

    def process_exception(self, request, exception):
        print('CustomMiddleware1.process_exception, exception: ', exception)
        return None
        # return HttpResponse(str(exception))


class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        # 在这里模拟错误,人家process_exception方法压根不管
        # int('xxoo')
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware2.process_view')
        return None
        # return HttpResponse("CustomMiddleware2.process_view")

    def process_exception(self, request, exception):
        print('CustomMiddleware2.process_exception, exception: ', exception)
        # return None
        return HttpResponse(str(exception))

请求的执行流程是这样的,前端页面不会报错。

CustomMiddleware1.precess_request: 1945433082224
CustomMiddleware2.precess_request: 1945433082224
CustomMiddleware1.process_view
CustomMiddleware2.process_view
视图函数index, request 1945433082224 response 1945433083376
CustomMiddleware2.process_exception, exception:  invalid literal for int() with base 10: 'xxoo'
CustomMiddleware2.process_response, request: 1945433082224 response: 1945433081936
CustomMiddleware1.process_response, request: 1945433082224 response: 1945433081936

当第二个中间件类的process_exception方法返回新的response对象时,请求会直接跳过它前面的process_exception方法,进而穿过来后面的process_response方法返回给前端。

process_template_response

这个方法用的很少,我就没有深入研究,就知道返回时,视图函数的返回的response对象必须具有render方法,这个render和返回页面的render函数不一样!

能干啥,我也没太看出来,这里就就简单看下怎么配置,以及什么时候执行就完了。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<h1>index page</h1>
</body>
</html>

views.py

from django.shortcuts import render, HttpResponse, redirect
from django.template import loader

def index(request):
    """ 主页 """
    response = HttpResponse("index page")
    print('视图函数index, request', id(request), 'response', id(response))
    def render():
        """ 魔改的render函数,返回一个html页面"""
        content = loader.render_to_string(template_name='index.html', context=None, request=request, using=None)
        return HttpResponse(content=content, content_type=None, status=None)
    response.render = render
    return response

app01\CustomMiddleware.py

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


class CustomMiddleware1(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware1.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware1.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware1.process_view')
        return None
        # return HttpResponse("CustomMiddleware1.process_view")

    def process_exception(self, request, exception):
        print('CustomMiddleware1.process_exception, exception: ', exception)
        return None
        # return HttpResponse(str(exception))

    def process_template_response(self, request, response):
        print("CustomMiddleware1.process_template_response,request: ", id(request), "response: ", id(response))
        return response


class CustomMiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('CustomMiddleware2.precess_request:', id(request))
        # return HttpResponse("CustomMiddleware1.precess_request")
        return None

    def process_response(self, request, response):
        print('CustomMiddleware2.process_response, request:', id(request), 'response:', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('CustomMiddleware2.process_view')
        return None
        # return HttpResponse("CustomMiddleware2.process_view")

    def process_exception(self, request, exception):
        print('CustomMiddleware2.process_exception, exception: ', exception)
        return None
        # return HttpResponse(str(exception))

    def process_template_response(self, request, response):
        print("CustomMiddleware2.process_template_response,request: ", id(request), "response: ", id(response))
        return response

浏览器访问index路由,打印结果:

CustomMiddleware1.precess_request: 1763286163856
CustomMiddleware2.precess_request: 1763286163856
CustomMiddleware1.process_view
CustomMiddleware2.process_view
视图函数index, request 1763286163856 response 1763285305856
CustomMiddleware2.process_template_response,request:  1763286163856 response:  1763285305856
CustomMiddleware1.process_template_response,request:  1763286163856 response:  1763285305856
CustomMiddleware2.process_response, request: 1763286163856 response: 1763286709584
CustomMiddleware1.process_response, request: 1763286163856 response: 1763286709584

自定义应用

中间件实现用户认证

我们可以在中间件中干很多事情,比如做用户认证。下面的示例演示了只有登录成功或者处于白名单中的url才能被中间件放行,其余的请求都要在中间件中进行session校验。

上代码,urls.py:

from django.contrib import admin
from django.urls import path, re_path
from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('pay/', views.pay),
    path('login/', views.login),
    path('logout/', views.logout),
]

views.py

from django.shortcuts import render, HttpResponse, redirect
from app01 import models

def login(request):
    """ 登录认证 """
    if request.method == "GET":
        return render(request, 'login.html')
    else:
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        # print(user, pwd)
        obj = models.User.objects.filter(user=user, pwd=pwd).first()
        if obj:
            request.session['is_login'] = True
            request.session['user'] = obj.user
            return redirect('/index/')
        else:
            return render(request, 'login.html', {"msg": "user or password error!!!"})

def index(request):
    """ 主页 """
    return HttpResponse(f"欢迎用户[{request.session['user']}]访问index页面!!!")

def pay(request):
    """ pay页 """
    return HttpResponse(f"欢迎用户[{request.session['user']}]访问pay页面!!!")


def logout(request):
    """ 清除session,做中间件功能是否完备用的 """
    request.session.clear()
    return redirect('/login/')

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    <input type="text" placeholder="用户名" name="user">
    <input type="password" placeholder="密码" name="pwd">
    <input type="submit" value="提交"><span style="color: red;">{{ msg }}</span>
</form>
</body>
</html>

models.py:

from django.db import models

class User(models.Model):
    user = models.CharField(max_length=32, verbose_name='用户名')
    pwd = models.CharField(max_length=64, verbose_name='密码')

    def __str__(self):
        return self.user

自己执行数据迁移,并且手动录入一些用户信息。
中间件app01\CustomMiddleware.py:

from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin


class CustomAuthMiddleware(MiddlewareMixin):
    # 白名单
    white_url_list = ['/login/', '/register/']

    def process_request(self, request):
        """ 处理请求的中间件方法 """
        current_path = request.path
        if current_path in self.white_url_list:
            return None
        # 白名单里面的路径都放行,其他的都要进行session校验
        status = request.session.get('is_login', None)
        if not status:
            return redirect('/login/')

然后在settings.py中注册中间件,其他配置都是默认的。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',  # 别忘了注册app
]

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',
    'app01.CustomMiddleware.CustomAuthMiddleware',  # 自定义用户认证中间件
]

然后浏览器可以访问login页面登录后,再访问index或者pay路由都可以了,也可以访问logout路由,退出登录状态,删除session,然后你再访问index或者pay路由时,中间件认证不通过,就会自动跳转到登录页面。

中间件实现自定义限流

示例还是上面的示例,只不过新加一个中间件而已。
中间件app01\CustomMiddleware.py:

import time
from django.shortcuts import redirect, HttpResponse
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings

class CustomAuthMiddleware(MiddlewareMixin):
    # 白名单
    white_url_list = ['/login/', '/register/']

    def process_request(self, request):
        """ 处理请求的中间件方法 """
        current_path = request.path
        if current_path in self.white_url_list:
            return None
        # 白名单里面的路径都放行,其他的都要进行session校验
        status = request.session.get('is_login', None)
        if not status:
            return redirect('/login/')

class Throttle(MiddlewareMixin):
    """ 限制访问ip访问次数 """

    def process_request(self, request):
        """
        限制每个ip在单位时间内的访问次数
        :param request:
        :return:
        """
        # 1. 获取请求的ip
        ip = request.META.get("REMOTE_ADDR")
        now = time.time()
        # 如果当前ip不在限流字典中,就添加进去
        if not settings.THROTTLE_VISIT_DICT.get(ip):
            settings.THROTTLE_VISIT_DICT[ip] = []

        # 获取当前ip的限流时间戳列表
        # {'127.0.0.1': [1641561087.420252, 1641561086.9668493, 1641561086.5121875, 1641561085.9309537]}
        # history: [1641561087.420252, 1641561086.9668493, 1641561086.5121875, 1641561085.9309537]
        history = settings.THROTTLE_VISIT_DICT[ip]

        # 将列表中无效的时间戳删除
        # 两个条件:
        #   1. history列表必须有值
        #   2. 从列表右边往左边挨个判断,如果当前时间戳减去列表中的时间戳大于设定的限流秒数
        #      就没啥用了,可以删除了
        while history and now - history[-1] > settings.THROTTLE_SECONDS:
            history.pop() # pop默认删除列表最后面的元素

        # 在单位限流时间内,限制访问次数
        if len(history) >= settings.THROTTLE_NUMS:
            s = history[-1] + settings.THROTTLE_SECONDS - now
            return HttpResponse('<h1 style="text-align:center;color:red;">您的访问过于频繁,请于[{:.2f}]秒后访问</h1>'.format(s))

        # 每次请求都将时间戳插入到列表的头部
        history.insert(0, now)
        print(settings.THROTTLE_VISIT_DICT)

然后在settings.py中注册中间件,其他配置都是默认的。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01'
]

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',
    'app01.CustomMiddleware.CustomMiddleware',  # 自定义用户认证中间件
    'app01.CustomMiddleware.Throttle',  # 自定义限流中间件
]

# ------------- 限流相关 -------------
# 限流时间,单位: 秒
THROTTLE_SECONDS = 10
# 限流次数,单位时间内允许访问的次数
THROTTLE_NUMS = 3
# 用户ip访问的字典
THROTTLE_VISIT_DICT = {
    # '127.0.0.1': [1641561025.2066863, 1641561024.730342, 1641561021.7718503]
}

欢迎斧正,that's all,see also:

Django基础九之中间件 | Django的中间件

posted @ 2019-06-01 20:50  听雨危楼  阅读(288)  评论(1编辑  收藏  举报