◆

HTTP 请求方法 GET 与 POST 的区别

2021-12-02 02:470

GET 请求和 POST 请求的区别是什么

  • GET 请求将参数附加在 URL 的查询字符串中,而 POST 请求将参数放在请求体中。
  • GET 请求的数据会显示在 URL 中,而 POST 请求的数据不会显示在URL中。
  • GET 请求一般用于获取数据,而 POST 请求一般用于提交数据。

从报文层面看两者的差别

上面三条是结论,但面试里往这一层追问,得能画出报文。HTTP 请求由请求行、请求头、空行、请求体四部分组成,GET 和 POST 的差别就是参数放在了不同的位置。

GET 把参数拼在请求行的 URL 里,没有请求体:

GET /api/users?page=1&size=20 HTTP/1.1
Host: example.com
Accept: application/json

POST 的参数在空行之后的请求体里,URL 保持干净:

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

{"name":"wfly","email":"a@example.com"}

注意 POST 多出来的 Content-Type 和 Content-Length——因为有了请求体,服务端必须知道这段字节该怎么解析、有多长。这也解释了一个常见的坑:后端接不到参数,十次里有八次是 Content-Type 写错了。三种常见的编码方式对应完全不同的报文体格式:

  • application/json — 请求体是一段 JSON 字符串,现在的接口默认用这个
  • application/x-www-form-urlencoded — 请求体是 name=wfly&email=a%40example.com,也就是 URL 查询串的格式,只是搬到了 body 里
  • multipart/form-data — 用分隔符把多个字段切开,能带二进制,上传文件必须用这个

更本质的区别:语义,而不是能力

很多人把「GET 不能带 body」当成规则,其实 HTTP 规范并没有禁止 GET 带请求体,只是规定了带了也没有定义好的语义,所以大量的中间件、代理、CDN 会直接把它丢掉。所谓的区别,绝大多数不是「做不到」,而是「约定了不该这么做」,而这个约定又反过来决定了整条链路上的基础设施会怎么对待这个请求。

真正写在规范里的两条语义属性是关键:

安全(safe)。GET 是安全方法,意思是它只读,不应该改变服务端状态。这条被违反的后果很实际:浏览器的预加载、搜索引擎的爬虫、企业网关的链路预取,都会自作主张地去请求它看到的 GET 链接。如果你把「删除用户」做成 GET /user/delete?id=1,某天爬虫扫一遍你的页面,数据就没了。这不是假设,是真实发生过很多次的事故。

幂等(idempotent)。同一个请求发一次和发 N 次,服务端的最终状态一样。GET 幂等,POST 不幂等。这条决定了失败之后能不能自动重试:GET 超时了,客户端、代理、浏览器都可以放心重发;POST 超时了没人敢重发,因为可能已经成功创建了一条记录,重发就变成两条。所以支付、下单这类接口需要业务层自己做幂等——传一个客户端生成的唯一 token,服务端拿它去重。

顺一遍常用方法的这两个属性:

方法安全幂等典型用途
GET是是查询资源
HEAD是是只要响应头,不要 body,探测资源是否存在、看大小
OPTIONS是是查询服务端支持的方法,跨域预检用它
POST否否创建资源、提交表单、触发动作
PUT否是整体替换资源(给全量字段,所以幂等)
PATCH否否局部更新资源
DELETE否是删除资源(删两次结果还是「不存在」)

DELETE 幂等这点常被问:第一次删返回 200,第二次删返回 404,响应码不一样,怎么算幂等?因为幂等约束的是服务端状态,不是响应内容——两次之后资源都是「已删除」,状态一致,所以幂等。

缓存:GET 会被缓存,POST 基本不会

这是实践中差别最大的一点。GET 的响应可以被浏览器、CDN、代理缓存,缓存策略由响应头决定:

HTTP/1.1 200 OK
Cache-Control: max-age=3600
ETag: "abc123"

POST 的响应默认不缓存(规范上允许在带了明确缓存头时缓存,但实际链路上几乎没人这么用)。

由此带来两个具体问题:

GET 接口的数据不刷新。老 IE 上尤其明显,同一个 URL 直接吃缓存不发请求。所以早年前端流行给 GET 请求拼一个时间戳破缓存:

fetch(`/api/list?page=1&_t=${Date.now()}`);

这是权宜之计。正确做法是服务端给出准确的缓存头,需要实时性就 Cache-Control: no-store,允许短缓存就给个合适的 max-age。

该缓存的接口被做成了 POST,白丢缓存收益。这个反过来也常见:因为参数太长就把查询接口改成 POST,结果 CDN 完全帮不上忙。参数长确实是 GET 的硬限制,但那个限制不在 HTTP 规范里——规范没规定 URL 长度上限,是浏览器和服务器各自设的。实际能用的安全值大约 2000 字符(IE 的限制最紧),Nginx 默认的 large_client_header_buffers 也会限制请求行长度,超了会返回 414。

跨域行为不同:POST 可能多发一次请求

浏览器的 CORS 机制把请求分成简单请求和需要预检的请求。GET 通常是简单请求,直接发;POST 只有在 Content-Type 是 text/plain、multipart/form-data、application/x-www-form-urlencoded 这三种之一时才算简单请求。

所以现在最常见的 Content-Type: application/json 的 POST 是非简单请求,浏览器会先自动发一个 OPTIONS 预检:

OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

服务端得回复允许,浏览器才发真正的 POST。这意味着一次跨域的 JSON POST 实际是两个往返。优化手段是让服务端在预检响应里带上 Access-Control-Max-Age,把预检结果缓存起来:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

调接口时发现 Network 面板里多出一个 OPTIONS,别以为是 bug,那就是预检。

「POST 比 GET 安全」是个误解

面试里如果顺着「POST 的数据不显示在 URL 中」说成「所以 POST 更安全」,会被追着打。原因是:

  • HTTP 明文传输时,GET 的 URL 和 POST 的 body 一样都是裸的,抓包都能看到。真正解决传输安全的是 HTTPS,跟用什么方法无关。
  • HTTPS 下 URL 也是加密的(在 TLS 层之内),所以 GET 的参数在链路上并不比 POST 更暴露。

POST 唯一实在的优势是参数不落到那些会记录 URL 的地方:浏览器历史记录、书签、服务器的 access log、Referer 头、CDN 日志。密码、token 这类敏感信息如果拼在 URL 里,会被写进一堆日志文件,这才是真正的风险点。结论是:敏感数据用 POST 是对的,但理由是「避免被日志记录」,不是「传输更安全」。

实际怎么选

我的判断顺序很简单:

  1. 这个请求会改变服务端状态吗? 会,就绝对不能用 GET。这是底线,跟参数长短、数据敏感度都无关。
  2. 不改状态,参数会超过 2000 字符吗? 不会就用 GET,能吃到缓存、能收藏、能直接贴给别人复现问题。
  3. 不改状态但参数极长(比如复杂的多维筛选、批量 ID 查询),只能退回 POST。这时记得在文档里注明它是查询语义,别让后续维护的人以为它有副作用。

RESTful 风格下再细分一层:创建用 POST,全量替换用 PUT,局部改用 PATCH,删除用 DELETE。这套约定的价值不在于「正确」,而在于让人和机器(网关、监控、重试中间件)看方法名就能判断这个请求能不能重试、能不能缓存。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEHTTP 请求方法 GET 与 POST 的区别加载中