- 问题背景
- 中间件背景知识
- python 主流框架的请求流程
- python 主流框架自定义中间件实现
- 本期问题的解决方案
- 中间件的适用场景总结
- 总结
全文: 5720字 预计阅读时间: 15 分钟
python生产实战 我靠这 python 中间件解决方案涨薪了
问题背景
当公司的业务只有pc端的时候,前端现在传入的参数有误,但是现在前端无法修改逻辑,必须由后端处理,此时若是让你解决你该如何解决呢?请思考1分钟再往下接着看。
当公司的业务不仅仅只有pc端 还有小程序、app端、h5,现在也出现了这个问题又该如何处理呢?请思考1分钟再接着往下看。
本case是基于python项目的案例可以说是python实战开发中很经典的一个案例,今天拿出来与大家分享。 我简化一下问题: 问题url = "127.0.0.1:8080/api/v1/get_detail_info?platform='h5'&device_id='1111'&version=1.20.1&..." 现在出现的问题为:公司项目中ios的版本在传入的参数 device_id 由于在测试时候写死 造成公司业务在处理逻辑上存在严重问题造成流水等信息错误?目前需要紧急处理,此时公司的项目使用的是python 的 Django架构 请问你会如何处理?亲思考5分钟。
这也就引出了我们今天讨论的话题: 中间件
中间件背景知识
可能有些同学还不清楚什么是中间件,我们就先科普一下,若你是开发老手请移步下一个部分。
中间件是一个用来处理服务的响应与请求的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改服务的输入和输出行为,每个中间件组件都负责做一些特定的功能.在python生态圈中简单来说,Django、Fastapi等的中间件是一个类。用来在全局范围内处理请求和响应。
两者有相似之处也有区别之处,这不是本期讨论的重点暂且不提。
python 主流框架的请求流程
在 http/https 请求到达视图函数之前和视图函数return之后,Django、Fastapi等会根据自己的规则在合适的时机执行中间件中相应的方法。 中间件的执行流程: 1.执行完所有的request方法 到达视图函数。 2.执行中间件的其他方法 3.经过所有response方法 返回客户端。
注意: 如果在其中一个中间件里 request方法中 return了值,就会执行当前中间的response 方法,返回给用户 若有报错则不会再执行下一个中间件。
我们通过流程图来看一下整个执行的流程:
python 主流框架自定义中间件实现
- Django 中关于中间件的定义及实现
# 1 在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',
"M1",
]
# 2 实现中间件
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self, request):
"""
# 请求时过滤
"""
print('M1.request')
def process_view(self, request,callback,callback_args,callback_kwargs):
"""
#request:请求信息, callback:函数名, callback_args:函数可变参数(元组调用), callback_kwargs:函数关键字参数(字典调用)
"""
print("M1.process_view")
def process_response(self, request, response):
"""
# 返回时过滤
"""
print('M1.response')
return response
def process_exception(self, request, exception):
"""
views内代码异常执行,exception:异常信息
"""
pass
def process_template_response(self, request, exception):
"""
# 如果views返回的对象有render方法 则该函数被执行
"""
pass
- Fastapi 中关于中间件的定义及实现
from fastapi import FastAPI
from app.responses import UTF8ORJSONResponse
from xx.oo import M1
app = FastAPI(
title=settings.PROJECT_NAME,
description="xxx API",
version="1.0.0",
docs_url="",
redoc_url="",
on_startup=[],
on_shutdown=[],
default_response_class=UTF8ORJSONResponse,
)
# 1 注册中间件
app.add_middleware(M1)
# 2 在xx下的oo.py文件 中实现中间件
from starlette.middleware.base import (BaseHTTPMiddleware,
RequestResponseEndpoint)
class M1(BaseHTTPMiddleware):
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
"""
实现具体的请求的逻辑
"""
response = await call_next(request)
return response
本期问题的解决方案
在网络上我搜索了一圈,给出的关于中间件的案例基本都是很简单的case,对于python入门的同学来说可以帮助其快速的理解中间件的流程,但是对于python实战来说没有任何的锻炼价值,也接触不到企业生产环境中是如何使用python的中间件的,看到最多的case 类似于:
代码语言:javascript复制import re
from django.http import JsonResponse
from django.shortcuts import HttpResponseRedirect
from django.utils.deprecation import MiddlewareMixin
class LoginCheckMiddleware(MiddlewareMixin):
def process_request(self, request):
# | 分隔要匹配的多个url,从左到右匹配,有匹配就返回匹配值,否则返回None。
pattern = r'^(/$|/user/user/[0-9] /$|/user/user/$|/user/getuserall|/user/get_token_code|/user/update_phone_no|/stock|/future)'
# 如果 request.path 的开始位置能够找到这个正则样式的任意个匹配,就返回一个相应的匹配对象。
# 如果不匹配,就返回None
match = re.search(pattern, request.path)
# 需要拦截的url
if match and not request.user.is_authenticated:
print('用户未登录URL拦截 >>: ', request.path)
# 主页未登录
if request.path == '/':
return HttpResponseRedirect('/user/login/')
# ajax请求未登录
else:
return JsonResponse({'status': False, 'info': '用户未登录!'})
只是做数据的判断然后进行一些逻辑的处理之后把请求返回,但在网上基本找不到做数据替换修改请求参数的案例,这说明什么问题?值得大家的思考。
现在给出我在生产环境中实现请求参数替换的案例case。
注意:一部分的逻辑我做了脱敏处理 大家知道其处理方案及方法就好,思路、方法的价值远大于具体问题的实现
解决方案: 1.在 nginx 层面做请求数据的替换(可行,不过不在本期的处理方案考虑中,有兴趣的可以自己尝试) 2.在 服务框架(Django、Fastapi) 进行数据的替换 --> 使用自定义中间件实现
我们先看一下这个版本的处理方式是否正确
代码语言:javascript复制class M1(object):
def process_request(self, request):
version_str = request.GET.get("version", "") or ""
device_id = request.GET.get("device_id","") or ""
if version_str == "1.10.0":
if device_id > 'x':
request.GET.get("device_id","") = "1234567"
def process_response(self, request, response):
return response
通过这种方式能成功的替换 device_id 的值吗?请大家思考,若不行是为什么呢?
我给出生产环境 可用且可靠的方案 2.1 Djang版本
代码语言:javascript复制class ReplaceDeviceMiddleware(object):
def process_request(self, request):
version_str = request.GET.get("version", "") or ""
device_id = request.GET.get("device_id","") or ""
if version_str == "1.10.1":
environ_qs = request.environ.get('QUERY_STRING', '')
replaced_environ_qs = environ_qs.replace(device_id.encode('utf8'),'1234567',)
request.environ['QUERY_STRING'] = replaced_environ_qs
del request.GET
def process_response(self, request, response):
return response
2.2 Fastapi 版本
代码语言:javascript复制from starlette.middleware.base import (BaseHTTPMiddleware,
RequestResponseEndpoint)
class M1(BaseHTTPMiddleware):
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
platform: str = request.query_params.get("version") or ""
device_id: str = request.query_params.get("device_id") or ""
if version == "1.10.0":
environ_qs = request.environ.get('QUERY_STRING', '')
replaced_environ_qs = environ_qs.replace(device_id.encode('utf8'),'1234567',)
request.environ['QUERY_STRING'] = replaced_environ_qs
del request.GET
response = await call_next(request)
return response
中间件的适用场景总结
由于中间件工作在 视图函数执行前、执行后(像不像所有视图函数的装饰器!)适合所有的请求/一部分请求做批量处理
1.做IP限制 放在 中间件类的列表中,阻止某些IP访问了;
2.URL访问过滤 如果用户访问的是login视图(放过) 如果访问其他视图(需要检测是不是有session已经有了放行,没有返回login),这样就省得在 多个视图函数上写装饰器了!
3.缓存(CDN相关) 客户端请求来了,中间件去缓存看看有没有数据,有直接返回给用户,没有再去逻辑层 执行视图函数
4.由于特殊需求进行请求参数的强制修复
其实在中间件部分能做的东西很多,这里列举的基本是比较常见的也是遇到的比较多的,后期若在生产环境中有使用再做相关分享。
总结
- 了解了中间件的使用场景。
- 分别讲解了 Djang、Fastapi框架如何使用中间件。
- 给出了自己生产实践遇到问题的解决方案。
原创不易,可能看文章只需要15分钟,可想而知作者在构思编辑排版文章花费的时间可能是4到5个小时,我图什么呢?只愿能帮助哪些需要这些内容的同行或刚入行的小伙伴,你的每次 点赞、分享 都是我继续创作下去的动力,我希望能在推广python技术的道路上尽我一份力量,感谢大家。