Skip to content
api2026-06-306 分钟阅读

写后端接口时,新人最常纠结的问题之一就是:这个接口该用 GET 还是 POST?有些人图省事,所有接口都走 POST,反正能用。但这样做会牺牲缓存、书签、CDN 加速,还会让 API 语义混乱。GET 和 POST 不是随便选的,它们的差异写在 HTTP 规范里,浏览器、代理、CDN 都按这些差异工作。搞清楚并不难,但收益很大。想实际观察 GET 和 POST 的差别?用我们的 API 测试工具 发几个请求,对比看看响应。

核心语义差异

GET 和 POST 的根本区别在于语义:GET 表示"读取",POST 表示"提交"。

| 维度 | GET | POST | |------|-----|------| | 主要用途 | 获取资源 | 创建或提交数据 | | 幂等性 | 幂等(多次执行结果相同) | 非幂等 | | 安全性 | 安全(不应改变服务器状态) | 不安全 | | 可缓存 | 是(浏览器、CDN、代理都会缓存) | 否 | | 可书签化 | 是(参数在 URL 中) | 否 | | 浏览器历史 | URL 含参数会被记录 | 不会 | | 参数位置 | URL 查询字符串 | 请求体 | | 长度限制 | 浏览器约 2k-8k 字符 | 理论上无限制 | | 编码类型 | application/x-www-form-urlencoded | 多种(JSON、multipart、form) |

"安全"和"幂等"是 HTTP 规范的术语,不是口语化的"安全"。安全方法指的是不修改服务器状态,幂等指的是重复执行不会产生额外效果。

GET 的细节

GET 设计目标是只读。参数放在 URL 查询字符串里,例如:

GET /api/users?role=admin&page=2 HTTP/1.1
Host: example.com

GET 请求有几个关键特性:

第一,可以被缓存。浏览器、CDN、反向代理都会按 Cache-Control 头决定是否缓存 GET 响应。重复访问同一 URL 时可能直接命中缓存,省掉一次网络往返。第二,可以被书签保存。URL 完整记录了请求参数,复制粘贴就能复现。第三,会出现在访问日志里。所有参数都明文写在 URL 中,nginx、Apache 的 access log 都会记录。第四,有长度限制。不同浏览器限制不同:Internet Explorer 约 2083 字符,Chrome 约 2 万,但 CDN 和代理通常截断在 8k 左右。第五,不应该有副作用。一个 GET 请求即便发 100 次,服务器状态都应该一致。

POST 的细节

POST 设计目标是写入。参数放在请求体中,可以是任意格式:

POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json

{"name": "Alice", "email": "user@example.com"}

POST 请求的关键特性:

第一,不会被缓存。每次请求都会到达服务器。第二,不会出现在 URL 中。参数在请求体里,浏览器历史、Referer 头都不会泄露。第三,长度无明确限制。受服务器配置约束(如 nginx 的 client_max_body_size),但不像是 GET 那种浏览器层限制。第四,支持任意 Content-Type。JSON、multipart/form-data、application/x-www-form-urlencoded、二进制流都行。第五,非幂等。同一请求发两次,会创建两个用户、两条订单、两笔扣款。

一个常见的误区:GET 可以带请求体吗

技术上 RFC 7231 允许 GET 带请求体,但强烈不推荐。原因如下:

很多代理和 CDN 会直接丢弃带 body 的 GET 请求。Elasticsearch 早期版本用 GET + body 做复杂查询,后来不得不加 POST 作为兼容方案。一些客户端库(如 fetch、XMLHttpRequest 在某些浏览器版本)会自动移除 GET 的 body。

正确做法是把复杂查询参数放 URL 里(注意长度限制),或者改用 POST。如果查询条件实在太多,POST + /api/users/query 端点是更干净的设计。

curl 示例对比

实际看一下 GET 和 POST 在 curl 下的差异:

# GET 请求,参数在 URL
curl -X GET "https://api.example.com/users?role=admin&page=2" \
  -H "Authorization: Bearer __VG_TOKEN_5147cb533cb5__"

# POST 请求,参数在 body
curl -X POST "https://api.example.com/users" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer __VG_TOKEN_5147cb533cb5__" \
  -d '{"name":"Alice","email":"user@example.com"}'

GET 没有请求体,POST 有。两者的鉴权头是一样的。响应通常是:

# GET 响应
HTTP/1.1 200 OK
Content-Type: application/json

[{"id": 1, "name": "Admin"}, {"id": 2, "name": "Bob"}]

# POST 响应(创建成功)
HTTP/1.1 201 Created
Location: /users/3
Content-Type: application/json

{"id": 3, "name": "Alice"}

注意状态码的差异:GET 成功用 200,POST 创建成功用 201,并返回新资源的 Location 头。

REST 中的方法约定

RESTful API 把 HTTP 方法对应到 CRUD 操作,每对关系都是固定的:

| 操作 | HTTP 方法 | 端点示例 | 幂等 | |------|----------|----------|------| | 创建 | POST | POST /users | 否 | | 读取列表 | GET | GET /users | 是 | | 读取单个 | GET | GET /users/123 | 是 | | 整体更新 | PUT | PUT /users/123 | 是 | | 部分更新 | PATCH | PATCH /users/123 | 否(视实现) | | 删除 | DELETE | DELETE /users/123 | 是 |

PUT 是幂等的:同样的请求发两次,最终状态一致(用户被覆盖成同一份数据)。DELETE 也是幂等的:删一次和删两次,结果都是"该用户不存在"。

两种典型错误用法

错误一:用 POST 做读取

// 反模式
app.post('/api/users/search', (req, res) => {
  const { keyword, page, filter } = req.body;
  // 查询数据库返回结果
});

问题:无法被浏览器缓存,无法被 CDN 加速,无法被书签保存,刷新时浏览器会弹"重新提交表单"的确认框。

正确做法:

app.get('/api/users/search', (req, res) => {
  const { keyword, page, filter } = req.query;
  // 同样的查询逻辑
});

错误二:用 GET 做修改

// 反模式:在 GET 里改数据
app.get('/api/users/delete', (req, res) => {
  const { id } = req.query;
  db.users.delete(id);
});

问题:违反幂等性约定。搜索引擎爬虫、预取机制、浏览器预加载都可能误触发这个 URL,把数据删掉。Google Web Light、Chrome prefetch、各类 SEO 工具都会主动访问页面中的链接。

正确做法:

app.delete('/api/users/:id', (req, res) => {
  db.users.delete(req.params.id);
  res.status(204).end();
});

安全考量

GET 和 POST 在安全上的差异经常被误解。POST 不比 GET 更安全,只是参数位置不同。

| 风险点 | GET | POST | |--------|-----|------| | 浏览器历史 | 暴露参数 | 不暴露 | | 服务器访问日志 | 暴露参数 | 不暴露 | | Referer 头 | 暴露给第三方 | 不暴露 | | 书签 | 暴露参数 | 不暴露 | | 中间人攻击 | 同样易受攻击 | 同样易受攻击 |

把密码、token 放在 GET 参数里是灾难。它们会进入浏览器历史、access log、Referer 头,被任何能看日志的人读到。但这不是说 POST 就"安全"了。未加密的 HTTP 下,POST body 同样会被中间人嗅探。真正的安全靠 HTTPS,不是靠方法选择。

敏感数据永远走 HTTPS,且不要在 URL 里传递。POST 把数据放 body 是一种"不暴露"的实践,但不是真正的加密。

何时该用哪个:决策表

| 场景 | 推荐方法 | 理由 | |------|---------|------| | 查询列表(带筛选、分页) | GET | 可缓存,可书签 | | 查询单个资源详情 | GET | 可缓存 | | 创建新资源 | POST | 非幂等 | | 整体更新资源 | PUT | 幂等 | | 部分更新资源 | PATCH | 语义更精确 | | 删除资源 | DELETE | 幂等 | | 上传文件 | POST (multipart) | body 可承载二进制 | | 复杂查询条件超过 URL 长度 | POST | 受长度限制 | | 触发副作用操作(发邮件、推送) | POST | 非幂等 | | 服务器侧搜索(建议可缓存) | GET | 提升性能 |

用 DevToolkit Pro 测试 HTTP 接口

调试 API 时,一个好的 HTTP 客户端能省下大量时间。下面三个工具都在浏览器本地运行,请求由你的浏览器直接发送到目标服务器,不经过 DevToolkit 后端:

API 测试器的请求完全由浏览器发起,目标服务器的响应直接显示在工具中。如果你的接口需要鉴权,token 只存在于你本地浏览器,不会上传到 DevToolkit 服务器。

总结

GET 和 POST 的选择不是品味问题,而是语义问题。GET 用于读取,幂等、可缓存、可书签。POST 用于写入,非幂等、不可缓存。把读取操作写成 POST 会牺牲缓存和性能,把写操作写成 GET 会带来数据损坏风险。记住一个原则:任何会改变服务器状态的请求都用 POST(或 PUT/PATCH/DELETE),纯读取的请求一律用 GET。再配合 HTTPS,API 的安全性就有了基本保证。


本文由 DevToolkit Pro 提供。更多开发者工具请访问 首页


广告