跳到主要内容
API 测试2026-09-13李帅7 分钟阅读

HTTP GET vs POST:什么时候用哪个?语义、安全与最佳实践

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

核心语义差异

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

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

"安全"和"幂等"是 HTTP 规范的术语,不是口语化的"安全"。安全方法指的是不修改服务器状态,幂等指的是重复执行不会产生额外效果。现行规范见 RFC 9110(HTTP Semantics):GET 被定义为安全方法,POST 不是。

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 方法端点示例幂等
创建POSTPOST /users否
读取列表GETGET /users是
读取单个GETGET /users/123是
整体更新PUTPUT /users/123是
部分更新PATCHPATCH /users/123否(视实现)
删除DELETEDELETE /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 更安全,只是参数位置不同。

风险点GETPOST
浏览器历史暴露参数不暴露
服务器访问日志暴露参数不暴露
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提升性能

用 ToolVault 工具匣 测试 HTTP 接口

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

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

FAQ

POST 比 GET 更安全吗?

不。POST 只是把参数从 URL、历史记录和 Referer 里藏起来。明文 HTTP 下中间人照样能读 body。真正加密靠 HTTPS。无论用哪种方法,都不要把密钥放进 GET 的 query。

RFC 9110 里说的 GET「安全」是什么意思?

指不应改变服务器状态(无副作用),不是「已加密」或「已鉴权」。爬虫和预取可以随意发 GET,所以写操作绝不能挂在 GET 上。

搜索接口什么时候该用 POST?

过滤条件超过 URL 长度上限(约 2k–8k,视客户端/代理而定),或查询体是不适合放 query 的复杂 JSON 时。若需要缓存和可分享链接,优先 GET。

怎么快速对比 GET / POST 响应?

用 API 测试器 带同一套鉴权头各发一次。需要复习幂等/安全语义时看 HTTP 方法速查表。

总结

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


ToolVault 工具匣 出品的开发者指南。相关工具:API 测试器、HTTP 状态码、HTTP 方法速查表。相关阅读:API 测试指南、CORS 预检说明。首页 汇集了更多浏览器本地工具。

实现注记

做 API 测试器时我们对「浏览器 CORS 限制 vs 真实服务端行为」的边界做过取舍:纯浏览器发跨域请求会被同源策略拦在看到响应之前,排错时恰恰需要看到服务端裸响应头。所以工具提供了一条服务端代理路径(带 SSRF 防护与限流),等价于「帮你执行一次 curl 并原样回显」。这层设计直接来自本文讨论的语义差异——知道 GET/POST 在网关、缓存、重试上的不同行为,才知道测试器该把哪些头原样透传。