|
2 | 2 | title: 节流 |
3 | 3 | --- |
4 | 4 |
|
5 | | -我们有一个关于路由器的历史讨论,如果你感兴趣,可以查看:[#70](https://github.com/fastapi-practices/fastapi_best_architecture/discussions/70) |
| 5 | +在现代 Web 开发中,API 限流(Rate |
| 6 | +Limiting)是保护后端服务、防止资源滥用、保证服务稳定性的重要机制,我们有一个关于路由器的历史讨论,如果你感兴趣,可以查看:[#70](https://github.com/fastapi-practices/fastapi_best_architecture/discussions/70) |
6 | 7 |
|
7 | | -[**fastapi-limiter** GitHub 仓库地址](https://github.com/long2ice/fastapi-limiter){.read-more} |
| 8 | +## 处理流程 |
8 | 9 |
|
9 | | -## 使用 |
| 10 | +以下是 RateLimiter 处理一次请求的完整流程: |
10 | 11 |
|
11 | | -更多使用方法请查看官方仓库 [README](https://github.com/long2ice/fastapi-limiter/blob/master/README.md#quick-start) |
| 12 | +```mermaid |
| 13 | +graph TD |
| 14 | + A[请求进入路由依赖] --> B[初始化 Bucket 和 Limiter] |
| 15 | + B --> C[获取 Identifier] |
| 16 | + C --> D[异步尝试获取] |
| 17 | + D -->|获取成功| E[放行请求,继续业务处理] |
| 18 | + D -->|获取失败| F[计算 Retry-After] |
| 19 | + F --> G[执行 Callback<br>(默认抛出 429 异常)] |
| 20 | +``` |
| 21 | + |
| 22 | +## 使用方法 |
| 23 | + |
| 24 | +RateLimiter 设计为 FastAPI 的依赖项,直接在路由中使用 `Depends` 注入 |
| 25 | + |
| 26 | +### 单规则限流 |
| 27 | + |
| 28 | +```python |
| 29 | +# 每分钟最多 60 次 |
| 30 | +@app.get( |
| 31 | + "/api/example", |
| 32 | + dependencies=[Depends(RateLimiter(Rate(5, Duration.MINUTE)))] |
| 33 | +) |
| 34 | +async def example(): |
| 35 | + return {"message": "success"} |
| 36 | +``` |
| 37 | + |
| 38 | +### 多规则复合限流 |
12 | 39 |
|
13 | | -```python{1,6,11-17,25,29} |
14 | | -@app.get("/", dependencies=[Depends(RateLimiter(times=1, seconds=5))]) |
15 | | -async def index_get(): |
16 | | - return {"msg": "Hello World"} |
| 40 | +```python |
| 41 | +# 每秒 10 次 + 每分钟 100 次 |
| 42 | +@app.post( |
| 43 | + "/api/heavy", |
| 44 | + dependencies=[ |
| 45 | + Depends( |
| 46 | + RateLimiter( |
| 47 | + Rate(10, Duration.SECOND), |
| 48 | + Rate(100, Duration.MINUTE), |
| 49 | + ) |
| 50 | + ) |
| 51 | + ] |
| 52 | +) |
| 53 | +async def heavy_endpoint(): |
| 54 | + return {"status": "ok"} |
| 55 | +``` |
17 | 56 |
|
| 57 | +### 自定义 Identifier |
18 | 58 |
|
19 | | -@app.post("/", dependencies=[Depends(RateLimiter(times=1, seconds=5))]) |
20 | | -async def index_post(): |
21 | | - return {"msg": "Hello World"} |
| 59 | +```python |
| 60 | +async def user_identifier(request: Request) -> str: |
| 61 | + return f"user:{request.user.id}" |
22 | 62 |
|
23 | 63 |
|
24 | 64 | @app.get( |
25 | | - "/multiple", |
| 65 | + "/api/user-data", |
26 | 66 | dependencies=[ |
27 | | - Depends(RateLimiter(times=1, seconds=5)), |
28 | | - Depends(RateLimiter(times=2, seconds=15)), |
29 | | - ], |
| 67 | + Depends( |
| 68 | + RateLimiter( |
| 69 | + Rate(50, Duration.MINUTE), |
| 70 | + identifier=user_identifier, |
| 71 | + ) |
| 72 | + ) |
| 73 | + ] |
30 | 74 | ) |
31 | | -async def multiple(): |
32 | | - return {"msg": "Hello World"} |
33 | | -
|
34 | | -
|
35 | | -@app.websocket("/ws") |
36 | | -async def websocket_endpoint(websocket: WebSocket): |
37 | | - await websocket.accept() |
38 | | - ratelimit = WebSocketRateLimiter(times=1, seconds=5) |
39 | | - while True: |
40 | | - try: |
41 | | - data = await websocket.receive_text() |
42 | | - await ratelimit(websocket, context_key=data) |
43 | | - await websocket.send_text("Hello, world") |
44 | | - except HTTPException: |
45 | | - await websocket.send_text("Hello again") |
| 75 | +async def user_data(): |
| 76 | + return {"data": "protected"} |
46 | 77 | ``` |
0 commit comments