我一直想定义一个日志类型的中间件,且再这个中间件中可以自由读取到消费我们的body,不会因为在路由里面进行消费,或其他中间件也消费的时候引发阻塞,也就是多重消费问题!通过多种方式间接和源码的理解,最终还是可以解决了!
本篇主要是为了纠正上一篇提出的方案,测试之后存在问题!需要纠正!所以前面那一片建议不用看了!上一篇的地址为
fastapi框架源码分析系列-starlette框架之Debug方式学源码(5)fastapi中间件消费body消息体再次消费
上一篇中提到关于双重中间件消费请求中requeset中的body受阻的问题,有探讨过一种上方式就是,单独使用一个中间件获取到requeset中的body然后赋值到,可以完美处理此类需求:
from fastapi import FastAPI, Header
from starlette.requests import Request
from starlette.responses import Response, StreamingResponse, PlainTextResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import Message
app = FastAPI()
from pydantic import BaseModel
class AdminLogin(BaseModel):
"""
管理员登陆 提交的参数信息
"""
password: str = None
username: str = None
class LogerMiddleware1(BaseHTTPMiddleware):
async def set_body(self, request):
# 定义一个携程对象,返回是一个
async def receive():
return receive_
receive_ = await request._receive()
request._receive = receive
async def dispatch(self, request: Request, call_next):
# await self.proxy_get_body(request)
re = await self.set_body(request)
print("中间件消费", re)
response = await call_next(request)
return response
class LogerMiddleware2(BaseHTTPMiddleware):
async def set_body(self, request):
# 定义一个携程对象,返回是一个
async def receive():
return receive_
receive_ = await request._receive()
request._receive = receive
async def dispatch(self, request: Request, call_next):
# await self.proxy_get_body(request)
re = await self.set_body(request)
print("中间件消费", re)
response = await call_next(request)
return response
app.add_middleware(LogerMiddleware2)
app.add_middleware(LogerMiddleware1)
@app.get("/on")
async def main(res: Request, sdsada: AdminLogin):
# 迭代的方式返回流数据
# buas = res.state.body
# print("路由中在消费---》", buas)
return PlainTextResponse('fanhui')
if __name__ == '__main__':
import uvicorn
uvicorn.run('ceshi88:app')
上面完整代码运行起来其实会受阻,归根结底病症还是多重消费问题,上面的问题涉及到三次消费:
加入我坚持需要都在各自中间中消费各自的body,且相互不影响的话,那该怎么办呢?
from fastapi import FastAPI, Header
from starlette.requests import Request
from starlette.responses import Response, StreamingResponse, PlainTextResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import Message
app = FastAPI()
from pydantic import BaseModel
class AdminLogin(BaseModel):
"""
管理员登陆 提交的参数信息
"""
password: str = None
username: str = None
class LogerMiddleware1(BaseHTTPMiddleware):
async def set_body(self, request):
# 定义一个携程对象,返回是一个
async def receive():
return receive_
receive_ = await request._receive()
request._receive = receive
async def dispatch(self, request: Request, call_next):
re = await self.set_body(request)
request.state.body = re
print("中间件消费", re)
response = await call_next(request)
return response
class LogerMiddleware2(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
buas = request.state.body
print("中间件消费22", buas)
response = await call_next(request)
return response
# app.add_middleware(LogerMiddleware2)
app.add_middleware(LogerMiddleware1)
@app.get("/on")
async def main(res: Request, sdsada: AdminLogin):
# 迭代的方式返回流数据
buas = res.state.body
# print("路由中在消费---》", buas)
return PlainTextResponse('fanhui')
@app.get("/on2")
async def main(res: Request, sdsada: AdminLogin):
# 迭代的方式返回流数据--路由内可以多次重复消费读取
buas = res.state.body
s = await res.body()
# print("路由中在消费---》", buas)
return PlainTextResponse('fanhui')
if __name__ == '__main__':
import uvicorn
uvicorn.run('ceshi8:app')
上述案例似乎是完美的,看似可以解决问题!!!!!但是但是但是!!!!问题点在于当我注册两个中间件的的时候,那么就又遇到被重复消费的问题了!!!也就是,其实第一个中间件读取了,放到了scope中保存,但是还是会遇到二次消费的问题!
app.add_middleware(LogerMiddleware2)
app.add_middleware(LogerMiddleware1)
注册两个都基于BaseHTTPMiddleware中间件的时候又遇到问题了!所以上面那种的方案!!又需要废弃了!!!因为不可能我用了一个之后 后续都不能继续注册中间件了是吧!!!
所以注意上面的方案宣告废弃!!!!
from fastapi import FastAPI
from starlette.responses import PlainTextResponse, RedirectResponse, Response
from starlette.types import ASGIApp, Receive, Scope, Send
from fastapi import Request, Depends
app = FastAPI()
class LogerMiddleware1:
def __init__(
self,
app: ASGIApp,
) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http": # pragma: no cover
await self.app(scope, receive, send)
return
# 定义一个新的携程函数---->
# 接收一次
receive_ = await receive()
async def receive():
return receive_
# 创建需要解析的参数
request = Request(scope, receive)
re = await request.json()
print('中间件1中多次消费!22212', re)
await self.app(scope, receive, send)
class LogerMiddleware2:
def __init__(
self,
app: ASGIApp,
) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http": # pragma: no cover
await self.app(scope, receive, send)
return
# 接收一次
receive_ = await receive()
async def receive():
return receive_
# 创建需要解析的参数
request = Request(scope, receive)
re2 = await request.body()
print('中间件2中多次消费!12', re2)
re = await request.json()
print('中间件2中多次消费!12', re)
# 把接收转化为一个新的携程对象穿
await self.app(scope, receive, send)
app.add_middleware(LogerMiddleware1)
app.add_middleware(LogerMiddleware2)
from pydantic import BaseModel
class AdminLogin(BaseModel):
"""
管理员登陆 提交的参数信息
"""
password: str = None
username: str = None
@app.get("/on")
async def main(res: Request, sdsada: AdminLogin):
# 迭代的方式返回流数据
buas = await res.json()
print("路由中单独再消费", buas)
return PlainTextResponse('fanhui')
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)
通过上面这种方式,则可以完美解决,我每个中间件中都可以独立提取body进行消费了!!也不需要以前那种必须在自定义一路由类中进行路由日志记录了!!!
完整代码示例:
from fastapi import FastAPI
from starlette.responses import PlainTextResponse, RedirectResponse, Response
from starlette.types import ASGIApp, Receive, Scope, Send
from fastapi import Request, Depends
app = FastAPI()
class LogerMiddleware1:
def __init__(
self,
app: ASGIApp,
) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http": # pragma: no cover
await self.app(scope, receive, send)
return
# 定义一个新的携程函数---->
# 接收一次
# receive_ = await receive()
# async def receive():
# return receive_
# 创建需要解析的参数
request = Request(scope, receive)
# re = await request.json()
re = request.state.body
print('中间件1中多次消费!22212', re)
await self.app(scope, receive, send)
class LogerMiddleware2:
def __init__(
self,
app: ASGIApp,
) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http": # pragma: no cover
await self.app(scope, receive, send)
return
# 接收一次
receive_ = await receive()
async def receive():
return receive_
# 创建需要解析的参数
request = Request(scope, receive)
re2 = await request.body()
print('中间件2中多次消费!12', re2)
re = await request.json()
print('中间件2中多次消费!12', re)
# buas = request.state.body
request.state.body = re
# 把接收转化为一个新的携程对象穿
await self.app(scope, receive, send)
app.add_middleware(LogerMiddleware1)
app.add_middleware(LogerMiddleware2)
from pydantic import BaseModel
class AdminLogin(BaseModel):
"""
管理员登陆 提交的参数信息
"""
password: str = None
username: str = None
@app.get("/on")
async def main(res: Request, sdsada: AdminLogin):
# 迭代的方式返回流数据
buas = await res.json()
print("路由中单独再消费", buas)
return PlainTextResponse('fanhui')
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)
上面都可以吧!后续你可以自己的定义属于自己的日志中间件了!主要是纠正上一篇问题!
以上仅仅是个人结合自己的实际需求,做学习的实践笔记!如有笔误!欢迎批评指正!感谢各位大佬!
END
简书:https://www.jianshu.com/u/d6960089b087
掘金:https://juejin.cn/user/2963939079225608
公众号:微信搜【程序员小钟同学】
小钟同学 | 文 【欢迎一起学习交流】| QQ:308711822
原文作者:程序员小钟同学;如有侵权,请联系管理员删除。