本文目录
浏览器地址栏敲下回车,到页面或 JSON 出现在屏幕上,中间经过好几层协议与进程。写后端时不必背全栈细节,但得清楚 FastAPI 管哪一段、Uvicorn 管哪一段——否则调试时会出现「路由明明写了却 404」「本地能跑线上 502」却找不到该查谁。
Web 框架在栈里的位置
可以把一次 HTTP 调用拆成三层直觉:
| 层级 | 典型角色 | 你写的代码在哪 |
|---|---|---|
| 传输与协议 | TCP、HTTP 解析 | 一般不写 |
| 应用服务器 | Uvicorn、Hypercorn 等 ASGI 宿主 | 配置端口、worker 数 |
| 应用框架 | FastAPI | 路由、校验、业务编排 |
FastAPI 不负责监听 80 端口、也不自己解析原始 HTTP 字节流;它接收 ASGI 层已经整理好的「作用域 + 消息流」,在 Python 里把路径映射到函数,再把返回值编码成响应。你关心的是:给定 method + path,哪个函数跑、返回什么结构。
ASGI 与 Uvicorn:谁把请求送进来
Python 同步时代常见 WSGI(如 Gunicorn + Flask);FastAPI 基于 ASGI,天然支持 async def 与 WebSocket、SSE 等长连接场景。ASGI 应用是一个可调用对象,签名大致是 (scope, receive, send) -> Awaitable[None]——框架帮你封装成 @app.get("/items") 这种写法。
Uvicorn 是常用的 ASGI 服务器:它绑定地址、读 socket、把 HTTP 转成 ASGI 事件,再调用你的 FastAPI 实例。本地开发常见:
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000app.main:app 表示从模块 app.main 导入名为 app 的 ASGI 应用对象。--reload 只应在开发用;生产环境由进程管理器拉起多个 worker,并关掉热重载。
理解分工:Uvicorn 管「进程怎么活、连接怎么接」;FastAPI 管「接到请求之后逻辑怎么走」。
生产里还常在前再加一层反向代理(Nginx、Caddy、云负载均衡):它们管 TLS 终止、限流、静态文件,再把请求转发到 Uvicorn 监听的 127.0.0.1:8000。排查 502 时要分清是代理够不到后端,还是 Uvicorn 进程挂了;排查 404 则多半在 FastAPI 路由 表没匹配上。养成「从外到内」看链路的习惯,比死记命令有用。
ASGI 与旧 WSGI 的关键差别在并发模型:WSGI 一次处理一个请求直到结束;ASGI 可在等待 I/O 时切换协程。对 CPU 占满的长计算,两种模型都不会 magically 变快——但若接口主要是等数据库、等下游 HTTP,async 路由 能少占线程。
路由:从 URL 到 Python 函数
路由 是框架最核心的映射表:HTTP 方法 + 路径模式 → 处理函数(同步或异步)。FastAPI 用装饰器或 APIRouter 注册:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ItemCreate(BaseModel):
name: str
price: float
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok"}
@app.post("/items", status_code=201)
async def create_item(body: ItemCreate) -> ItemCreate:
# 此处仅演示请求体校验与响应模型,持久化留到后续篇章
return body几点机制:
- 路径
/items与@app.post绑定,GET 同路径不会命中此函数。 body: ItemCreate触发 Pydantic 校验:缺字段或类型不对时,框架直接返回 422,不必手写if not name。- 返回类型注解参与响应 schema 生成,也约束序列化形状。
路由 函数应尽量薄——只负责 HTTP 语义(状态码、Header、依赖注入入口);复杂规则放到 service 层(系列后面会展开)。
请求与响应:框架替你做了什么
一次 POST /items 进入 FastAPI 后,大致顺序是:
- ASGI 层把 body 流交给框架。
- 路由匹配命中
create_item。 - 依赖项(若有
Depends)先解析。 - Pydantic 把 JSON 转成
ItemCreate实例。 - 函数执行,返回值经响应模型编码为 JSON。
- Uvicorn 把响应写回客户端。
查询参数、路径参数、Header、Cookie 都有对应声明方式:
from fastapi import FastAPI, Query, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(..., ge=1),
q: str | None = Query(default=None, max_length=50),
) -> dict[str, int | str | None]:
return {"item_id": item_id, "q": q}item_id 从路径段解析并校验 >= 1;q 来自 query string,可选。类型不对时同样是 422——这是 FastAPI 相对「手写 Flask 路由 + 手动 json.loads」最省心的部分之一。
OpenAPI:文档从类型注解长出来
OpenAPI(以前叫 Swagger)描述 API 的路径、参数、请求体、响应 schema。FastAPI 的独特之处:只要你用了类型注解和 Pydantic 模型,OpenAPI 规范会在应用启动时自动生成,无需维护第二份 YAML。
启动后访问:
/docs—— Swagger UI 交互文档/redoc—— ReDoc 风格只读文档/openapi.json—— 原始 JSON schema
这对前后端协作、联调、生成客户端 SDK 都很有用。改了一个 response_model,文档跟着变,减少「代码已改文档还在说旧字段」的 drift。
可以在创建应用时定制元数据:
app = FastAPI(
title="Shop API",
version="0.1.0",
description="示例:商品与健康检查",
)生产环境若不想暴露 /docs,可通过 docs_url=None 关闭,或加网关层鉴权——OpenAPI 本身仍是框架能力,只是是否对外可见是部署策略。
OpenAPI 里的 components.schemas 来自 Pydantic 模型名;改类名会改 schema 标题,联调时注意客户端生成代码是否重新拉取。给 路由 加 tags、summary、description 参数,文档可读性会明显好于只有路径字符串:
@app.get("/items/{item_id}", tags=["items"], summary="按 ID 查询商品")
async def read_item_detail(item_id: int) -> dict[str, int]:
return {"item_id": item_id}团队可以把 /openapi.json 纳入 CI:对比本次 PR 与 main 的 diff,防止无意删除字段或改类型。契约驱动开发不必上全套工具,但「文档与代码同源」是 FastAPI 选型的主要收益之一。
同步路由与 async 路由
FastAPI 同时支持 def 与 async def 处理函数。async def 跑在 ASGI 事件循环上;普通 def 会在线程池里执行,避免阻塞 loop。选型不必教条:函数内部全是 async 数据库驱动,用 async def;内部调用旧式同步 ORM,可用 def 让框架帮你丢线程池。
@app.get("/sync-ok")
def sync_handler() -> dict[str, str]:
return {"mode": "threadpool"}
@app.get("/async-ok")
async def async_handler() -> dict[str, str]:
return {"mode": "coroutine"}混用时关注监控:线程池耗尽的表现是延迟整体抬升,而不是单个 路由 报错。后续系列在数据库、依赖注入篇会回到这一分工。
从 curl 到 handler 的排查清单
本地验证最小闭环:
curl -s http://127.0.0.1:8000/health
curl -s -X POST http://127.0.0.1:8000/items \
-H 'Content-Type: application/json' \
-d '{"name":"tea","price":12.5}'若连接被拒绝,查 Uvicorn 是否监听、端口是否被占用。若返回 422,对照 OpenAPI 文档看请求体 schema。若返回 404,查 method 是否匹配、前缀是否被 include_router 多加一段。若 500,看 Uvicorn 日志栈 trace——通常是 路由 函数体内未捕获异常,而不是框架本身崩溃。
和「更大框架」怎么相处(不是对比表)
有人从 Django 来,习惯「电池Included」:ORM、Admin、模板一体。FastAPI 刻意保持轻:专注 HTTP API 层,数据库、任务队列、模板各选各的。有人从 Flask 来,习惯微内核自己拼;FastAPI 则把校验、文档、异步约定写进默认路径,换更少样板代码。
不必纠结「谁更好」——问:当前服务是不是以 JSON API 为主、要不要 async、团队是否愿意用类型注解换文档与校验。若是,FastAPI + Uvicorn 是合理默认;若还要内置 Admin 后台和一体化 ORM,可能 Django 更省事。本篇只建立位置感,不展开全功能对比。
选型时还可以看生态:Starlette 提供 路由 与中间件底座,Pydantic 做校验,Uvicorn 做 ASGI 宿主——四者职责清晰,换其中一层不必重写全部。学习路径上,先能在本地跑通 Uvicorn、能在 /docs 里看到 OpenAPI、能写两个 路由 返回 JSON,再往下加数据库与认证,顺序最稳。
常见误解
误解一:「FastAPI 等于 Uvicorn。」 前者是框架,后者是服务器;可以换 Hypercorn、Daphne 等 ASGI 宿主。
误解二:「写了 @app.get 就会自动注册到所有 worker。」 每个 worker 进程各自 import 应用;全局可变状态不在 worker 间共享,除非外接 Redis 等。
误解三:「OpenAPI 只是装饰品。」 它是契约;移动端、测试生成器、API 网关规则都可从 /openapi.json 派生。
误解五:「asyncio.run 和 loop 是两回事。」 谈 ASGI 宿主时,Uvicorn 持有一个长期运行的 loop;概念与 asyncio 相同,只是生命周期更长。
小结
FastAPI 站在 ASGI 应用层:Uvicorn 接连接,框架做 路由 匹配、校验与响应编码,并自动生成 OpenAPI 文档。把一次请求想成「代理 → ASGI 宿主 → 框架 路由 → 你的函数 → JSON 响应」,排错就有地图。入门时优先熟练 路由 声明与 /docs 对照,再叠加数据库与认证。下一篇从「一个 main.py 撑不住」说起,看 包 与 模块 怎么拆才方便改。