行文: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 | [客户端] --TCP/HTTP字节--> [ASGI 服务器] <--约定--> [ASGI 应用] |
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 | async def app(scope, receive, send): |
三个参数就是「协议合同」的全部:
| 参数 | 是什么 | 类比 |
|---|---|---|
scope |
描述这次连接的字典(类型、路径、头、方法等) | 快递单上的地址与类型 |
receive |
异步可调用:从服务器取事件(如请求体块) | 打开包裹取货 |
send |
异步可调用:向服务器发事件(状态码、头、正文) | 回寄回执与货物 |
HTTP 下:一次请求 ≈ 一次对 app(...) 的调用,scope["type"] == "http"。
WebSocket 下:连接存活期间共用一个 scope,事件持续进出。
另有 lifespan 类型:不服务用户请求,只负责进程内「启动 / 关闭」。
2.3 框架在合同之上堆的东西
FastAPI 并不替代 ASGI;它实现上述 callable,并在内部叠:
- Starlette:路由、中间件、Request/Response、WebSocket 等 ASGI 工具层。
- Pydantic:把 query/body 等解析成类型安全的数据模型(细节见 Pydantic 笔记)。
- 你的路由函数:业务逻辑;框架负责把
scope/receive/send翻译成函数参数与返回值。
第一性原理结论:你会写的每个 @app.get,最终都要变成对 send 的若干次调用。
3. 白话重建(费曼稿)
把整条链路讲给「只懂寄快递」的人听:
- 客户往某个门牌(主机:端口)扔一个信封(HTTP 请求)。
- 门房(uvicorn)拆开信封,填一张派送单(scope):送去哪条路径、什么方法、有哪些贴纸(headers)。正文可能太大,先不一次性塞进派送单,后面用 receive 一块块取。
- 门房把派送单交给公司前台(FastAPI 应用)。前台按路径找柜员(路由),柜员可能先找后勤(依赖注入,见 03),再办事。
- 柜员办完,前台把结果整理成「回执事件」:先说状态与贴纸,再一段段正文,通过 send 交给门房。
- 门房写回套接字,客户收到响应。
和 Flask 差在哪(只记一点):Flask 走 WSGI——一次调用通常是「同步函数拿完请求、返回完响应」;ASGI 把交互拆成 事件流,天然能挂异步 IO、WebSocket、流式响应,也才能和「同一进程里的其他 ASGI 应用」(例如 MCP 的 HTTP 传输)组合。
进程级另一条线(lifespan):店开门时备货(连库、加载模型),打烊时收摊。这条线不是每个请求都跑,而是每个 worker 进程 / 事件循环在开始接客前、停业时各走一轮。多 worker 时,每个进程各自备货——所以「全局只连一次」的直觉在多进程下是错的。
4. 结构图(双重编码)· 全链路执行流程
先建立「服务器 ↔ 应用」粗图,再展开 FastAPI 进程内全部可能组件的任务流转。与 04 §0 同源,便于中间件/异常篇对照。
4.1 粗粒度: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 | flowchart TD |
旁路:中间件不调 call_next 则短路;WebSocket 为长连接事件流;mount 把前缀交给子 ASGI 应用;多 worker 则每进程一套 lifespan。中间件洋葱与异常落点的排错见 04。
段末注释:中间件洋葱:请求从外层进入、响应从内层传出;后注册的
add_middleware通常更靠外。
5. 最小代码:只证明原理
5.1 纯 ASGI:不用 FastAPI 也能应答
1 | # plain_asgi.py —— 证明合同长什么样 |
合上眼睛应能复述:没有路由装饰器,只有 send 两次事件(先 start,再 body)。
5.2 FastAPI:同一合同上的薄封装
1 | # main.py |
要点:
FastAPI(...)实例就是 ASGI 应用(可被 uvicorn 加载,也可被别的 ASGI 应用 mount)。lifespan推荐写法是 async context manager 交给FastAPI(lifespan=...);yield前为启动,后为关闭。官方已不推荐再依赖旧的@app.on_event("startup")风格作为新代码默认。- 路由函数返回
dict时,框架负责序列化并最终变成对send的调用——你日常不必手写事件字典。
5.3 组件位置速查
1 | uvicorn → 加载 main:app |
完整图见 §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. 合书自测(费曼三问)
遮住上文,口头或纸笔回答:
- 两句话解释:ASGI 服务器和 ASGI 应用各干什么?
scope / receive / send分别像什么? - 一点本质差异:相对 Flask/WSGI,ASGI 把「一次调用返回完整响应」改成了什么?这为什么能支撑 WebSocket / 流式?
- 画出 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:app 里 app 是什么? |
模块 main 中的 ASGI 应用对象 |
小结
- 心智模型:服务器兑现字节 ↔ 事件;应用兑现
scope/receive/send;FastAPI 是后者上的生产力层。 - 两层时钟:lifespan(进程) 与 请求(每次) 不要混用。
- 单次请求主链:中间件 → 路由 → Depends → 校验 → 端点 → Response → 中间件 → send(全图见 §4)。
- 下一篇 02 路由与数据模型:Path / Query / Body / 响应模型六组块。