上周我在审查一个新人提交的登录接口代码时,发现他用了 GET 请求:
js
// ❌ 危险!不要这样写登录接口
fetch(`/api/login?username=${username}&password=${password}`, {
method: 'GET'
})
我问他:"如果用户在公共电脑上登录,别人能通过浏览器历史记录看到他的密码吗?"
他愣住了------这正是 GET
和 POST
最核心的区别。今天我就结合真实业务场景,带你彻底搞懂 HTTP 方法的本质差异。
一、问题场景:登录接口的安全隐患
我们有个后台管理系统,登录模块最初是这样设计的:
js
// 前端代码
function login(username, password) {
return fetch(`/login?user=${encodeURIComponent(username)}&pass=${encodeURIComponent(password)}`, {
method: 'GET'
})
}
结果运维报警:Nginx 日志里全是明文密码!
因为 GET 请求的参数会出现在:
- URL 中
- 浏览器地址栏
- 服务器访问日志
- 代理服务器日志
- Referer 头
任何能看到这些地方的人,都能看到用户的密码。
二、解决方案:改用 POST 提交敏感数据
我们重构为 POST 请求:
js
// ✅ 安全做法
function login(username, password) {
return fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
})
}
现在密码不会出现在 URL 和日志中,安全性大幅提升。
三、原理剖析:从表面到网络层的五层差异
1. 第一层:数据传输位置(最核心区别)
方法 | 数据位置 | 是否可见 |
---|---|---|
GET | URL 查询参数(?key=value ) |
✅ 明文可见 |
POST | 请求体(Request Body) | ❌ 不直接可见 |
我们来画一张 HTTP 请求结构对比图:
ET /api/data?name=Alice&age=25 HTTP/1.1
Host: example.com
User-Agent: Chrome
→ 参数在第一行,随请求头一起发送
POST /api/login HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 38
{"username":"Alice","password":"123"}
↑ 参数在空行之后,作为请求体独立存在
2. 第二层:缓存与历史记录
方法 | 能否被缓存 | 是否保存在浏览器历史 |
---|---|---|
GET | ✅ 可以(除非禁用) | ✅ 是 |
POST | ❌ 通常不缓存 | ✅ 但参数不可见 |
js
// GET 可以被浏览器缓存,提升性能
fetch('/api/config', { method: 'GET' }) // 可能读取缓存
// POST 每次都会发请求
fetch('/api/submit', { method: 'POST' }) // 总是发送新请求
3. 第三层:数据大小限制
方法 | 数据大小限制 | 原因 |
---|---|---|
GET | 约 2KB - 8KB | 受 URL 长度限制(浏览器和服务器限制) |
POST | 几 MB 甚至更大 | 请求体无理论限制(受服务器配置影响) |
js
// ❌ GET 无法发送大文件
fetch(`/upload?file=${hugeBase64Data}`) // 可能失败
// ✅ POST 可以发送大文件
fetch('/upload', {
method: 'POST',
body: fileData // 可达几十 MB
})
4. 第四层:幂等性与安全性
方法 | 幂等性 | 安全性 |
---|---|---|
GET | ✅ 幂等 | ✅ 安全(只读) |
POST | ❌ 非幂等 | ❌ 不安全(可能修改数据) |
PUT | ✅ 幂等 | ❌ 不安全 |
DELETE | ✅ 幂等 | ❌ 不安全 |
PATCH | ❌ 非幂等 | ❌ 不安全 |
📌 术语解释:
- 幂等 :多次执行结果相同(如
GET /user/1
调 10 次结果一样) - 安全:不会修改服务器状态
5. 第五层:典型使用场景
方法 | 使用场景 | 示例 |
---|---|---|
GET |
获取数据、搜索、查询 | /api/users , /search?q=keyword |
POST |
创建资源、提交表单、登录 | /api/users , /login |
PUT |
完全替换资源 | PUT /api/users/1 (更新整个用户对象) |
DELETE |
删除资源 | DELETE /api/users/1 |
PATCH |
部分更新资源 | PATCH /api/users/1 (只改邮箱) |
四、对比主流 HTTP 方法
方法 | 数据位置 | 幂等 | 安全 | 典型用途 |
---|---|---|---|---|
GET |
URL 参数 | ✅ | ✅ | 查询、获取 |
POST |
请求体 | ❌ | ❌ | 创建、提交 |
PUT |
请求体 | ✅ | ❌ | 替换 |
DELETE |
通常无 | ✅ | ❌ | 删除 |
PATCH |
请求体 | ❌ | ❌ | 部分更新 |
HEAD |
无 | ✅ | ✅ | 获取元信息(如检查资源是否存在) |
OPTIONS |
无 | ✅ | ✅ | 预检请求(CORS) |
五、实战避坑指南
❌ 错误用法:用 GET 提交敏感信息
js
// 千万不要这样做!
fetch(`/transfer?to=Bob&amount=1000&token=${token}`, { method: 'GET' })
✅ 正确做法:敏感操作用 POST/PUT
js
fetch('/api/transfer', {
method: 'POST',
body: JSON.stringify({ to: 'Bob', amount: 1000 }),
headers: { 'Authorization': `Bearer ${token}` }
})
❌ 错误用法:用 POST 实现查询接口
js
// 虽然能用,但违反 REST 原则
fetch('/search', {
method: 'POST',
body: JSON.stringify({ q: 'react' })
})
✅ 正确做法:查询用 GET,即使参数复杂
js
// 方案1:用 GET + 查询参数
fetch(`/search?q=react&tags=hooks,context&sort=desc`)
// 方案2:如果参数太复杂,考虑用 POST 但命名要明确
fetch('/api/advanced-search', {
method: 'POST',
body: complexQuery
})
六、举一反三:三个变体场景实现思路
-
需要上传文件并携带元数据
使用
POST
或PUT
,Content-Type: multipart/form-data
,文件和字段一起发送。 -
实现"撤销删除"功能
删除用
DELETE
,但服务器端改为标记删除(is_deleted=true
),前端用GET /items?deleted=true
查看已删项。 -
批量操作接口设计
- 批量创建:
POST /api/users
(数组 body) - 批量删除:
POST /api/users/delete
(ID 数组)或DELETE /api/users
(带 body) - 批量更新:
PATCH /api/users/batch
(操作列表)
- 批量创建:
js
// 批量删除示例
fetch('/api/users/batch-delete', {
method: 'POST',
body: JSON.stringify({ ids: [1, 2, 3] })
})
小结
GET
和 POST
的区别,本质是 "安全查询" vs "数据操作" 的哲学差异:
- GET 是"读卡器"------只能读,不能写,数据暴露在外
- POST 是"投递箱"------内容密封,专门用于提交
查数据,用 GET;
改数据,用 POST;
传敏感,绝不用 GET;
想幂等,选 PUT/DELETE。
当你在设计 API 时,先问自己:
- 这个操作会修改数据吗?→ 不能用 GET
- 数据是否敏感?→ 不能用 GET
- 是否可能重复提交?→ 考虑幂等性(PUT vs POST)
- 是创建还是更新?→ POST vs PUT/PATCH