写后端接口时,新人最常纠结的问题之一就是:这个接口该用 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 测试器:支持 GET、POST、PUT、DELETE、PATCH,自定义 headers、body、超时可取消
- HTTP 状态码查询:查询任意状态码的含义和正确使用场景
- HTTP 方法速查表:所有 HTTP 方法的语义、幂等性、安全属性一览
API 测试器的请求完全由浏览器发起,目标服务器的响应直接显示在工具中。如果你的接口需要鉴权,token 只存在于你本地浏览器,不会上传到 DevToolkit 服务器。
总结
GET 和 POST 的选择不是品味问题,而是语义问题。GET 用于读取,幂等、可缓存、可书签。POST 用于写入,非幂等、不可缓存。把读取操作写成 POST 会牺牲缓存和性能,把写操作写成 GET 会带来数据损坏风险。记住一个原则:任何会改变服务器状态的请求都用 POST(或 PUT/PATCH/DELETE),纯读取的请求一律用 GET。再配合 HTTPS,API 的安全性就有了基本保证。
本文由 DevToolkit Pro 提供。更多开发者工具请访问 首页。