Django(18):中间件原理和使用

2023-09-19 17:51:17

概述

中间件(middleware)是一个镶嵌到Django的request(请求)/response(响应)处理机制中的一个钩子(hooks) 框架。它是一个可以修改Django全局输入或输出的一个底层插件系统。

HTTP Web服务器工作原理一般都是接收用户发来的请求(request), 然后给出响应(response)。Django也不例外,其一般工作方式是接收request请求和其它参数,交由视图(view)处理,然后给出它的响应(response): 渲染过的html文件或json格式的数据。然而在实际工作中Django并不是接收到request对象后,马上交给视图函数或类(view)处理,也不是在view执行后立马把response返回给用户。**一个请求在达到视图View处理前需要先经过一层一层的中间件处理,经过View处理后的响应也要经过一层一层的中间件处理才能返回给用户 **。

中间件(Middleware)在整个Django的request/response处理机制中的角色如下所示:

HttpRequest -> Middleware -> View -> Middleware -> HttpResponse

中间件常用于权限校验、限制用户请求、打印日志、改变输出内容等多种应用场景,比如:

  • 禁止特定IP地址的用户或未登录的用户访问我们的View视图函数
  • 对同一IP地址单位时间内发送的请求数量做出限制
  • 在View视图函数执行前传递额外的变量或参数
  • 在View视图函数执行前或执行后把特定信息打印到log日志
  • 在View视图函数执行后对response数据进行修改后返回给用户

注意:装饰器也经常用于用户权限校验。但与装饰器不同,中间件对Django的输入或输出的改变是全局的。比如@login_required装饰器仅作用于单个视图函数。如果你希望实现全站只有登录用户才能访问,编写一个中间件是一个更好的解决方案。

Django自带中间件

当你创建一个新Django项目时,你会发现settings.py里的MIDDLEWARE列表已经注册了一些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',
]

每个中间件的功能如下:

  • SecurityMiddleware:为request/response提供了几种安全改进;
  • SessionMiddleware:开启session会话支持;
  • CommonMiddleware:基于APPEND_SLASH和PREPEND_WWW的设置来重写URL,如果APPEND_SLASH设为True,并且初始URL 没有以斜线结尾以及在URLconf 中没找到对应定义,这时形成一个斜线结尾的新URL;
  • CsrfViewMiddleware:添加跨站点请求伪造的保护,通过向POST表单添加一个隐藏的表单字段,并检查请求中是否有正确的值;
  • AuthenticationMiddleware:在视图函数执行前向每个接收到的user对象添加HttpRequest属性,表示当前登录的用户,无它用不了request.user
  • MessageMiddleware:开启基于Cookie和会话的消息支持
  • XFrameOptionsMiddleware:对点击劫持的保护

除此以外, Django还提供了压缩网站内容的GZipMiddleware,根据用户请求语言返回不同内容的LocaleMiddleware和给GET请求附加条件的ConditionalGetMiddleware。这些中间件都是可选的。

Django的中间件执行顺序

当你在settings.py注册中间件时一定要要考虑中间件的执行顺序,中间件在request到达view之前是从上向下执行的,在view执行完后返回response过程中是从下向上执行的,如下图所示。举个例子,如果你自定义的中间件有依赖于request.user,那么你自定义的中间件一定要放在AuthenticationMiddleware的后面。

在这里插入图片描述

自定义中间件

自定义中间件你首先要在app所属目录下新建一个文件middleware.py, 添加好编写的中间件代码,然后在项目settings.py中把它添加到MIDDLEWARE列表进行注册,添加时一定要注意顺序。

Django提供了两种编写自定义中间件的方式:函数和类,基本框架如下所示:

函数

def simple_middleware(get_response):
    # 一次性设置和初始化
    def middleware(request):
        # 请求在到达视图前执行的代码
        response = get_response(request)
        # 响应在返回给客户端前执行的代码
        return response
    return middleware

当请求从浏览器发送到服务器视图时,将执行response = get_response(request)该行之前的所有代码。当响应从服务器返回到浏览器时,将执行response = get_response(request)此行之后的所有内容。

那么这条分界线respone = get_response(request)做什么的?简而言之,它将调用列表中的下一个中间件。如果这是最后一个中间件,则将调用该视图。

示例

我们现在以函数编写一个名为timeit_middleware的中间件,打印出执行每个请求所花费的时间,代码如下所示:

import time

def timeit_middleware(get_response):
    
    def middleware(request):
        start = time.time()
        response = get_response(request)
        end = time.time()
        print("请求花费时间: {}秒".format(end - start))
        return response

    return 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',
    'blog.middleware.timeit_middleware', # 新增
]

执行效果

每当Django处理一个请求时,终端(terminal)就会打印出请求花费时间。

使用类

class SimpleMiddleware:
    def __init__(self, get_response):
        # 一次性设置和初始化
        self.get_response = get_response
        
    def __call__(self, request):
        # 视图函数执行前的代码
        response = self.get_response(request)
        # 视图函数执行后的代码
        return response

示例

我们现在以类来编写一个名为LoginRequiredMiddleware的中间件,实现全站要求登录,但是登录页面和开放白名单上的urls除外。代码如下所示:

from django.shortcuts import redirect
from django.conf import settings

class LoginRequiredMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.login_url = settings.LOGIN_URL
        # 开放白名单,比如['/login/', '/admin/']
        self.open_urls = [self.login_url] + getattr(settings, 'OPEN_URLS', [])

    def __call__(self, request):        
        if not request.user.is_authenticated and request.path_info not in self.open_urls:
            return redirect(self.login_url + '?next=' + request.get_full_path())
        
        response = self.get_response(request) 
        return response

小知识: request.path_info用于获取当前请求的相对路径,如/articles/,而request.get_full_path()用于获取当前请求完整的相对路径,包括请求参数,如/articles/?page=2。使用request.get_full_path()时别忘了加括号哦,否则返回的是uwsgi请求对象,不是字符串。

注册中间件

修改settings.py, 注册中间件,并添加 LOGIN_URLOPEN_URLS

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',
    'blog.middleware.timeit_middleware',
    'blog.middleware.LoginRequiredMiddleware',
]

LOGIN_URL = "/admin/login/"
OPEN_URLS = ["/admin/"]

查看效果

添加完中间件后,你访问任何非LOGIN_URL和OPEN_URLS里的urls,都需要你先进行登录。

其它中间件钩子函数

Django还提供了其它三个中间件钩子函数,分别在执行视图函数,处理异常和进行模板渲染时调用。

process_view

process_view(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并返回结果。

process_exception

process_exception(self, request, exception)

该方法两个参数:

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

这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。该方法常用于发生异常时通知管理员或将其日志的形式记录下来。

process_template_response

process_template_response(self, request, response)

该方法两个参数:

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

该方法是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象)。该方法常用于向模板注入变量或则直接改变模板。

如何使用这3个钩子函数?

在函数或类的中间件中应该如何使用上面3个钩子函数呢? 具体实现方式如下:

函数

from django.http import HttpResponse

def timeit_middleware(get_response):
    
    def middleware(request):
        response = get_response(request)
        return response
    
    def process_view(request, view_func, view_args, view_kwargs)
        return None or HttpResponse(xx)
 
    def process_exception(self, request, exception):
        return None or HttpResponse(xx)
    
    def process_template_response(self, request, response)
        return ...
    
    middleware.process_view = process_view
    middleware.process_exception = process_exception
    middleware.process_template_response = process_template_response
 
    return middleware

class MyClassMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
 
    def __call__(self, request):
        return self.get_response(request)
    
    def process_view(request, view_func, view_args, view_kwargs)
        return None or HttpResponse(xx)
 
    def process_exception(self, request, exception):
        return None or HttpResponse(xx)
        # 例子: 打印出异常
        return HttpResponse(<h1>str(exception)</h1)
    
    # 该方法仅对TemplateResponse输入有用,对render方法失效
    def process_template_response(self, request, response)
        response.context_data['title'] = 'New title'
        return response

全局异常处理

利用process_exception实现全局异常处理。

首先创建一个中间件

import traceback
from django.http import JsonResponse
import logging

logger = logging.getLogger(__name__)


class ExceptionMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        return response

    def process_exception(self, request, exception):

        traceback_info = traceback.format_exc()
        logger.info(f"request_path: {request.path}, traceback_info: {traceback_info}")
        return JsonResponse({"code": -1, "msg": "error"}, status=500)

使用 traceback.format_exc() 函数获取到 exception 的报错信息,然后通过 logger 日志打印输出。

这里主要输出两个信息,一个是接口请求的路径,request.path,一个是报错信息 traceback_info,当然,这里我们还可以记录更多的信息,比如请求的用户信息,请求的参数等。

最后返回报错:eturn 了 response,还有一个 http 的状态码 status=500,这些信息都是可以自己拟定的。

定义好之后就是调用中间件,settings.py 里去引用这个中间件:

MIDDLEWARE = [
    ...
    'hunter.middlewares.exception_middleware.ExceptionMiddleware',
]

小结

本文介绍了Django中间件(Middleware)的工作原理,执行顺序及如何自定义中间件。了解中间件一定要先对Django的request/response处理过程非常了解。当你希望在视图函数执行请求前或执行请求后添加额外的功能,且这种功能是全局性的(针对所有的request或view或response), 那么使用中间件是最好的实现方式。

更多推荐

自然语言处理之北大词性标注集

最近在做人工智能相关技术的学习,在自然语言处理这里发现了词标注标准,顺带分享给小伙伴们词性是词汇基本的语法属性,通常也称为词类。词性标准是在给定句子中判定每个词的语法范畴,确定其词性并加以标注的过程。例如名词、动词、形容词等。中文领域尚无统一的标注标准,较为主流的为北大的词性标注集和宾州词性标注集两大类。下面分享北大的

Python爬虫(二十一)_Selenium与PhantomJS

本章将介绍使用Selenium和PhantomJS两种工具用来加载动态数据,更多内容请参考:Python学习指南SeleniumSelenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,最初是为网站自动化测试而开发的,类型像我们玩游戏用的按键精灵,可以按指定的命令自动化操作,不同是Selenium可

MongoDB【部署 02】mongodb使用配置文件启动、添加为系统服务及自启动(一个报错:[13436][NotMasterOrSecondary])

MongoDB使用配置文件启动、添加为系统服务及设置自启动1.是什么2.下载安装启动配置2.1下载2.2安装2.3配置2.4使用配置文件启动3.设置系统服务及自启动3.1设置为系统服务3.2自启动1.是什么【以下内容来自ChatGPT3.5】MongoDB是一个流行的开源文档型数据库管理系统,广泛用于构建高度可扩展、灵

JVM 程序计数器

Java虚拟机(JVM,JavaVirtualMachine)是一个能够执行Java字节码的虚拟机。在JVM的架构中,程序计数器(ProgramCounter,PC)是一个关键的组成部分。程序计数器用于存储当前正在执行的Java字节码指令的地址。每个线程在JVM中都有自己的程序计数器。每条线程都需要有一个独立的程序计数

计算机视觉与深度学习-图像分割-视觉识别任务02-目标检测-【北邮鲁鹏】

目录标题参考目标检测定义深度学习对目标检测的作用单目标检测多任务框架多任务损失预训练模型姿态估计多目标检测问题滑动窗口(SlidingWindow)滑动窗口缺点AdaBoost(AdaptiveBoosting)参考区域建议selectivesearch思想慢速R-CNN慢速R-CNN思路边界框回归(Bboxreg)慢

leetcode分类刷题:队列(Queue)(二、优先队列解决TopK简单问题)

1、优先队列好像一般都叫堆,以大顶堆为例,顶部第一个元素最大,底部最后一个元素最小,自顶向底是递减的(更准确的说是非递增的),对外只能访问顶部第一个元素(对应索引为0)和底部最后一个元素(对应索引为-1);在Python中,heapq默认维护小顶堆,构造大顶堆时需要在入堆时添加相反数2、本次博客总结下用优先队列解决To

JavaScript系列从入门到精通系列第三篇:JavaScript基本语法(一)

文章目录一:JavaScript基本语法1:JS注释(一):JS多行注释(二):JS单行注释(三):JS中大小写(四):分号问题(五):空格和换行2:字面量和变量(一):字面量(二):变量(三):如何声明变量(四):如何给变量赋值(五):标识符二:JS中6种数据类型(一)1:JS字符串(一):JS字符串基本使用(二):

Ubuntu 安装 CUDA 与 OPENCL

前言:最近需要做一些GPU并行计算,因而入坑CUDA和OPENCL,两者都有用到一些,刚好有点时间,同时记录一些学习过程,排掉一些坑,这篇是环境安装篇,基本跟着走就没什么问题,环境:ubuntu18.04/ubuntu20.04显卡:Nvidia一、CUDA安装1.查看电脑是否识别GPUlspci|grep-invid

Liunx(Ubuntu20)常用指令

-rwxr-xr-x,在Linux系统中权限是区分用户的,即用户、组用户、其他用户,第一位表示文件的类型,-代表文件,d代表目录,其他每个用户占三个字符用户、组用户、其他用户都是rwx形式,其中r表示读、w表示写、x表示可执行,-表示没有权限,拿用户组举例,r只能出现在第一个位置、w只能出现在第二个位置、x只能出现在第

在 CentOS 上安装 Docker Engine

文章目录在CentOS上安装DockerEngine先决条件操作系统要求卸载旧版本安装方法使用rpm存储库安装设置存储库安装DockerEngine安装最新版本安装指定版本以非root用户身份管理Docker配置Docker以使用systemd启动参考官方文档:https://docs.docker.com/engin

阿里云服务器价格表,轻量和服务器最新活动价格表汇总

租用阿里云服务器怎么收费?阿里云服务器配置不同一年价格也不同,阿里云2核2G3M带宽108元一年、2核4G4M带宽297.98元12个月,云服务器u1公网带宽可选1M到5M,系统盘为ESSD云盘40GB起,CPU内存配置可选2核2G、2核4G、4核8G、8核16G等配置,还有ECS计算型c7、通用型g7和内存型r7多C

热文推荐