FastAPI-02补.HTTP请求方法对比手册

本系列:00 导读 · 01补 网络基础 · 01 心智模型 · 02 路由与数据模型 · 02补 HTTP 方法对比(本文) · 02补2 状态码共识 · 03 依赖注入与分层 · 04 中间件异常日志 · 05 异步后台与流式 · 06 鉴权与安全 · 07 测试与项目骨架 · 08 实战 HTTP↔MCP

行文:T3 辨析篇(工具书体) | 本篇方法:对比 + 费曼 | 辅助:组块化、主动回忆

定位01补 讲「HTTP 是请求–响应」;02 讲 Path/Query/Body 怎么写。本文专讲 Method(方法):为什么不止 GET/POST、各自语义从哪来、场景与坑。成功/失败如何用数字表达见 02补2 状态码

规范依据:以 RFC 9110(HTTP 语义)为主;REST 风格是约定而非协议强制。社区写 API 时优先遵守「安全 / 幂等」语义,再谈资源路径怎么切。


0. 一张总表(先建立坐标)

图 A HTTP 方法地图(科普漫画)

方法 一句话意图 安全 幂等 常可缓存 典型成功码 FastAPI 装饰器
GET 取资源表示 ✓(常) 200 @app.get
HEAD 只要头、不要正文 ✓(同 GET) 200 @app.head / 自动
OPTIONS 问服务器允许什么 200 / 204 @app.options / CORS
POST 处理提交;常=创建子资源 201 / 200 / 202 @app.post
PUT 用请求体整体替换目标资源 200 / 201 / 204 @app.put
PATCH 部分修改目标资源 有条件† 200 / 204 @app.patch
DELETE 删除目标资源 200 / 204 / 202 @app.delete
TRACE 回显请求(诊断) 200 一般禁用
CONNECT 隧道(代理/HTTPS) 应用层几乎不用

† PATCH 是否幂等取决于你怎么定义补丁;JSON Merge Patch 常可做成幂等,JSON Patch 操作序列则不一定。

段末注释安全(safe)指语义上不应改服务器状态;幂等(idempotent)指同一请求故意重复多次,效果与成功一次等价。后文沿用。


1. 产生背景:方法从哪来、解决什么问题

1.1 简史对照

阶段 有什么方法 要解决的问题
HTTP/0.9 实质只有取文档 取静态页
HTTP/1.0 GET / HEAD / POST 表单提交、上传;区分「取」与「交」
HTTP/1.1 + PUT / DELETE / OPTIONS / TRACE / CONNECT 可写的分布式超媒体、代理、诊断
后来扩展 PATCH(RFC 5789)等 「改一部分」不必整资源替换

浏览器时代长期只用 GET(链接触发)POST(表单),导致很多人误以为「API 只有这两种」。现代 API(含 FastAPI)按 资源语义 选用方法,才能让缓存、重试、网关、OpenAPI 文档有一致预期。

1.2 两个正交属性:安全 × 幂等

图 B 安全 vs 幂等(科普漫画)

安全 非安全
幂等 GET、HEAD、OPTIONS PUT、DELETE(及多数「写同一最终态」的设计)
非幂等 (规范上安全方法应幂等) POST(典型);某些 PATCH

为什么网关/客户端在乎这两属性?

  • 安全方法可被预取、爬虫、缓存更激进地使用;用 GET 做「删号」「扣款」会酿灾。
  • 幂等方法在超时后可安全重试;非幂等重试可能双下单——需幂等键或改用 PUT。

1.3 费曼一句

方法不是「URL 后面的花活」,而是对服务器的动词合同:我这次是来「看」、来「交一份新活」、还是「把这个位子换成我说的样子」。合同错了,中间所有代理、缓存、重试逻辑都会按错误假设行动。


2. 方法分论:背景 · 场景 · 用法 · 局限

2.1 GET — 获取表示

维度 说明
背景 最早、最核心;链接、书签、搜索引擎都默认「点一下 = GET」
场景 查详情、列表、导出只读视图、健康检查
语义 安全、幂等;响应常可缓存(看 Cache-Control
局限 ① URL+Query 有长度限制(代理/浏览器);② 规范上不应依赖请求体(部分客户端丢 body);③ 敏感参数进 Query 易进日志/Referer
反模式 GET /orders/1/cancel 取消订单;GET /pay?amount= 扣款
1
2
3
4
5
6
7
8
from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = Query(default=None, max_length=50)):
return {"item_id": item_id, "q": q}

2.2 HEAD — 只要元数据

维度 说明
背景 与 GET 同语义,但响应无 body,省带宽
场景 探活文件是否存在、拿 Content-Length/ETag、CDN 校验
用法 FastAPI/Starlette 常对已注册的 GET 自动支持 HEAD;也可显式 @app.head
局限 服务端仍可能做完整计算再丢弃 body——「省」的是传输,不一定省算力;实现必须与 GET 头信息一致

2.3 POST — 提交处理 / 创建

维度 说明
背景 表单与「非幂等动作」容器;REST 里常映射「在集合下创建成员」
场景 创建资源、/search 复杂查询(body 很大)、触发非幂等动作(下单、发送邮件)、批量导入
语义 非安全、非幂等;重复提交可能产生多个资源
成功码 创建资源多用 201 + Location;异步受理用 202;动作结果直接返回可用 200
局限 超时重试易双写 → 需要 Idempotency-Key 或业务去重;缓存不友好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from fastapi import FastAPI, status
from pydantic import BaseModel

app = FastAPI()


class ItemCreate(BaseModel):
name: str
price: float


@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(body: ItemCreate):
new_id = 42 # 示例
return {"id": new_id, **body.model_dump()}

2.4 PUT — 整体替换(或按 URI 创建)

维度 说明
背景 客户端声明「这个 URI 的完整内容应当是……」;幂等写入
场景 上传完整配置文档、按客户端指定 ID 创建/覆盖、全量更新
语义 非安全、幂等:同一 PUT 连打 N 次 ≈ 一次成功后的状态
与 POST 对比 POST 常「服务器分配 ID」;PUT 常「客户端选定 URI」
局限 ① 大资源全量传,带宽差;② 并发易丢更新(需 ETag/If-Match);③ 「漏传字段」被当成「清空」——易踩坑
1
2
3
4
@app.put("/items/{item_id}")
async def put_item(item_id: int, body: ItemCreate):
# 用 body 整体覆盖 item_id 对应资源
return {"id": item_id, **body.model_dump()}

2.5 PATCH — 部分更新

维度 说明
背景 RFC 5789:避免为改一个字段而 PUT 整文档
场景 改邮箱、改状态机一步、JSON 补丁文档
语义 非安全;幂等性取决于补丁格式与实现
局限 ① 无单一标准 body(JSON Merge Patch / JSON Patch / 自定义);② OpenAPI/客户端对「部分字段」约定要文档写清;③ 与 PUT 混用导致团队语义分裂
1
2
3
4
5
6
7
8
9
10
11
12
from pydantic import BaseModel


class ItemPatch(BaseModel):
name: str | None = None
price: float | None = None


@app.patch("/items/{item_id}")
async def patch_item(item_id: int, body: ItemPatch):
updates = body.model_dump(exclude_unset=True) # 只应用客户端显式给出的字段
return {"id": item_id, "updated": updates}

图 C POST 新建 · PUT 整换 · PATCH 补丁(科普漫画)

2.6 DELETE — 删除

维度 说明
背景 与 PUT 对称的写操作;幂等:删一次与删多次,资源都应「不在了」
场景 删资源、取消订阅(若建模为删关系)
成功码 204 无正文常见;200 可带被删摘要;异步删 202
局限 ① 「软删除」仍改状态,语义要在文档声明;② 带 body 的 DELETE 兼容性差,过滤条件优先放 Query;③ 已删除再删应仍 200/204(幂等),不要改成 404 除非产品坚持「暴露不存在」
1
2
3
4
5
6
from fastapi import Response, status


@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
return Response(status_code=status.HTTP_204_NO_CONTENT)

2.7 OPTIONS — 能力发现 / CORS 预检

维度 说明
背景 询问目标资源允许哪些方法;浏览器 CORS 预检会发 OPTIONS
场景 跨域前端调 API;调试「这个路径允不允许 PUT」
用法 生产多由 CORSMiddleware 自动应答;业务很少手写 @app.options
局限 预检失败时表现为「前端神秘挂了」——根因常在方法/头不在 Allow/Access-Control-Allow-*

段末注释CORS(跨源资源共享,Cross-Origin Resource Sharing)是浏览器限制跨站读响应的机制;预检(preflight)用 OPTIONS 问清服务器是否允许本次跨域请求。

2.8 TRACE / CONNECT — 知道即可

方法 用途 API 服务建议
TRACE 沿路径回显,排障 关闭(信息泄露、XST 风险)
CONNECT 代理建立隧道 由代理/网关处理,业务 FastAPI 不暴露

3. 核心对比专题

3.1 POST vs PUT vs PATCH(写操作三角)

问题 POST PUT PATCH
目标 URI 通常指向? 集合或「动作处理器」 具体资源 具体资源
创建时谁决定 ID? 常服务器 常客户端(URI 已定) 一般不负责「首次创建」
更新粒度 不限(语义最宽) 全量替换 部分修改
重复提交 可能多个资源 同一最终态 视补丁而定
选谁的经验法则 「提交一份处理」或「集合下新建」 「这个地址的内容就是这份」 「只改若干字段」

易混例

需求 更合适 别写成
新建订单,ID 服务器生成 POST /orders PUT /orders(无明确 ID)
客户端上传 config/v1 整文件 PUT /configs/v1 反复 POST /configs
只改用户手机号 PATCH /users/1 PUT 却漏传其它字段导致清空
「支付」非幂等动作 POST /payments + 幂等键 GET /pay

3.2 GET vs POST(只读查询的灰色地带)

条件 倾向 GET 倾向 POST
参数短、可放 Query
参数极长/结构深 ✓(如复杂搜索 DSL)
要被缓存、可分享链接
查询本身有副作用(写审计且不可接受重复) 谨慎 用 POST,或 GET+异步审计解耦

行业常见妥协:POST /search 表示「只读但 body 很大」——偏离严格 REST,但务实;须在文档标明无副作用,避免网关按 POST 禁缓存时误伤性能预期。

3.3 安全方法里塞写操作(历史包袱)

部分旧站点用 GET /delete?id= 只因「<a href> 只能 GET」。在 API 中这是明确错误:爬虫、预取、日志回放都可能触发删除。


4. FastAPI / ASGI 层落地注意

4.1 装饰器与 OpenAPI

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
from fastapi import APIRouter, FastAPI

app = FastAPI()
router = APIRouter(prefix="/items", tags=["items"])


@router.get("/{item_id}")
async def get_one(item_id: int): ...


@router.post("")
async def create(...): ...


@router.put("/{item_id}")
async def replace(...): ...


@router.patch("/{item_id}")
async def update(...): ...


@router.delete("/{item_id}")
async def remove(...): ...


app.include_router(router)
  • 同一路径可注册不同方法;OpenAPI 会分开展示。
  • 未实现的方法由框架返回 405 Method Not Allowed(并常带 Allow 头)。

4.2 Body 与方法的搭配习惯

方法 Body 说明
GET / HEAD 不建议 部分中间件/客户端忽略
POST / PUT / PATCH 常见 JSON / 表单 / 文件
DELETE 尽量不用 兼容性差
OPTIONS 通常无业务 body 预检由中间件处理

路径与 Query/Body 组块细节见 02

4.3 幂等与重试(和异步篇的交界)

客户端超时后重试 PUT/DELETE 通常安全;重试 POST 前需要:

  1. 业务幂等键(头如 Idempotency-Key),或
  2. 把「创建」改成可 PUT 的「客户端指定 URI」,或
  3. 先查后建的条件 API

异步任务受理见 05 的 202 + BackgroundTasks / 队列边界。


5. 局限性总览(按层)

局限 启示
协议语义 方法不传输「业务动词」细节 复杂动作用 POST /resources/{id}/actions/... 或领域路径,并写清副作用
浏览器 表单只好发 GET/POST 前端 API 调用用 fetch/axios 才能发 PUT/PATCH/DELETE
缓存/CDN 默认主要信任 GET/HEAD 误用 GET 写数据会被缓存成事故
代理/网关 可能剥离冷门方法或 body 上线前用真实链路测 DELETE/PATCH
团队约定 PUT/PATCH 混用 项目 README 钉死一种更新策略
安全 TRACE、错误的 CORS OPTIONS TRACE 关;CORS 白名单收敛

6. 踩坑对照表(T3)

# 现象 根因 修法
1 爬虫误删数据 删除做成 GET 改 DELETE + 鉴权
2 超时后双订单 POST 无幂等键被重试 幂等键或改 PUT
3 PATCH 后字段变 null 模型默认值覆盖「未传字段」 exclude_unset=True
4 PUT 「更新」清掉其它列 把部分字段当全量 改 PATCH,或 PUT 要求完整文档
5 浏览器控制台 CORS 报错 OPTIONS 预检失败 CORSMiddleware 允许方法/头
6 GET 带超长 JSON body 偶发失败 中间件丢 body / 长度墙 改 POST /search
7 删除返回 404 导致重试警报 非幂等风格 已删仍 204
8 OpenAPI 试出来 405 路径有、方法未注册 补装饰器或改客户端方法

7. 选型决策树(实操)

1
2
3
4
5
6
7
8
9
要读数据且无副作用?
├─ 是 → GET(参数过大?→ POST /search 并文档声明只读)
└─ 否 → 要改服务器状态
├─ 删除资源 → DELETE
├─ 新建(服务器分配 ID)→ POST 集合
├─ 指定 URI 的完整内容 → PUT
└─ 只改部分字段 → PATCH
复杂非 CRUD 动作(支付、发送)→ POST 到动作型路径 + 考虑幂等键
跨域浏览器调用 → 确保 OPTIONS/CORS 放行所用方法

8. 合书自测

  1. 用一句话区分 安全幂等;举一个「非安全但幂等」的方法。
  2. 为什么「取消订单」不应做成 GET?会触发哪些现实风险?
  3. 同一资源「改邮箱」选 PUT 还是 PATCH?各有什么代价?
  4. POST 创建超时后客户端重试,如何避免双资源?列出两种方案。
  5. OPTIONS 在浏览器里最常见的用途是什么?

9. 闪卡候选

正面 背面
GET 是否幂等/安全? 都是
POST 是否幂等? 通常否
PUT 核心语义? 用 body 替换目标 URI 的完整表示
PATCH 来自哪个需求? 部分更新,免全量传输
DELETE 再删一次规范期望? 仍成功(幂等),资源保持不存在
405 含义? 路径认识但方法不允许
浏览器预检用何方法? OPTIONS

小结

  • 方法是 HTTP 语义合同:安全/幂等决定缓存与重试能不能「瞎帮忙」。
  • CRUD 速记:GET 读、POST 建/动作、PUT 整换、PATCH 补丁、DELETE 删;OPTIONS 管能力与 CORS。
  • FastAPI 装饰器与 OpenAPI 只是把合同暴露出来——选错方法,框架救不了业务语义
  • 参数怎么进 Path/Query/Body → 02;TCP/HTTP 会话心智 → 01补成功/失败怎么用状态码表达02补2
-------------本文结束感谢您的阅读-------------