FastAPI-01.心智模型-ASGI与请求生命周期

行文:T1 原理篇 | 本篇方法:第一性原理 + 费曼 | 辅助:双重编码

前置补充(host/port、TCP、HTTP 与 WebSocket、流式对比):01补 网络基础


1. 问题:没有这层心智模型会坏在哪

只背 @app.get 也能写出 Hello World,但下面几类问题会反复踩坑:

现象 根因往往在
「异步写了却不异步」:接口变慢、事件循环卡死 不知道请求跑在 ASGI 服务器的事件循环里,阻塞调用会占住循环
启动时连库失败、多 worker 下连接池错乱 把「进程启动」和「单次请求」混为一谈,没用对 lifespan
想把 MCP / 别的 ASGI 应用挂进同一进程却挂不上 不清楚 FastAPI 本身就是一个 ASGI 应用,可被 mount / 组合
分不清 Flask 与 FastAPI「慢在哪、快在哪」 没建立 WSGI vs ASGI 的协议层差异

本篇只建立心智模型:一次请求从字节流变成你的函数返回值,再变回字节流;路由细节留给 02。

段末注释ASGI(Asynchronous Server Gateway Interface,异步服务器网关接口)是 Python 异步 Web 服务器与应用之间的标准接口;WSGI(Web Server Gateway Interface)是传统同步接口。后文沿用缩写。


2. 拆到底:最少必要组件

把「Web 框架」剥到不能再剥,只剩三样东西在协作:

1
2
[客户端] --TCP/HTTP字节--> [ASGI 服务器] <--约定--> [ASGI 应用]
uvicorn 等 FastAPI / Starlette / 手写 callable

2.1 ASGI 服务器(谁听端口)

  • 职责:收 TCP、解析 HTTP(或 WebSocket)、按 ASGI 规范调用应用、把应用发出的事件写回套接字。
  • 常见实现:uvicorn、hypercorn、daphne。
  • 你本地敲的 uvicorn main:app,本质是:用服务器加载名为 app 的 ASGI 应用
  • host:port / TCP / HTTP 会话边界的通俗说明见 01补

2.2 ASGI 应用(一个可等待的调用约定)

规范上,应用是一个 async callable,签名形如:

1
2
async def app(scope, receive, send):
...

三个参数就是「协议合同」的全部:

参数 是什么 类比
scope 描述这次连接的字典(类型、路径、头、方法等) 快递单上的地址与类型
receive 异步可调用:从服务器取事件(如请求体块) 打开包裹取货
send 异步可调用:向服务器发事件(状态码、头、正文) 回寄回执与货物

HTTP 下:一次请求 ≈ 一次对 app(...) 的调用,scope["type"] == "http"
WebSocket 下:连接存活期间共用一个 scope,事件持续进出。
另有 lifespan 类型:不服务用户请求,只负责进程内「启动 / 关闭」。

2.3 框架在合同之上堆的东西

FastAPI 并不替代 ASGI;它实现上述 callable,并在内部叠:

  1. Starlette:路由、中间件、Request/Response、WebSocket 等 ASGI 工具层。
  2. Pydantic:把 query/body 等解析成类型安全的数据模型(细节见 Pydantic 笔记)。
  3. 你的路由函数:业务逻辑;框架负责把 scope/receive/send 翻译成函数参数与返回值。

第一性原理结论:你会写的每个 @app.get,最终都要变成对 send 的若干次调用。


3. 白话重建(费曼稿)

把整条链路讲给「只懂寄快递」的人听:

  1. 客户往某个门牌(主机:端口)扔一个信封(HTTP 请求)。
  2. 门房(uvicorn)拆开信封,填一张派送单(scope):送去哪条路径、什么方法、有哪些贴纸(headers)。正文可能太大,先不一次性塞进派送单,后面用 receive 一块块取。
  3. 门房把派送单交给公司前台(FastAPI 应用)。前台按路径找柜员(路由),柜员可能先找后勤(依赖注入,见 03),再办事。
  4. 柜员办完,前台把结果整理成「回执事件」:先说状态与贴纸,再一段段正文,通过 send 交给门房。
  5. 门房写回套接字,客户收到响应。

和 Flask 差在哪(只记一点):Flask 走 WSGI——一次调用通常是「同步函数拿完请求、返回完响应」;ASGI 把交互拆成 事件流,天然能挂异步 IO、WebSocket、流式响应,也才能和「同一进程里的其他 ASGI 应用」(例如 MCP 的 HTTP 传输)组合。

进程级另一条线(lifespan):店开门时备货(连库、加载模型),打烊时收摊。这条线不是每个请求都跑,而是每个 worker 进程 / 事件循环在开始接客前、停业时各走一轮。多 worker 时,每个进程各自备货——所以「全局只连一次」的直觉在多进程下是错的。


4. 结构图(双重编码)· 全链路执行流程

先建立「服务器 ↔ 应用」粗图,再展开 FastAPI 进程内全部可能组件的任务流转。与 04 §0 同源,便于中间件/异常篇对照。

4.1 粗粒度:ASGI 两边

ASGI 请求生命周期(科普漫画)

读图时同时记住两层时间尺度:

尺度 跑几次 典型工作
lifespan(进程/事件循环) 每 worker 启动与关闭各一套 建池、加载模型、释放资源
单次 HTTP 请求 每请求一次 app(scope, receive, send) 中间件 → 路由 → 依赖 → 校验 → 业务 → 响应事件

4.2 细粒度:一次 HTTP 的完整组件链

全链路请求生命周期(科普漫画)

组件 职责
网络 客户端、TCP、反向代理(可选) 字节进出
ASGI 服务器 uvicorn 解析协议 → 调 app(scope, receive, send)
应用 FastAPI / Starlette ASGI 应用本体
横切 中间件洋葱 进路由前 / 出响应后;可短路
错误 exception_handler 异常 → Response
路由 路由表 / APIRouter method+path 匹配
注入 Depends 用户、会话等
校验 Pydantic Path/Query/Body;失败常 422
业务 端点 async def / def 后者常进线程池
整形 response_model、状态码 出站形状
后置 BackgroundTasks 响应后轻量活
回写 send 响应事件写回服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
flowchart TD
C[客户端] --> N[TCP / 可选反向代理]
N --> U[uvicorn ASGI 服务器]
U -->|scope receive send| APP[FastAPI ASGI 应用]

subgraph life["进程时钟 · 非每请求"]
L1[lifespan 启动] -.-> APP
APP -.-> L2[lifespan 关闭]
end

APP --> MW_IN[中间件洋葱 · 请求下行]
MW_IN --> RT{路由匹配}
RT -->|否| E404[404 / 405]
RT -->|是| DEP[Depends]
DEP --> VAL[参数校验]
VAL -->|失败| E422[422 handler]
VAL -->|通过| EP{端点 async / sync}
EP --> BIZ[业务逻辑]
BIZ --> RESP[Response / response_model]
BIZ -.->|异常| EH[exception_handler]
E404 --> MW_OUT
E422 --> MW_OUT
EH --> MW_OUT
RESP --> MW_OUT[中间件洋葱 · 响应上行]
MW_OUT --> SEND[ASGI send]
SEND --> U --> C
RESP -.-> BG[BackgroundTasks 可选]

旁路:中间件不调 call_next 则短路;WebSocket 为长连接事件流;mount 把前缀交给子 ASGI 应用;多 worker 则每进程一套 lifespan。中间件洋葱与异常落点的排错见 04

段末注释中间件洋葱:请求从外层进入、响应从内层传出;后注册的 add_middleware 通常更靠外。


5. 最小代码:只证明原理

5.1 纯 ASGI:不用 FastAPI 也能应答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# plain_asgi.py —— 证明合同长什么样
async def app(scope, receive, send):
if scope["type"] != "http":
return
await send({
"type": "http.response.start",
"status": 200,
"headers": [(b"content-type", b"text/plain; charset=utf-8")],
})
await send({
"type": "http.response.body",
"body": b"hello from raw ASGI\n",
})

# 运行:uvicorn plain_asgi:app --port 8000

合上眼睛应能复述:没有路由装饰器,只有 send 两次事件(先 start,再 body)。

5.2 FastAPI:同一合同上的薄封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# main.py
from contextlib import asynccontextmanager

from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动:备货(示例仅打印)
app.state.ready = True
yield
# 关闭:收摊
app.state.ready = False


app = FastAPI(lifespan=lifespan)


@app.get("/ping")
async def ping():
return {"ok": True, "ready": app.state.ready}

# 运行:uvicorn main:app --reload --port 8000
# 探针:curl -s http://127.0.0.1:8000/ping

要点:

  • FastAPI(...) 实例就是 ASGI 应用(可被 uvicorn 加载,也可被别的 ASGI 应用 mount)。
  • lifespan 推荐写法是 async context manager 交给 FastAPI(lifespan=...)yield 前为启动,后为关闭。官方已不推荐再依赖旧的 @app.on_event("startup") 风格作为新代码默认。
  • 路由函数返回 dict 时,框架负责序列化并最终变成对 send 的调用——你日常不必手写事件字典。

5.3 组件位置速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uvicorn  →  加载 main:app

├─ lifespan:启动 / 关闭(每 worker 一次套)

└─ 每个 HTTP 请求:
scope + receive + send
→ 中间件洋葱(下行)
→ 路由匹配
→ Depends
→ Pydantic 校验
→ 端点 async/def
→ Response(或 exception_handler)
→ 中间件洋葱(上行)
→ send 事件
→(可选)BackgroundTasks

完整图见 §4


6. 和 Flask / WSGI 的边界(防混淆)

维度 WSGI(Flask 典型) ASGI(FastAPI 典型)
调用形态 同步 environ + start_response 异步 scope + receive + send
长连接 / 流式 别扭或靠扩展 协议一等公民(WebSocket、chunked/流式)
并发模型 多线程/多进程为主 事件循环 + 可混进程/多 worker
应用组合 中间件栈,形态偏 WSGI ASGI 应用可嵌套、mount

不必贬低 Flask:同步 CRUD、模板站仍然合适。选 FastAPI 的常见理由是 API + 类型校验 + 异步 IO + ASGI 生态组合(含后续与 MCP 共进程)。


7. 合书自测(费曼三问)

遮住上文,口头或纸笔回答:

  1. 两句话解释:ASGI 服务器和 ASGI 应用各干什么?scope / receive / send 分别像什么?
  2. 一点本质差异:相对 Flask/WSGI,ASGI 把「一次调用返回完整响应」改成了什么?这为什么能支撑 WebSocket / 流式?
  3. 画出 4~6 步:从客户端发起到收到 JSON,中间经过哪些角色?(对照 §4 自检;加分:标出中间件前半段/后半段)

加分题:多开 4 个 uvicorn worker 时,lifespan 里的「全局连接池」会建几份?为什么?


8. 闪卡候选(间隔重复)

正面 背面
ASGI 应用 callable 三参数? scope, receive, send
HTTP 下一次请求对应几次应用调用? 通常一次(一个 http scope)
lifespan 和单次请求谁更「少跑」? lifespan:每 worker 启停;请求:每次
FastAPI 底下主要两块库? Starlette(ASGI 工具)+ Pydantic(数据)
uvicorn main:appapp 是什么? 模块 main 中的 ASGI 应用对象

小结

  • 心智模型:服务器兑现字节 ↔ 事件;应用兑现 scope/receive/send;FastAPI 是后者上的生产力层。
  • 两层时钟:lifespan(进程)请求(每次) 不要混用。
  • 单次请求主链:中间件 → 路由 → Depends → 校验 → 端点 → Response → 中间件 → send(全图见 §4)。
  • 下一篇 02 路由与数据模型:Path / Query / Body / 响应模型六组块。
-------------本文结束感谢您的阅读-------------