2、HTTP协议基础:HTTP请求方法、状态码、请求头与响应头
说实话,HTTP协议这东西,你天天在用,但很多人其实没真正搞懂。
我记得刚入行那会儿,写API接口就知道GET和POST。后来线上出了个事故——有人用GET请求删了用户数据。嗯,那次教训挺深刻的。从那以后,我对HTTP方法的理解才算真正到位了。
2.1 HTTP请求方法:不只是GET和POST
HTTP定义了多种请求方法,每种都有它的语义和用途。我个人习惯把它们分成两类:安全方法和非安全方法。
GET:获取资源
GET是最常用的方法。它的特点是幂等且安全——说白了,你调多少次,结果都一样,而且不会改变服务器状态。
关键点:GET请求不应该有请求体(body)。参数通过URL传递。
# 好的GET请求
GET /api/users?page=1&size=20 HTTP/1.1
# 坏的GET请求(带请求体)
GET /api/users HTTP/1.1
Content-Type: application/json
{"page": 1, "size": 20} # 不要这样做!
我曾经踩过的坑:有个同事把删除操作做成了GET请求。结果搜索引擎爬虫一爬,数据全没了。记住:任何修改数据的操作,都不要用GET。
POST:创建资源
POST用于创建新资源。它不幂等——你发两次同样的POST,可能会创建两个资源。
POST /api/users HTTP/1.1
Content-Type: application/json
{
"name": "张三",
"email": "zhangsan@example.com"
}
PUT:全量更新
PUT用于替换整个资源。它是幂等的——你发多少次,最终结果都一样。
我的经验:PUT要求客户端提供完整的资源数据。如果只更新部分字段,用PATCH更合适。
PUT /api/users/123 HTTP/1.1
Content-Type: application/json
{
"name": "李四",
"email": "lisi@example.com",
"age": 30
}
PATCH:部分更新
PATCH用于部分更新资源。它不一定是幂等的——取决于你的实现方式。
PATCH /api/users/123 HTTP/1.1
Content-Type: application/json
{
"email": "newemail@example.com"
}
注意:PATCH的请求体格式有讲究。我建议使用JSON Patch(RFC 6902)或JSON Merge Patch(RFC 7396)标准。
DELETE:删除资源
DELETE用于删除资源。它是幂等的——第一次删除返回200,第二次可能返回404,但服务器状态是一样的。
DELETE /api/users/123 HTTP/1.1
2.2 HTTP状态码:服务器的“表情包”
状态码是服务器对请求的回应。我把它比作服务器的“表情包”——一看就知道发生了什么。
2xx:成功
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 请求成功 | GET、PUT、PATCH成功 |
| 201 Created | 资源创建成功 | POST创建资源后 |
| 204 No Content | 请求成功,无返回内容 | DELETE成功 |
我的习惯:POST创建资源后,返回201 + Location头(指向新资源的URL)。DELETE成功后,返回204,不返回body。
3xx:重定向
3xx告诉客户端,你需要去别的地方找资源。
| 状态码 | 含义 | 说明 |
|---|---|---|
| 301 Moved Permanently | 永久重定向 | 搜索引擎会更新URL |
| 302 Found | 临时重定向 | 搜索引擎保留原URL |
| 304 Not Modified | 资源未修改 | 配合缓存使用 |
我曾经遇到的坑:API版本升级时用了302,结果客户端缓存了旧URL。后来改用301,配合Location头,问题解决了。
4xx:客户端错误
4xx表示客户端有问题——你发错了请求。
| 状态码 | 含义 | 常见原因 |
|---|---|---|
| 400 Bad Request | 请求格式错误 | JSON解析失败、参数缺失 |
| 401 Unauthorized | 未认证 | 缺少Token或Token过期 |
| 403 Forbidden | 无权限 | 认证了但没权限 |
| 404 Not Found | 资源不存在 | URL写错了 |
| 409 Conflict | 资源冲突 | 版本冲突、唯一约束 |
| 422 Unprocessable Entity | 语义错误 | 校验失败 |
关键原则:4xx错误一定要在响应体里给出详细的错误信息。我习惯用统一的错误格式:
{
"error": {
"code": "USER_NOT_FOUND",
"message": "用户ID 123 不存在",
"details": "请检查用户ID是否正确"
}
}
5xx:服务器错误
5xx表示服务器出问题了——不是你的错。
| 状态码 | 含义 | 说明 |
|---|---|---|
| 500 Internal Server Error | 服务器内部错误 | 未捕获的异常 |
| 502 Bad Gateway | 网关错误 | 上游服务挂了 |
| 503 Service Unavailable | 服务不可用 | 过载、维护中 |
| 504 Gateway Timeout | 网关超时 | 上游响应太慢 |
我的建议:5xx错误不要暴露堆栈信息给客户端。记录日志就好,返回一个通用的错误消息。
2.3 请求头与响应头:HTTP的“元数据”
HTTP头是请求和响应的“元数据”。你想想看,没有头信息,服务器怎么知道客户端能接受什么格式的数据?
常见的请求头
| 请求头 | 作用 | 示例 |
|---|---|---|
| Content-Type | 请求体的格式 | application/json |
| Accept | 客户端能接受的响应格式 | application/json |
| Authorization | 认证信息 | Bearer <token> |
| User-Agent | 客户端标识 | Mozilla/5.0... |
常见的响应头
| 响应头 | 作用 | 示例 |
|---|---|---|
| Content-Type | 响应体的格式 | application/json |
| Location | 新资源的URL | /api/users/123 |
| Cache-Control | 缓存策略 | max-age=3600 |
| ETag | 资源版本标识 | "33a64df5" |
我的经验:API设计时,一定要明确Content-Type。我见过太多因为忘记设置Content-Type导致解析失败的案例。
2.4 实战:用Python实现一个简单的HTTP客户端
光说不练假把式。我们来看看Python里怎么用requests库发送各种HTTP请求。
import requests
# GET请求
response = requests.get('https://api.example.com/users', params={'page': 1})
print(response.status_code) # 200
print(response.json()) # 解析JSON响应
# POST请求
response = requests.post(
'https://api.example.com/users',
json={'name': '张三', 'email': 'zhangsan@example.com'},
headers={'Authorization': 'Bearer your-token'}
)
print(response.status_code) # 201
# PUT请求
response = requests.put(
'https://api.example.com/users/123',
json={'name': '李四', 'email': 'lisi@example.com', 'age': 30}
)
# PATCH请求
response = requests.patch(
'https://api.example.com/users/123',
json={'email': 'newemail@example.com'}
)
# DELETE请求
response = requests.delete('https://api.example.com/users/123')
print(response.status_code) # 204
总结一下:
- GET用于获取,POST用于创建
- PUT全量更新,PATCH部分更新
- DELETE用于删除
- 2xx成功,3xx重定向,4xx客户端错,5xx服务器错
- 请求头和响应头是HTTP的元数据,别忽略它们
嗯,这一章的内容就到这里。下一章我们会深入聊聊RESTful API的设计原则——那才是真正有意思的部分。