【Python基础】FastAPI 中间件与依赖注入使用详解

目录

一、前言

[二、FastAPI 中间件介绍与使用](#二、FastAPI 中间件介绍与使用)

[2.1 什么是FastAPI中间件](#2.1 什么是FastAPI中间件)

[2.2 中间件核心执行原理](#2.2 中间件核心执行原理)

[2.3 中间件基本使用](#2.3 中间件基本使用)

[2.3.1 基本语法](#2.3.1 基本语法)

[2.3.2 示例代码](#2.3.2 示例代码)

[2.3.3 记录请求时间](#2.3.3 记录请求时间)

[2.3.4 记录请求日志信息](#2.3.4 记录请求日志信息)

[2.4 Starlette 内置中间件](#2.4 Starlette 内置中间件)

[三、FastAPI 依赖注入](#三、FastAPI 依赖注入)

[3.1 依赖注入介绍](#3.1 依赖注入介绍)

[3.1.1 什么是依赖注入](#3.1.1 什么是依赖注入)

[3.1.2 依赖注入优点](#3.1.2 依赖注入优点)

[3.1.3 依赖注入使用场景](#3.1.3 依赖注入使用场景)

[3.1.4 依赖注入使用步骤](#3.1.4 依赖注入使用步骤)

[3.2 依赖注入案例操作](#3.2 依赖注入案例操作)

[3.2.1 注入通用分页参数](#3.2.1 注入通用分页参数)

[3.2.2 类作为依赖](#3.2.2 类作为依赖)

[3.2.3 子依赖](#3.2.3 子依赖)

[3.2.4 装饰器中使用依赖](#3.2.4 装饰器中使用依赖)

[3.2.5 全局依赖](#3.2.5 全局依赖)

四、写在文末


一、前言

在日常项目开发中,会涉及到一些与主业务流程逻辑无关,但又是很重要的操作,比如日志记录,参数记录,请求拦截等,这些过程不会对主流程造成影响但却是项目中非常重要的操作,此时就可以借助FastAPI中的中间件或依赖注入完成对这类业务的统一操作,本文将详细介绍。

二、FastAPI 中间件介绍与使用

2.1 什么是FastAPI中间件

中间件是一种在每个请求到达路由处理函数之前和之后执行的函数。

中间件可以用于添加日志、修改请求/响应、处理 CORS 等通用逻辑。

中间件的作用是:为每个请求添加统一的处理逻辑(记录日志、身份认证、跨域、设置响应头、性能监控等)

比如在下图的一些场景中就可以借助中间件来实现

2.2 中间件核心执行原理

中间件(Middleware)是一个在每次请求进入FastAPI应用时都会被执行的函数。

它在请求到达实际的路径操作(路由处理函数)之前运行,并且在响应返回给客户端之前再运行一次。

下面这张图形象的展示了中间件在一次请求响应的处理中所处的关键位置

完整的执行流程如下:

  • 请求到达中间件

  • 中间件执行预处理逻辑

  • 中间件将请求传递给下一个中间件或路由函数

  • 路由函数返回响应

  • 中间件执行后处理逻辑

  • 响应返回给客户端

对上图中关键的组件介绍如下:

|------------------------|------------------------------------------|
| 名称 | 作用 |
| 客户端(Client) | 浏览器、前端 App、Postman 等向 FastAPI 发起 HTTP 请求 |
| Request 请求 | HTTP 请求对象,包含 URL、Header、Body、Cookie 等数据 |
| 中间件 1 | 请求进入后的第一层处理 |
| 中间件 2 | 第二层处理逻辑 |
| 中间件 N | 多个中间件按顺序执行 |
| 路由处理函数(Path Operation) | 真正执行业务代码的位置 |
| 响应阶段 | 路由返回 Response 后,响应会再次经过中间件 |
| 中间件逆序返回 | 响应按"相反顺序"返回 |
| Response 响应 | 最终返回给客户端的数据 |

2.3 中间件基本使用

2.3.1 基本语法

中间件:函数的顶部使用装饰器@app.middleware("http"),如下代码:

2.3.2 示例代码

下面是一段结合中间件使用的示例代码

python 复制代码
from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.middleware("http")
async def middleware1(request, call_next):
    print("中间件1开始")
    response = await call_next(request)
    print("中间件1结束")
    return response

@app.middleware("http")
async def middleware2(request, call_next):
    print("中间件2开始")
    response = await call_next(request)
    print("中间件2结束")
    return response

@app.get("/")
def get_root():
    res = {"message":"hello world!"}
    print(res)
    return res

请求一下接口之后,观察控制台输出,通过输出日志可以清晰看到中间件的执行顺序

代码解释说明:

  1. @app.middleware("http")

    1. 声明这是一个 HTTP 中间件
  2. call_next

    1. 调用下一个中间件或路由函数的回调
  3. await call_next(request)

    1. 将请求传递给下一层,获取响应

call_next 接收 request 参数并返回 response。你可以在调用 call_next 之前修改请求,在调用之后修改响应。

2.3.3 记录请求时间

一种比较常见的场景是,记录接口的响应时间,从而指导接口做性能优化,如下代码

python 复制代码
import time
from fastapi import FastAPI, Request

app = FastAPI()


@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    # 1. 请求前的处理:记录开始时间
    start_time = time.time()

    # 2. 将请求传递给下一个中间件或路由函数
    response = await call_next(request)

    # 3. 响应后的处理:计算处理时间并添加响应头
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response


@app.get("/")
async def root():
    return {"message": "Hello World"}

2.3.4 记录请求日志信息

在很多场景下,需要对接口请求的参数进行统一控制,比如做一些安全参数的审计等,就可以通过中间件来做,如下的代码中,通过中间件记录某次接口请求的完整信息

python 复制代码
import time
import logging
from fastapi import FastAPI, Request

app = FastAPI()

logger = logging.getLogger("uvicorn.access")

@app.middleware("http")
async def log_requests(request: Request, call_next):
    # 记录请求信息
    logger.info(f"请求: {request.method} {request.url}")

    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time

    # 记录响应信息
    logger.info(
        f"响应: {request.method} {request.url} "
        f"状态码={response.status_code} 耗时={process_time:.3f}s"
    )

    return response
@app.get("/")
def get_root():
    res = {"message":"hello world!"}
    #print(res)
    return res

2.4 Starlette 内置中间件

FastAPI 继承自 Starlette,可以直接使用 Starlette 提供的中间件:

python 复制代码
from fastapi import FastAPI
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware

app = FastAPI()

# 强制 HTTPS 重定向
app.add_middleware(HTTPSRedirectMiddleware)

@app.get("/")
async def root():
    return {"message": "使用 HTTPS 访问"}

常用的内置 中间件

  1. HTTPSRedirectMiddleware

    1. 强制将 HTTP 请求重定向为 HTTPS
  2. TrustedHostMiddleware

    1. 限制允许访问的主机名
  3. GZipMiddleware

    1. 自动压缩响应内容
  4. CORSMiddleware

    1. 处理跨域请求(下一章详细介绍)

启用 GZip 压缩可以减少响应体积,提升传输速度:

python 复制代码
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware

app = FastAPI()

# 当响应大小超过 1000 字节时自动压缩
app.add_middleware(GZipMiddleware, minimum_size=1000)


@app.get("/")
async def root():
    return {"message": "这个响应可能会被 GZip 压缩"}

小结:

  • 中间件在请求/响应的处理链中执行通用逻辑

  • 使用 @app.middleware("http") 创建自定义中间件

  • call_next(request) 将请求传递给下一层

  • 中间件按注册顺序执行(请求正序,响应反序)

  • FastAPI/Starlette 提供了 CORS、GZip、HTTPS 重定向等内置中间件

三、FastAPI 依赖注入

3.1 依赖注入介绍

3.1.1 什么是依赖注入

FastAPI 提供了一个强大而简洁的依赖注入系统。

依赖注入是一种设计模式,让你可以将通用的逻辑(如数据库连接、身份验证、参数校验等)提取为可复用的组件,然后在路由中按需使用。

  • 使用依赖注入系统来共享通用逻辑,减少代码重复

  • 中间件控制所有接口,依赖注入可以按需人为指定

通俗解释,依赖就是一个函数,它可以使用与路径操作函数相同的参数(查询参数、路径参数、请求体等),FastAPI 会在执行路由函数之前自动调用依赖函数,并将其返回值传递给路由函数。

比如下面这几个列表接口中,都需要用到分页查询的逻辑,那就可以将分页逻辑作为依赖注入的模式进行使用

补充说明:

  1. 依赖项:可重用的组件(函数/类),负责提供某种功能或数据

  2. 注入:FastAPI自动帮你调用依赖项,并将结果"注入"到路径操作函数中。

3.1.2 依赖注入优点

赖注入具有如下优点:

  • 代码复用:一次编写,多处使用

  • 解耦:业务逻辑与基础设施代码分离

  • 易于测试:轻松地用模拟依赖替换真实依赖进行测试

3.1.3 依赖注入使用场景

在下图的场景中可以使用依赖注入

3.1.4 依赖注入使用步骤

下图完整呈现了依赖注入的使用流程

具体的执行流程如下:

当请求到达时,FastAPI 的处理顺序:

  1. 识别路由函数及其依赖

  2. 执行依赖函数(按依赖顺序)

  3. 将依赖函数的返回值传递给路由函数

  4. 执行路由函数

3.2 依赖注入案例操作

3.2.1 注入通用分页参数

在下面的代码中,编写一个通用的分页参数方法,然后被其他的列表查询接口注入使用

python 复制代码
from fastapi import FastAPI, Request,Query,Depends

app = FastAPI()

@app.get("/")
def get_root():
    res = {"message":"hello world!"}
    #print(res)
    return res

# 定义依赖项,定义分页查询参数方法
async def common_page_params(
        skip: int = Query(0,ge=0),
        limit: int = Query(10,le=50)
):
    return {"skip":skip,"limit":limit}

@app.get("/book/list")
def book_list(commons=Depends(common_page_params)):
    return commons

@app.get("/news/list")
def book_list(commons=Depends(common_page_params)):
    return commons

调用一下两个查询接口,可以看到能够拿到依赖注入的方法的参数

3.2.2 类作为依赖

除了普通的函数,你也可以使用类作为依赖,如下代码

  • Depends() 不传入参数时,FastAPI 会自动使用参数的类型注解(CommonQueryParams)作为依赖
python 复制代码
from typing import Annotated
from fastapi import Depends, FastAPI

app = FastAPI()


# 用类声明依赖
class CommonQueryParams:
    def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


# 使用类作为依赖
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
    return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}

3.2.3 子依赖

依赖可以有自己的依赖,形成一个依赖链

  • 代码中的依赖链执行流程:query_extractor -> query_checker -> 路由函数
python 复制代码
from typing import Annotated
from fastapi import Depends, FastAPI

app = FastAPI()


# 依赖函数
def query_extractor(q: str | None = None):
    return q


# 子依赖:依赖 query_extractor
def query_checker(q: str = Depends(query_extractor)):
    if q == "admin":
        # 子依赖可以进行校验
        return q + " (checked)"
    return q


# 路由使用子依赖
@app.get("/items/")
async def read_items(q: str = Depends(query_checker)):
    return {"q": q}

3.2.4 装饰器中使用依赖

假如你只需要依赖的副作用(如权限校验),不需要其返回值。可以在装饰器的 dependencies 参数中声明:

python 复制代码
from fastapi import Depends, FastAPI, Header, HTTPException

app = FastAPI()


# 依赖:校验 API Key
async def verify_api_key(x_api_key: str = Header()):
    if x_api_key != "secret-key":
        raise HTTPException(status_code=400, detail="X-API-Key invalid")


# 在装饰器中使用依赖,不需要返回值
@app.get("/items/", dependencies=[Depends(verify_api_key)])
async def read_items():
    return [{"item": "Foo"}]


# 对整个路由组使用依赖
@app.get("/users/", dependencies=[Depends(verify_api_key)])
async def read_users():
    return [{"user": "Bar"}]

3.2.5 全局依赖

可以在 FastAPI 实例上声明全局依赖,这样的话对所有路由生效,如下代码:

python 复制代码
from fastapi import Depends, FastAPI, Header, HTTPException

async def verify_token(x_token: str = Header()):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


# 全局依赖:所有路由都需要通过 token 校验
app = FastAPI(dependencies=[Depends(verify_token)])


@app.get("/items/")
async def read_items():
    return [{"item": "Foo"}]


@app.get("/users/")
async def read_users():
    return [{"user": "Bar"}]

小结

  • 依赖注入将通用逻辑提取为可复用的函数或类

  • 使用 Depends() 在路由函数参数中声明依赖

  • 依赖可以嵌套,形成依赖链

  • 装饰器的 dependencies 参数用于不需要返回值的依赖

  • 全局依赖对所有路由生效

四、写在文末

本文详细介绍了FastAPI 中中间件和依赖注入的使用,有兴趣的同学可以继续深入研究并结合实际项目使用,本篇到此结束,感谢观看。

相关推荐
逆风飞翔的小叔18 天前
【Python基础】FastAPI 从入门到项目实战操作详解
fastapi·fastapi 详解·fastapi 使用详解·fastapi 请求参数详解·fastapi 总结