Django 中间件介绍及修改请求参数


Django 中间件介绍及修改请求参数

本文将主要介绍:Django 中间件介绍、 Django 中间件使用、Django 中间件中增加请求参数


1. 业务场景

在 API 接口中,在请求头中增加了 token 验证,其中,token 是 JWT 类型,也就是说,不仅需要验证 token 的有效性和合法性,同时也要解析 token 中所携带的数据信息,最后还需将解析出来的数据,加入到 HttpRequest 中,供接口使用


2. Django 中间件介绍

Django 中间件是修改 Django request 或者 response 对象的钩子,可以理解为是介于 HttpRequest 与 HttpResponse 处理之间的一道处理过程。浏览器从请求到响应的过程中,Django 需要通过很多中间件来处理,可以看如下图所示:

img


2.1 Django 中间件作用

  • 修改请求,即传送到 view 中的 HttpRequest 对象
  • 修改响应,即 view 返回的 HttpResponse 对象

2.2 中间件组件配置

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

2.3 自定义中间件

2.3.1 方法一:继承 MiddlewareMixin(官方不建议)

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

process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_exception(self, request, exception)
process_response(self, request, response)

自定义中间的步骤:

在 app 目录下新建一个 py 文件,名字自定义,并在该 py 文件中导入 MiddlewareMixin:

from django.utils.deprecation import MiddlewareMixin

img

自定义的中间件类,要继承父类 MiddlewareMixin:

class MD1(MiddlewareMixin):
pass

在 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',
'app01.middlewares.MD1',
]

中间件类的方法:

  • process_request
  • process_response
  • process_view
  • process_exception
  • process_template_response()

process_request(self, request)

  • 参数:

    • request,这个 request 和视图函数中的 request 是一样的
  • 返回值:

    • None 的话,按正常流程继续走,交给下一个中间件处理
    • 返回值是 HttpResponse 对象,Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse
class MD1(MiddlewareMixin):
def process_request(self, request):
print("md1 process_request 方法。", id(request))

注:当配置多个中间件时,会按照 MIDDLEWARE中 的注册顺序,也就是列表的索引值,顺序执行


process_response(self,request, response)

  • 参数:
    • request 是请求对象
    • response 是视图函数返回的 HttpResponse 对象,该方法必须要有返回值,且必须是 response
  • 执行顺序:
    • process_response 方法是在视图函数之后执行的
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse
class MD1(MiddlewareMixin):
def process_request(self, request):
print("md1 process_request 方法。", id(request)) #在视图之前执行
def process_response(self,request, response):
print("md1 process_response 方法!", id(request))
return response

注:当配置多个中间件时,会按照 MIDDLEWARE 中的注册顺序,也就是列表的索引值,倒序执行

从下图看,正常的情况下按照绿色的路线进行执行,假设 中间件1 有返回值,则按照红色的路线走,直接执行该类下的 process_response 方法返回,后面的其他中间件就不会执行

img


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

  • 参数:
    • request 是 HttpRequest 对象
    • view_func 是 Django 即将使用的视图函数
    • view_args 是将传递给视图的位置参数的列表
    • view_kwargs 是将传递给视图的关键字参数的字典
  • 返回值:
    • 是 None 的话,按正常流程继续走,交给下一个中间件处理
    • 返回值是 HttpResponse 对象,Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法
    • 是 view_func(request),Django 将不执行后续视图函数之前执行的方法,提前执行视图函数,然后再倒序执行视图函数之后执行的方法
  • 执行顺序:
    • process_view 方法是在视图函数之前,process_request 方法之后执行的
class MD1(MiddlewareMixin):
def process_request(self, request):
print("md1 process_request 方法。", id(request)) # 在视图之前执行
def process_response(self,request, response): # 基于请求响应
print("md1 process_response 方法!", id(request)) # 在视图之后
return response
def process_view(self,request, view_func, view_args, view_kwargs):
print("md1 process_view 方法!") # 在视图之前执行 顺序执行
#return view_func(request)

注:

  • view_args 和 view_kwargs 都不包含第一个视图参数(request)

从下图看,当最后一个中间件的 process_request 到达路由关系映射之后,返回到第一个中间件 process_view,然后依次往下,到达视图函数

img


process_exception(self, request, exception)

  • 参数:
    • request 是 HttpRequest 对象
    • exception 是视图函数异常产生的 Exception 对象
  • 返回值:
    • 返回值是 None,页面会报 500 状态码错误,视图函数不会执行
    • 是 HttpResponse 对象,页面不会报错,返回状态码为 200
  • 执行顺序:
    • 在视图函数之后,在 process_response 方法之前执行
    • process_exception 方法倒序执行,然后再倒序执行 process_response 方法
    • process_exception 方法只有在视图函数中出现异常了才执行,按照 settings 的注册倒序执行
    • 视图函数不执行,该中间件后续的 process_exception 方法也不执行,直接从最后一个中间件的 process_response 方法倒序开始执行
    • 若是 process_view 方法返回视图函数,提前执行了视图函数,且视图函数报错,则无论 process_exception 方法的返回值是什么,页面都会报错, 且视图函数和 process_exception 方法都不执行
class MD1(MiddlewareMixin):
def process_request(self, request):
print("md1 process_request 方法。", id(request)) # 在视图之前执行
def process_response(self,request, response): # 基于请求响应
print("md1 process_response 方法!", id(request)) # 在视图之后
return response
def process_view(self,request, view_func, view_args, view_kwargs):
print("md1 process_view 方法!") # 在视图之前执行 顺序执行
#return view_func(request)
def process_exception(self, request, exception): # 引发错误 才会触发这个方法
print("md1 process_exception 方法!")
# return HttpResponse(exception) # 返回错误信息

2.3.2 方法二:装饰器

这种编写方式省去了process_request()process_response()方法的编写,中间件本质上是一个可调用的对象(函数、方法、类),它接受一个请求request,并返回一个响应response或者None,就像视图一样。其初始化参数是一个名为 get_response 的可调用对象将它们直接集成在一起了

中间件可以被写成下面这样的函数,下面的语法,本质上是一个Python 装饰器,不推荐这种写法:

def simple_middleware(get_response):
# 配置和初始化
def middleware(request):
# 在这里编写具体业务视图和随后的中间件被调用之前需要执行的代码
response = get_response(request)
# 在这里编写视图调用后需要执行的代码
return response
return middleware

2.3.3 方法三:类方式(推荐)

写成一个类,这个类的实例是可调用的

class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# 配置和初始化
def __call__(self, request):
# 在这里编写视图和后面的中间件被调用之前需要执行的代码
# 这里其实就是旧的 process_request() 方法的代码
response = self.get_response(request)
# 在这里编写视图调用后需要执行的代码
# 这里其实就是旧的process_response()方法的代码
return response
def process_view(self, request, view_func, view_args, view_kwargs):
pass
def process_exception(self,request,exception):
pass

Django 提供的 get_response 方法可能是一个实际视图(如果当前中间件是最后列出的中间件),或者是列表中的下一个中间件。我们不需要知道或关心它到底是什么,它只是代表了下一步要进行的操作

注意事项:

  • Django 仅使用 get_response 参数初始化中间件,因此不能为 __init__() 添加其他参数

  • 与每次请求都会调用 __call__() 方法不同,当 Web 服务器启动后,__init__() 只被调用一次


3. Django 中间件中增加请求参数

主要想法,获取请求头里面的 token(也可以放在参数里),进行解析和验证,将解析得到的数据,放到请求参数HttpRequest 里,供视图使用

from django.http import HttpRequest, HttpResponse
from django.conf import settings
from jwttoken import JwtToken
class TokenVerifyMiddleWare():
""" this is token verification middleware
1. if you want to exempt token verification, you could set the path in the settings.EXEMPT_TOKEN_VERIFY_PATH
"""
_exempt_token_verify_path = settings.EXEMPT_TOKEN_VERIFY_PATH
_request_params_map_items = [
dict(request_method="GET", content_types=["text/plain", ''], request_params_key="GET", mutable_key="GET"),
dict(request_method="POST", content_types=["text/plain", 'application/x-www-form-urlencoded', "multipart/form-data"], request_params_key="POST", mutable_key="POST"),
dict(request_method="POST", content_types=["application/json"], request_params_key="body", mutable_key="_body")]
def __init__(self, get_response: callable) -> None:
self.get_response = get_response
def _is_exempt_verify_path(self, request: HttpRequest) -> bool:
"判断路径是否需要 token 验证"
path_verify_result = False
request_path = request.path
if request_path in self._exempt_token_verify_path or request_path.startswith(self._exempt_file_download_path):
path_verify_result = True
return path_verify_result
def __call__(self, request: HttpRequest) -> HttpResponse:
token_verify_status = True
if not self._is_exempt_verify_path(request):
token_verify_status, payload_data = self._token_verify(request)
self._attach_token_payload(request, payload_data)
response = self.get_response(request) if token_verify_status else HttpResponse(MyTools.to_pretty_dict_str(payload_data))
return response
def _token_verify(self, request: HttpRequest) -> None:
"进行 token 验证"
token = request.META.get('HTTP_HWTOKEN')
verify_status, payload_data = JwtToken.parse_token(token)
return verify_status, payload_data
def _attach_token_payload(self, request: HttpRequest, payload_data: dict) -> None:
"将 token 解析出来的数据,添加进 request"
request_key_info = self._get_request_params_map_infos(request)
request_params_key = request_key_info["request_params_key"]
mutable_key = request_key_info["mutable_key"]
raw_request_params = getattr(request, request_params_key)
is_bytes_format = isinstance(raw_request_params, bytes)
request_params = raw_request_params.copy() if not is_bytes_format else json.loads(raw_request_params.decode())
request_params["personInfo"] = payload_data
uniform_request_params = request_params if not is_bytes_format else json.dumps(request_params, ensure_ascii=False).encode()
setattr(request, mutable_key, uniform_request_params)
def _get_request_params_map_infos(self, request: HttpRequest) -> dict:
"根据 request 映射到对应的 params item"
request_method = request.method
content_type = request.content_type
ret = dict(request_params_key=request_method, mutable_key=request_method)
for request_params_item in self._request_params_map_items:
support_request_method = request_params_item["request_method"]
support_content_types = request_params_item["content_types"]
if support_request_method == request_method and content_type in support_content_types:
ret = request_params_item
break
return ret

主要知识点:

  • 获取请求参数并修改

    # 获取请求参数,这个参数是不可以修改的
    immurequest_params = getattr(request, request_method)
    # 复制一份请求参数,变为可以修改
    mutable_request_params = immurequest_params.copy()
    # 修改请求参数
    mutable_request_params['personInfo'] = payload_data
    # 将修改后的请求参数放到请求头
    setattr(request, request_method, mutable_request_params)
    # 没有办法直接对 body 进行赋值
    setattr(request, "_body", mutable_request_params)
  • 请求中其他参数

    • request.scheme : 请求协议
    • request.body : 请求主体(get没有请求主体)
    • request.path : 请求路径(具体资源路径)
    • request.get_host() : 请求的主机地址 / 域名
    • request.method : 获取请求方法
    • request.GET : 封装了get请求方式所提交的数据(字典)
    • request.POST : 封装了post请求方式所提交的数据(字典)
    • request.COOKIES : 封装了 cookies 中的所有数据
    • request.META : 封装了请求的源数据
  • 如何获取请求头数据

    • request.META.get("header key") 用于获取 header 的信息
    • 注意的是 header key 必须增加前缀 HTTP,同时大写,例如你的 key 为username,那么应该写成:request.META.get("HTTP_USERNAME")
    • 另外就是当你的 header key 中带有中横线,那么自动会被转成下划线,例如my-user的写成: request.META.get("HTTP_MY_USER")
posted @   ChaosMoor  阅读(2141)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩