4. HTTP/1.1持久连接与管道化
聊完了请求,咱们来看看响应。说白了,服务器收到你的请求后,总得给个回话。这个回话就是 HTTP 响应报文。它的结构其实和请求报文很像,但细节上完全不同。
我个人习惯把响应报文拆成四块来看:状态行、响应头、空行、响应体。嗯,咱们一块一块拆开说。
核心记忆点:HTTP 响应报文 = 状态行 + 响应头 + 空行 + 响应体。缺一不可。
4.1 状态行:服务器的“一句话总结”
状态行是响应报文的第一行。它用一句话告诉客户端:这次请求到底怎么样了。
它的格式长这样:
HTTP/版本号 状态码 状态描述\r\n
举个例子:
HTTP/1.1 200 OK\r\n
这里面有三个部分:
- 协议版本:通常是 HTTP/1.1 或 HTTP/2。我刚开始做开发时,经常看到 HTTP/1.0 的老古董,现在基本绝迹了。
- 状态码:三位数字,200 表示成功,404 表示找不到,500 表示服务器内部错误。这是最关键的。
- 状态描述:一段简短的人类可读文本,比如 "OK"、"Not Found"、"Internal Server Error"。说白了,就是给程序员看的提示。
小技巧:状态描述其实可以自定义,但主流浏览器和客户端只认状态码。我曾经见过有人把 200 的状态描述改成 "Everything is fine, bro",虽然能跑,但建议别这么干。
4.2 状态码分类:5 个家族,各司其职
状态码分 5 类,每类代表一种结果。我整理了一张表,方便你对照:
| 分类 | 范围 | 含义 | 常见例子 |
|---|---|---|---|
| 1xx | 100-199 | 信息性响应 | 100 Continue(继续发送请求体) |
| 2xx | 200-299 | 成功 | 200 OK、201 Created、204 No Content |
| 3xx | 300-399 | 重定向 | 301 Moved Permanently、302 Found、304 Not Modified |
| 4xx | 400-499 | 客户端错误 | 400 Bad Request、401 Unauthorized、404 Not Found |
| 5xx | 500-599 | 服务器错误 | 500 Internal Server Error、502 Bad Gateway、503 Service Unavailable |
为什么会这样分类?你想想看,第一位数字就决定了这个响应的“大方向”。看到 2xx,你就知道请求成功了;看到 4xx,赶紧检查你的请求参数;看到 5xx,那是服务器背锅了。
避坑指南:我曾经在项目中遇到一个诡异的问题——客户端一直报 404,但接口路径明明是对的。查了半天才发现,是 Nginx 配置里把请求转发到了错误的 upstream。所以,看到 404 别急着改代码,先确认请求到底到了哪台机器。
4.3 响应头:服务器的“自我介绍”
状态行之后,就是一堆响应头字段。它们告诉客户端:我返回的是什么类型的数据、有多大、要不要缓存、要不要设置 Cookie 等等。
常见的响应头有这些:
- Server:服务器软件信息。比如
Server: nginx/1.18.0。我个人建议在生产环境隐藏具体版本号,避免被针对性攻击。 - Content-Type:响应体的 MIME 类型。比如
Content-Type: text/html; charset=utf-8表示返回的是 HTML 页面。 - Content-Length:响应体的字节长度。比如
Content-Length: 1024表示响应体有 1024 个字节。 - Set-Cookie:让客户端设置 Cookie。比如
Set-Cookie: sessionId=abc123; Path=/; HttpOnly。 - Cache-Control:缓存策略。比如
Cache-Control: max-age=3600表示缓存一小时。
来看一个完整的响应头示例:
HTTP/1.1 200 OK
Server: nginx/1.18.0
Content-Type: application/json; charset=utf-8
Content-Length: 256
Set-Cookie: sessionId=abc123; Path=/; HttpOnly
Cache-Control: no-cache
Date: Mon, 15 Jan 2024 12:00:00 GMT
{"status": "success", "data": {"id": 1, "name": "test"}}
小技巧:Content-Length 和实际响应体长度不一致时,浏览器会报错。我遇到过因为 Gzip 压缩后忘记更新 Content-Length 导致页面白屏的问题。嗯,血的教训。
4.4 空行:分隔符,但很重要
响应头和响应体之间,有一个空行。它由 \r\n 组成。别看它不起眼,它是 HTTP 协议解析的关键——告诉解析器:响应头结束了,接下来是响应体。
如果没有这个空行,解析器会一直把响应体当成响应头来解析,结果就是乱码或解析失败。
4.5 响应体:真正的“干货”
响应体就是服务器返回的实际数据。它可以是任何东西:
- HTML 页面:浏览器渲染的网页源码。
- JSON 数据:API 接口返回的结构化数据。
- 图片/视频:二进制文件。
- 纯文本:比如
robots.txt文件。 - 空:比如 204 No Content 响应,就没有响应体。
响应体的格式由 Content-Type 头决定。比如 Content-Type: application/json 告诉客户端:请用 JSON 解析器来解析这段数据。
重点:响应体可以是空的,但空行必须存在。即使没有响应体,空行也不能省略。这是协议规范,没得商量。
4.6 一张图看懂响应报文结构
说了这么多,不如一张图来得直观。下面是我画的 HTTP 响应报文结构图:
4.7 实战:用 curl 看响应报文
光说不练假把式。咱们用 curl -v 命令,看看真实的 HTTP 响应长什么样:
$ curl -v https://api.example.com/data
* Trying 93.184.216.34:443...
* Connected to api.example.com (93.184.216.34) port 443 (#0)
* SSL handshake completed
> GET /data HTTP/1.1
> Host: api.example.com
> User-Agent: curl/7.68.0
> Accept: */*
>
* HTTP 1.1, assume close after body
< HTTP/1.1 200 OK
< Server: nginx/1.18.0
< Content-Type: application/json; charset=utf-8
< Content-Length: 256
< Set-Cookie: sessionId=abc123; Path=/; HttpOnly
< Cache-Control: no-cache
< Date: Mon, 15 Jan 2024 12:00:00 GMT
<
{"status": "success", "data": {"id": 1, "name": "test"}}
* Closing connection 0
看到没?< 开头的行就是响应报文。状态行、响应头、空行、响应体,一目了然。
小技巧:用 curl -v 调试接口时,重点关注状态码和 Content-Type。如果返回 200 但 Content-Type 是 text/html,而你期望的是 JSON,那八成是服务器配置有问题。
4.8 常见问题与避坑
- 状态码 200 但响应体为空:可能是服务器端逻辑错误,或者 Content-Length 为 0。用 curl 确认一下。
- 响应头大小写:HTTP 规范规定头字段名不区分大小写,但建议统一用首字母大写格式(如
Content-Type)。 - Set-Cookie 多个值:可以发送多个
Set-Cookie头,每个设置一个 Cookie。 - Content-Length 与实际不符:如果 Content-Length 小于实际响应体长度,浏览器会截断数据;如果大于,浏览器会一直等待。我遇到过因为 Gzip 压缩后忘记更新 Content-Length 导致页面白屏的问题。
避坑指南:我曾经在项目中遇到一个诡异的问题——客户端一直报 404,但接口路径明明是对的。查了半天才发现,是 Nginx 配置里把请求转发到了错误的 upstream。所以,看到 404 别急着改代码,先确认请求到底到了哪台机器。