前言:一道经典的“送命题”
在互联网大厂的面试中,HTTP 基础几乎是必考题。而 "GET 和 POST 有什么区别?" 这道题出现的频率之高,堪称“面试界的 Hello World”。
然而,作为面试官,我听过太多60分的回答:
“GET 请求参数在 URL 里,POST 请求参数在 Body 里。”
“GET 不安全,POST 安全。”
“GET 长度有限制,POST 没有。”
如果你的理解仅止步于此,对不起,这只能说明你还在“使用工具”的层面,而非“理解原理”。
今天,我们不仅仅是背八股文,而是要从 RFC 规范、TCP 数据流、浏览器行为到现代架构设计,对这道题进行一次“降维打击”式的拆解。
第一层维度:RFC 7231 规范与语义(道)
我们要做的第一件事,是回归源头。HTTP 协议是由 RFC 定义的,RFC 7231 明确了方法的语义。区别的核心在于两个关键词:Safe(安全性) 和 Idempotent(幂等性) 。
首先说明一下RFC是什么
RFC(Request for Comments) 是互联网工程任务组(IETF)发布的一系列技术文档的统称,是互联网技术标准和协议的核心载体。
1. 安全性(Safe)
这里的“安全”不是指“不泄露隐私”,而是指**“不修改服务器状态”**。
- GET 是安全的:它不仅用于获取资源,而且被设计为只读操作。无论你调用多少次,数据库里的数据都不应该发生变化。
- POST 是不安全的:它用于提交数据,通常会引起服务器状态的变更(如创建订单、修改余额)。
2. 幂等性(Idempotent)
这是分布式系统中最关键的概念之一。
- GET 是幂等的:对同一个 URL 发起一次请求和发起一万次请求,对服务器资源的影响是一样的(不仅结果一样,副作用也一样为零)。
- POST 是非幂等的:发送一次请求,创建一个资源;发送两次,就创建两个资源。
灾难案例:为什么不能用 GET 转账?
假设你设计了一个银行接口:GET /transfer?amount=1000&to=zhangsan。
如果搜索引擎爬虫(Googlebot)爬取了这个链接,或者用户浏览器的“预加载”(Prefetch)功能自动请求了这个链接,甚至仅仅是用户手抖刷新了一下页面——恭喜你,钱被扣光了。 没有开发者想写出这样的,所以这才是面试官问你的核心,你对于这种微小细节的考察
** 面试官独白:**
“我问这个问题的初衷,是考察候选人是否有系统设计的严谨性。如果连幂等性都不知道,我不放心让你去写支付微服务。”
第二层维度:底层传输与 TCP(术)
这里有一个流传甚广的谣言,很多所谓的“面经”都在瞎教:
“GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包。”
辟谣:TCP 层面的真相
HTTP 协议从来没有规定 POST 必须要分两次发送。这个说法主要源于某些早期浏览器或库(如老版本 libcurl)在发送 POST 请求时,会先发送 Header,包含 Expect: 100-continue,询问服务器是否接受数据,收到 100 响应后再发送 Body。
但在现代网络环境下:
- 协议层面: HTTP GET 和 POST 在底层都是 TCP 连接。对 TCP 来说,它根本不知道什么是 GET 什么是 POST,它看到的只是一串字节流。
- 实现层面: 现代浏览器(Chrome、Firefox)通常会将 Header 和 Body 尽可能合并在一个 TCP 包中发送(除非 Body 特别大,受到 TCP MSS 或 Nagle 算法影响拆包)。
- 性能差异: 所谓“两次包”带来的延迟在长连接(Keep-Alive)和现代高带宽网络下几乎可以忽略不计。
结论: GET 和 POST 在 TCP 层面没有本质区别,只有报文格式的差异。
Http
// GET 报文
GET /search?q=abc HTTP/1.1
Host: www.example.com
// POST 报文
POST /search HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
q=abc
面试官独白
“能解释清楚 Expect: 100-continue 机制,并且敢于指出网上教程错误的人,通常具备独立思考和阅读源码/抓包验证的能力。”
第三层维度:浏览器行为与工程限制(实战)
既然底层一样,为什么工程实践中限制这么多?这更多是浏览器和服务器软件的“锅”。
1. URL 长度限制的真相
HTTP 协议从未限制 URL 的长度。
但是,IE 浏览器限制 2083 字符,Chrome 限制约 8KB;服务器端 Nginx 默认限制 header 大小为 4KB-8KB。
后果: 如果你非要用 GET 传输 5KB 的 JSON 配置,可能会触发 HTTP 414 URI Too Long 错误,或者被中间件(CDN/WAF)截断。
2. 缓存与书签
- GET: 天生对缓存友好。浏览器会主动缓存 GET 请求(若 header 允许),可以加入书签,可以回退(Back)。
- POST: 默认不被缓存,无法加入书签。
- 刷新页面(F5): 这是最直观的区别。刷新 GET 页面无事发生;刷新 POST 页面,浏览器会弹窗警告“确认重新提交表单? ”。这是浏览器在保护用户避免重复执行非幂等操作(如重复下单)。
3. 数据编码
- GET: 只能进行 URL 编码(ASCII)。二进制数据(图片/文件)需要先 Base64 编码,但这会增加体积且不仅容易超长。
- POST: 支持多种编码,天然支持 multipart/form-data,是上传文件的唯一选择。
第四层维度:安全性与攻击面(防御)
很多人说“POST 比 GET 安全”,理由是“参数在 Body 里看不见”。
这是大错特错! 在 HTTP 明文传输下,Wireshark 抓个包,Body 里的密码和 URL 里的密码一样是裸奔。真正的区别在于攻击面和日志。
1. 日志泄露(Log Leakage)—— 致命伤
这是工程上最严重的隐患。
- URL(GET): 会被记录在 Web Server 的 Access Log(如 Nginx 日志)、代理服务器日志、CDN 日志、浏览器的历史记录中。
- Body(POST): 通常不会被默认记录在服务器日志中。
场景: 如果你用 GET 提交 password=123456,这串密码将永久保存在运维人员管理的 Nginx 日志里。这是严重的安全事故。
2. CSRF(跨站请求伪造)
- GET: 极易被利用。攻击者只需要在钓鱼网站放一个
,受害者一打开页面,钱就没了。
- POST: 稍微麻烦一点,攻击者需要构造一个隐藏表单并用 JS 自动提交。虽然 POST 不能天然防 CSRF(还需要 Token 或 SameSite Cookie),但至少堵住了
、
第五层维度:现代架构视角(进阶)
在 RESTful 盛行的今天,我们强调“动词(Method)”对应“操作”。但在微服务和某些现代架构中,规则正在被打破。
1. GraphQL 与 gRPC-Web
如果你使用 GraphQL,你会发现所有的查询(Query)和修改(Mutation)默认都是 POST 请求。
- 为什么? 因为 GraphQL 的查询语句结构复杂,放在 URL 里太长且不美观。
- 违背 REST 吗? 是的。但这是一种架构权衡。它们不再通过 HTTP Method 区分语义,而是定义了自己的应用层协议语义。
2. CORS 预检(Preflight)
- 简单请求(Simple Request): 某些标准的 GET/POST 请求直接发送。
- 复杂请求: 如果 POST 请求携带了 application/json 或自定义 Header,浏览器会先发送一个 OPTIONS 请求(预检),询问服务器“我能发这个 POST 吗?”。这增加了一次 RTT(往返时间)。
总结:面试满分回答路线图
如果面试官再问你这个问题,请把下面这张图印在脑海里,从下往上输出:
| 维度 | GET | POST | 核心关键词 |
|---|---|---|---|
| 5. 架构 | GraphQL 全用 POST;CORS 可能触发 OPTIONS | 适合 RPC 模式 | 权衡 |
| 4. 安全 | 参数暴露在日志/历史记录;易受 CSRF 攻击 | 相对隐蔽;日志不记录 Body | 隐私保护 |
| 3. 浏览器 | 可缓存、可书签、刷新无害;受 URL 长度限制 | 不可缓存、刷新报警告;无长度限制 | 用户体验 |
| 2. 传输 | 报文载体不同;TCP 层面无本质区别 | 可能存在 Expect: 100 机制 | TCP流 |
| 1. 规范 | 幂等、安全(只读) | 非幂等、修改状态 | 语义 (Semantics) |
最后,你可以这样收尾:
“所以,GET 和 POST 的区别不仅仅是参数位置的不同,更体现了我们对 协议语义的尊重、对 数据一致性的保障(幂等)、对 隐私安全的敏感度(日志)以及对 工程约束的权衡。”
这,才是一个 P7 工程师该有的回答。
求点赞 👍
觉得有收获?点赞、收藏、关注,下期我们深挖 HTTPS 握手过程中的性能损耗与优化。