打开浏览器开发者工具,切到Network面板,刷新一个刚提交过表单的页面。你会看到两条记录:一条是GET,一条是POST。它们长得像双胞胎,但干的事完全不同。GET把参数塞在URL里,POST把参数藏在请求体里。就这么一个区别,衍生出后面一堆事。
我见过一个后台系统,所有删除操作都用GET实现。链接长这样:/delete?id=123。结果某天运营同事在群里发了个“删除所有测试数据”的链接,有人手滑点了一下,数据库里三千条记录没了。这就是GET的脾气——它假设你是去“获取”东西,所以浏览器、爬虫、预加载器都敢随便碰它。POST不一样,浏览器知道你不会无缘无故发一个POST,所以不会预加载,也不会因为一个链接就触发。
从协议层面看,GET请求没有请求体(或者说请求体没有语义),所有信息必须编码在URL里。URL长度有限制,虽然HTTP标准没规定上限,但浏览器和服务器各有各的脾气。IE曾经卡在2083个字符,Chrome现在能撑到几万,但Nginx默认只收8K的请求行。所以你要传一张图片的base64,GET直接歇菜。POST把数据放在body里,理论上可以传几个G,当然实际还得看服务器配置。
安全性上,很多人说POST比GET安全。这话只对了一半。GET的参数在URL里,会留在浏览器历史、服务器访问日志、代理日志里。你搜一下“site:example.com?password=”,能捞出不少明文密码。POST的参数在body里,不主动记录的话,日志里看不到。但如果你不用HTTPS,GET和POST都是裸奔,抓包工具一清二楚。所以别把POST当加密手段,它只是不把数据写在脸上。
缓存是另一个分水岭。GET请求可以被浏览器主动缓存,也可以被CDN缓存。你访问一个新闻页面,第二次打开快很多,因为GET响应被存下来了。POST默认不缓存,除非你手动设置Cache-Control。这就解释了为什么搜索框用GET——搜“Python教程”和搜“Java教程”是两个不同的URL,可以分别缓存、分享、加书签。而登录表单用POST,因为你不希望别人通过一个URL就登进你的账号。
幂等性这个词听起来唬人,其实就是“做一次和做一百次结果一样”。GET是幂等的,你刷新一百次新闻页面,服务器压力大,但数据不会变。POST不幂等,你刷新一百次支付页面,可能扣一百次钱。所以浏览器在刷新POST结果时会弹窗:“要重新提交表单吗?”而刷新GET页面它从来不问。这个细节暴露了HTTP设计者的意图:GET用于读取,POST用于写入。
实际开发中,RESTful API把这条线画得很清楚。GET /users 拿列表,GET /users/1 拿单个,POST /users 创建,PUT /users/1 全量更新,PATCH /users/1 部分更新,DELETE /users/1 删除。但很多团队偷懒,所有操作都走POST,连查列表都用POST。理由是“方便传JSON”。这也没错,但你就失去了缓存、书签、历史记录这些浏览器白送的功能。我见过一个后台,筛选条件用POST传,结果用户没法把筛选结果分享给同事,只能截图。
还有一个冷知识:GET和POST在HTTP/1.1里都是明文,但GET请求只发一次TCP包(如果URL短),POST要发两次——先发header,再发body。有些服务器会等body到了才响应。所以理论上GET比POST快那么一丁点,但这点差异在真实网络里可以忽略。真正影响性能的是缓存和CDN,那才是数量级的差别。
最后说个踩坑经历。有次调第三方API,文档说用POST,我传了JSON,返回400。换成表单格式,还是400。折腾半小时发现,对方要求参数必须放在URL query string里,但方法必须是POST。也就是说,它用POST方法,却把参数当GET用。这种四不像的设计在老旧系统里不少见。所以别死记“GET放URL,POST放body”,具体还得看接口文档。协议是协议,实现是实现。
```