大型fastapi项目实战 靠 python 中间件解决方案涨薪了

2021-03-03 11:04:57 浏览数 (1)

  • 问题背景
  • 中间件背景知识
  • 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 主流框架自定义中间件实现
  1. Django 中关于中间件的定义及实现
代码语言:javascript复制
# 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
  1. Fastapi 中关于中间件的定义及实现
代码语言:javascript复制
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.由于特殊需求进行请求参数的强制修复

其实在中间件部分能做的东西很多,这里列举的基本是比较常见的也是遇到的比较多的,后期若在生产环境中有使用再做相关分享。

总结
  1. 了解了中间件的使用场景。
  2. 分别讲解了 Djang、Fastapi框架如何使用中间件。
  3. 给出了自己生产实践遇到问题的解决方案。

原创不易,可能看文章只需要15分钟,可想而知作者在构思编辑排版文章花费的时间可能是4到5个小时,我图什么呢?只愿能帮助哪些需要这些内容的同行或刚入行的小伙伴,你的每次 点赞、分享 都是我继续创作下去的动力,我希望能在推广python技术的道路上尽我一份力量,感谢大家。

0 人点赞