1. 本课定位:是什么、为何重要
上一阶段你已经会当「客户端」:用 requests / httpx 去调别人的 HTTP 接口,会看状态码、会设超时、会读 JSON。那套能力解决的是「我去找别人拿数据」。可是真实后端岗位的核心产出,往往是反过来的——别人(浏览器、App、别的服务)来找你拿数据。如果只会写「跑完就退出」的脚本,就没法把业务能力变成可联调、可部署的接口。
所以本课先完成角色切换:你要启动一个常驻进程,监听本机端口,接收请求,返回 JSON。技术栈选 FastAPI + Uvicorn:前者用函数和类型注解声明路由,后者负责真正监听端口并调用你的应用。学完你应能本地起服务、用浏览器 /docs 或 curl 验证,并为后续书签 API 主线打底。
阶段 B 你是客户端;本阶段起你是服务端。
| 概念 | 一句话 |
|---|---|
| Web 服务 / API | 用 HTTP 把业务能力暴露给浏览器、App、其它程序 |
| FastAPI | 用 Python 函数 + 类型注解声明路由;自动校验与 OpenAPI 文档 |
| Uvicorn | ASGI 服务器:监听端口,把网络字节流变成「调用你的 app」 |
| ASGI | 异步网关接口约定;知道「炉灶与菜谱之间有标准」即可 |
为何重要:不会起服务,就无法把路由、模型、数据库、鉴权串成产品。
对比已学:
| 已学(阶段 B) | 本课(阶段 C) |
|---|---|
| 发出请求、解析响应 | 接收请求、构造响应 |
| 关心对方 URL/状态码 | 自己决定路径、状态码、Body |
| 超时、重试在客户端 | 进程要常驻、端口要监听 |
| 调外部演示站 | 别人(或未来的你)来调你 |
生活类比:
| 角色 | 像什么 |
|---|---|
| 客户端脚本 | 你去餐厅点菜、等上菜 |
| Web 服务 | 你开餐厅:听点单、出菜、报状态 |
| FastAPI | 菜单与出菜流程(路由逻辑) |
| Uvicorn | 店面开门营业(进程监听) |
贯穿业务:书签 API(本课先健康检查与问候;后续课逐步加路由、模型、配置、落库)。
2. 本质:一次 HTTP 交换里你站哪边
很多人一听「写后端」就想到 Socket、多线程、协议细节,结果迟迟不敢动手。其实入门阶段你要抓住的本质很简单:一次 HTTP 交换里,你的进程站在服务器一侧——读方法/路径/头/体,写状态码/头/体。框架已经把监听和协议解析包好了,你主要写「路径对应哪个函数、返回什么 JSON」。
上一节把角色切换说清了,这一节用一张请求往返图和三层分工表,把「菜谱 / 炉灶 / 接口约定」钉牢。地图清楚后,最小应用代码才不会变成照抄咒语。
浏览器 / 脚本 / 前端 你的 Uvicorn + FastAPI | ---- GET /health ----> | | <--- 200 {"status":"ok"} || 层 | 像什么 | 本课角色 |
|---|---|---|
| FastAPI 应用 | 菜谱(路径与逻辑) | 你写的app |
| Uvicorn | 炉灶(跑起来) | 命令行启动 |
| ASGI | 炉灶与菜谱的接口约定 | 知道有即可 |
本质一句话:服务端 = 常驻进程 + 路由函数 + HTTP 响应。
3. 约束与常见坑
理解了「站在服务端」之后,必须立刻补边界:进程要一直跑、端口会冲突、本机地址不等于公网可达、改代码不生效通常是没热重载或改错文件。这些坑在课堂演示里几乎必现,先列出来能省半小时抓狂。
这一节用清单和对照表把红线画清。目的不是吓人,而是让你第一次
uvicorn失败时知道该查哪一类问题,而不是怀疑「FastAPI 是不是坏了」。
约束:
- 进程要一直跑着,关掉终端服务就停(与「跑完就退出」的脚本不同)。
- 端口只能被一个进程占用;冲突就换端口。
- 默认只给本机访问(
127.0.0.1)更安全;绑定0.0.0.0才对外网卡开放。 - 开发用
--reload,生产不要依赖热重载当部署方案。 - 返回的
dict会变成 JSON;不能直接返回任意不可序列化对象(裸datetime等需配置或转换)。
常见坑:
| 坑 | 现象 | 正确直觉 |
|---|---|---|
| 把 FastAPI 当「还要自己写 socket」 | 无从下手 | 框架已封装监听,你写路由函数即可 |
| 改代码不生效 | 仍是旧逻辑 | 加--reload,或确认改的是正在运行的文件 |
Address already in use | 起不来 | 换--port或关掉占用进程 |
| 只开了服务、不去请求 | 「没反应」 | 用浏览器/curl//docs主动访问 |
外网朋友访问不了本机127.0.0.1 | 连不上 | 本地开发本就如此;部署是后面的课 |
把main:app写成文件名乱猜 | 导入失败 | 模块路径 + 变量名app |
4. 最小应用:定义与创建
坑讲完,开始动手认最小闭环:创建
FastAPI实例、用装饰器挂 GET 路由、返回 dict。这三步对应「有应用、有路径、有响应」。你会看到装饰器在导入模块时就完成路由注册——不是「调用函数时才注册」。理解这一点,后面拆文件、
include_router时才不会疑惑「为什么 import 一下路由就生效了」。
fromfastapiimportFastAPI app=FastAPI(title="Bookmark API",version="0.1.0")@app.get("/")defroot():return{"message":"hello bookmark api"}@app.get("/health")defhealth():return{"status":"ok"}| 写法 | 含义 |
|---|---|
app = FastAPI(...) | 创建应用实例(Uvicorn 要加载它) |
@app.get("/health") | 注册GET/health |
return dict | 自动 JSON 序列化,默认状态码200 |
关键语义:装饰器在导入模块时完成路由注册。
预期输出形态(访问/health):
{"status":"ok"}5. 运行方式对照
写好
app还不等于服务在跑——还需要 Uvicorn 加载它。main:app这种写法初学者常读错:左边是模块,右边是变量名,中间冒号不是文件扩展名。这一节把开发命令、包路径写法、文档入口放进对照表,并说明
/docs、/redoc、/openapi.json各自干什么。你会发现:框架根据路由自动生成 OpenAPI,文档页只是它的可视化。
| 方式 | 命令直觉 | 何时用 |
|---|---|---|
| 开发 | uvicorn main:app --reload --host 127.0.0.1 --port 8000 | 本课默认 |
| 指定模块路径 | uvicorn app.main:app | 包结构项目 |
| 生产倾向 | 多进程/容器 + 无 reload(后续部署课) | 上线 |
main:app读作:模块main里的变量app。
| 文档入口 | 用途 |
|---|---|
/docs | Swagger UI,可点按钮试调 |
/redoc | 更偏阅读的文档页 |
/openapi.json | 机器可读契约(后续课展开) |
| 参数 | 含义 |
|---|---|
--reload | 代码变更自动重启(仅开发) |
--host 127.0.0.1 | 只监听本机回环 |
--port 8000 | 端口号;冲突则改 |
6. 小步示例:查询参数预览
严格的 Query 系统化在下一课,但本课若完全不碰「带参数的接口」,体验会偏干。这里用最小
/hello?name=让你看见:函数参数可以来自查询串,FastAPI 会帮你填。把它当作预告,不要求一次吃透校验与 422。重点是:服务端同样在「约定 URL 形状」,客户端拼
?name=alice就能对上。
@app.get("/hello")defhello(name:str="world"):return{"greeting":f"hello,{name}"}| 请求 | 响应要点 |
|---|---|
GET /hello | {"greeting":"hello, world"} |
GET /hello?name=alice | {"greeting":"hello, alice"} |
7. 落地场景:书签服务从哪起步
贯穿业务是书签 API,但第一课不必上 CRUD。先定「服务活着」和「能问候」两个最小能力,运维探活与前端联调都用得上。
这一节用表格把场景和接口直觉对齐,避免一上来就设计大而全的资源树。正确节奏是:先能起、能验、能改代码热更新,再加资源路径。
| 场景 | 最小接口直觉 |
|---|---|
| 运维探活 | GET /health→{"status":"ok"} |
| 给前端的问候 | GET /hello?name=alice |
| 书签服务雏形 | 本课先健康检查;第 41 课起加资源路径 |
| 自测文档 | 打开/docs,Try it out |
8. 修改前 / 修改后:客户端思维 vs 服务端思维
学过 requests 的人容易把「写服务」也想成「我再发一次请求」——方向反了。用对照表把思维拧过来:你不再拼对方 URL,而是定义自己的路径;不再解析对方 JSON,而是构造自己的 JSON。
这种对照会反复出现在后面几课:路径参数、请求体、依赖注入,都是在服务端兑现「对外约定」。
| 维度 | 修改前(客户端) | 修改后(服务端) |
|---|---|---|
| 主动方 | 你发起 | 别人发起,你响应 |
| 进程 | 短命脚本 | 常驻监听 |
| 成功标志 | 对方 200 | 你返回 200 与约定字段 |
| 调试入口 | 打日志看响应 | /docs+ curl 双验证 |
| 失败常见 | 超时、DNS | 端口占用、路由未注册 |
9. 环境与目录建议
工具链没摆好,后面所有课都会卡在「包找不到 / 命令不对」。这一节固定:虚拟环境、依赖版本下限、Windows 下如何跑
cat <<'EOF'类脚本。Windows 用户推荐 Cygwin 或 WSL 执行带 heredoc 的 bash 片段;PowerShell 也可手建文件,但课程示例统一按 bash 写法给出,便于复制。
| 项目 | 要求 |
|---|---|
| Python | 3.10+(推荐 3.12) |
| 包 | fastapi、uvicorn[standard] |
| Windows | 推荐 Cygwin / WSL跑cat <<'EOF'脚本;或 VS Code 终端手建文件 |
mkdir-p~/python-lab/src/day40cd~/python-lab/src/day40 python3-mvenv .venvsource.venv/bin/activate# Windows PowerShell: .\.venv\Scripts\Activate.ps1pipinstall'fastapi>=0.110''uvicorn[standard]>=0.27'10. 综合实践:完整可运行脚本
前面是分块概念,这一节一次落地:写入
main.py、启动 Uvicorn、另开终端验证。请完整跑通,不要只看不敲——「服务起来了」的体感建立不起来,后面路由课会虚。Windows 请在 Cygwin/WSL 中执行 heredoc;若必须用 PowerShell,可手动创建同名文件并粘贴 EOF 之间的内容。服务占用前台终端时,验证命令请开第二个终端。
mkdir-p~/python-lab/src/day40cd~/python-lab/src/day40# 已激活 venv 并安装依赖后:cat>main.py<<'EOF' from fastapi import FastAPI app = FastAPI(title="Day40 Bookmark API", version="0.1.0") @app.get("/") def root(): return {"message": "hello bookmark api", "course": 40} @app.get("/health") def health(): return {"status": "ok"} @app.get("/hello") def hello(name: str = "world"): return {"greeting": f"hello, {name}"} EOFuvicorn main:app--reload--host127.0.0.1--port8000另开终端验证:
curl-shttp://127.0.0.1:8000/healthcurl-s"http://127.0.0.1:8000/hello?name=alice"curl-shttp://127.0.0.1:8000/浏览器打开:http://127.0.0.1:8000/docs→ 选接口 →Try it out→Execute。
预期输出:
{"status":"ok"} {"greeting":"hello, alice"} {"message":"hello bookmark api","course":40}| 检查项 | 通过标准 |
|---|---|
| 健康检查 | Body 含"status":"ok" |
| 问候 | name进入 greeting |
| 文档 | /docs能看到三条 GET |
| 热重载 | 改message字符串后刷新仍更新(保存后等一秒) |
11. 常见问答
课堂高频问题集中在:FastAPI 和 Flask 差在哪、Uvicorn 能不能省略、为什么必须另开终端、
main:app报错怎么办。集中答疑,减少「概念都懂了但命令跑不通」的挫败感。
Q:FastAPI 和 Flask 入门差在哪?
A:FastAPI 强调类型注解、自动校验与 OpenAPI;本系列统一 FastAPI,不强制对比深度。
Q:能不用 Uvicorn 吗?
A:开发期几乎总要 ASGI 服务器;Uvicorn 是官方推荐默认之一。
Q:为什么 curl 要另开终端?
A:前台跑着的 Uvicorn 占住了当前 shell;不停服务就另开窗口发请求。
Q:Could not import module "main"?
A:当前目录是否有main.py;是否在项目目录执行;包结构时用app.main:app。
Q:返回中文乱码?
A:终端编码与 JSON 本身 UTF-8;浏览器/docs一般正常。确保源文件 UTF-8 保存。
12. 错误示范对照
专辟对照,把「假服务端」和「真最小服务」并排看。常见错误包括:写了函数却没装饰器、return 了不可序列化对象、host/port 乱绑导致自己都访问不了。
| 错误写法 | 问题 | 正确直觉 |
|---|---|---|
定义了def health()无装饰器 | 路由未注册 | @app.get("/health") |
return open("x") | 无法 JSON 化 | 返回 dict/list/模型 |
只print不 return | 客户端拿到 null/空 | 明确 return 响应体 |
| 绑定错误端口却 curl 8000 | 连接失败 | 命令与 curl 端口一致 |
# 修改前:有函数,无路由defhealth():return{"status":"ok"}# 修改后:@app.get("/health")defhealth():return{"status":"ok"}13. 自我检查清单
学完先别急着翻第 41 课。用清单打勾:角色切换、三层分工、最小代码、启动命令、curl 与 /docs 双验证。勾不上的回到对应小节五分钟,比往前赶更有效。
- 能用一句话说清客户端 vs 服务端
- 能说出 FastAPI 与 Uvicorn 各干什么
- 会写
GET /与GET /health - 会
uvicorn main:app --reload并成功访问 - 会用
/docsTry it out - 会用 curl 验证 JSON
- 知道端口占用时如何处理
14. 与后续课的衔接(地图)
本课只搭舞台。后面书签 API 会按「路径 → 请求体 → 依赖配置 → 数据库」加厚。知道地图,才不会觉得每课在换题材。
| 课 | 你将加上 |
|---|---|
| 41 | 路径/查询参数、状态码、APIRouter、内存书签读删 |
| 42 | Pydantic 模型、POST/PATCH Body |
| 43 | Depends、Settings、.env |
| 44 | SQLAlchemy + SQLite 持久化 CRUD |
15. 客户端调自己的服务(闭环)
阶段 B 你会用 requests 调别人;现在服务在本机,完全可以用同一套客户端能力调自己,形成「一端写服务、一端写调用」的闭环。这对以后写集成测试、健康检查脚本也很有用。
服务仍要用 Uvicorn 先跑着;下面脚本在另一个终端执行。超时建议带上,避免服务没起时一直挂起。
importrequests base="http://127.0.0.1:8000"r=requests.get(f"{base}/health",timeout=5)print(r.status_code,r.json())r2=requests.get(f"{base}/hello",params={"name":"lab"},timeout=5)print(r2.json())预期输出形态:
200 {'status': 'ok'} {'greeting': 'hello, lab'}| 方式 | 适合 |
|---|---|
浏览器/docs | 探索、演示 |
| curl | 终端快速验 |
| requests/httpx | 脚本化、后续测试 |
总结
到了收束的时候。若整课只能带走几句:角色已从「调别人」换成「被别人调」;FastAPI 写 app,Uvicorn 跑 app;dict 变 JSON;用 /docs 和 curl 双验证;进程常驻、端口唯一、本机开发先绑回环地址。
这些句子后面每课都会用到。先保证「能起、能调、能改」,再谈漂亮架构。
- 服务端 = 常驻进程 + 路由函数 + HTTP 响应。
- FastAPI 声明路由与文档;Uvicorn 负责监听与调用 app。
main:app= 模块里的应用实例;开发加--reload。return dict→ JSON,默认 200;用/docs与 curl 验证。- 端口冲突换端口;本地
127.0.0.1不等于公网可达。 - 贯穿业务从书签 API 健康检查起步,后续课逐步加厚。
小练笔
练习用来自测,不要求一次全对,更不要先翻答案。题型覆盖选择、判断、简答与可选实践:建议你真的改一行返回字段,保存后看热重载是否生效。
做题时优先用自己的话解释「为什么」。卡很久的题,回到「三层分工」和「运行方式」两节通常就能想通。
题 1
Uvicorn 的主要职责?
A. 写 SQL
B. ASGI 服务器监听并调用 app
C. 画前端页面
题 2
uvicorn main:app里的app指什么?
题 3
判断:浏览器打开/docs能试调接口,说明 OpenAPI 文档由框架根据路由自动生成。
题 4
端口被占用时,优先做什么?
题 5
用「方法、URL、期望状态码、请求体、响应体」描述一次对/health的成功访问。
题 6
判断:开发环境绑定127.0.0.1后,公网用户一定能访问你的服务。
题 7
为什么服务启动后当前终端不能再直接敲很多交互命令?
题 8
GET /hello?name=bob若路由是def hello(name: str = "world"),响应 greeting 应是什么?
题 9(可选实践)
给/增加字段"domain": "bookmark",保存后不手动重启(依赖--reload),curl 验证新字段出现。
题 10
一句话区分 FastAPI 与 Uvicorn。
小练笔参考答案
参考答案在下面。请先自己做完再看。答案只给标准方向,简答题意思对即可。
若你的表述和答案不同但道理成立,可以算对。真正要修正的,是把客户端/服务端角色搞反,或以为框架还要你手写 socket 的理解。
题 1
B
题 2
main模块中FastAPI()创建的应用实例变量。
题 3
对
题 4
换端口或结束占用该端口的进程。
题 5
示例:GEThttp://127.0.0.1:8000/health;无 Body;期望 200;Body{"status":"ok"}。
题 6
错
题 7
Uvicorn 前台占用该终端;验证请另开终端,或后台运行(开发期另开更直观)。
题 8
hello, bob(即{"greeting":"hello, bob"})。
题 9
以你机器为准;保存后 curl/应出现"domain":"bookmark"。
题 10
FastAPI 写应用与路由;Uvicorn 跑 ASGI 应用并监听端口。(合理即可)