读懂响应
状态码、响应头和响应体。
每个请求都会得到恰好一个响应(除非连接断了,那本身也是一种回答,会在后面的课程中讲到)。一个响应由三部分组成:状态行、响应头和响应体。刚开始做测试的人往往只看响应体,因为数据都在那里。而漏网的 bug 大多藏在另外两部分里。
响应长什么样
和请求一样,HTTP/1.1 响应也是纯文本。下面是 GET /api/health 的响应,用 curl 的 -i 选项(它会把响应头和响应体一起打印出来)显示的样子。去掉了几个基础设施相关的响应头。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
x-powered-by: TestingAPIs
{"status":"healthy","uptimeSeconds":3542,"timestamp":"2026-09-30T18:28:12Z"}第一行是状态行:协议版本、三位数的状态码和一个简短的原因短语。接着是响应头,每行一个,然后是一个空行,最后是响应体。原因短语只是给人看的,客户端和测试应该看数字。HTTP/2 完全去掉了原因短语,所以一个检查文本 "OK" 的测试可能会在服务器升级后失败。
状态码的类别
状态码的第一位数字告诉你响应的类别。很多时候,仅凭这一位数字就能判断结果。
| 类别 | 含义 | 是谁的问题 |
|---|---|---|
| 1xx | 信息。请求仍在处理中。 | 谁的都不是。你很少直接看到它们。 |
| 2xx | 成功。服务器完成了你的要求。 | 谁的都不是。 |
| 3xx | 重定向。去别处找。 | 谁的都不是。客户端需要继续跟进。 |
| 4xx | 客户端错误。请求有问题。 | 客户端:输入错误、没有权限、URL 不对。 |
| 5xx | 服务器错误。请求本身可能没问题。 | 服务器,或者它背后的某个东西。 |
最后一列正是状态码在测试中如此重要的原因。4xx 的意思是“修改你的请求”,5xx 的意思是“我们这边出了问题”。如果你发送了乱七八糟的内容却得到了 500,即使请求本身是错的,这也是一个 bug,因为服务器本该用 400 礼貌地拒绝这个输入,却直接崩溃了。找出这些 500,是刚入门的测试人员能做的最有成效的事情之一。
最常见的状态码
| 状态码 | 名称 | 典型用途 |
|---|---|---|
| 200 | OK | 请求成功,响应体中有结果。 |
| 201 | Created | 一个 POST 创建了新记录。 |
| 204 | No Content | 成功了,并且有意不返回响应体。DELETE 之后很常见。 |
| 301 / 302 | Moved Permanently / Found | 资源位于 Location 响应头给出的 URL。 |
| 304 | Not Modified | 你缓存的副本仍然是最新的。会在缓存一课中讲到。 |
| 400 | Bad Request | 请求格式错误,或某个值无效。 |
| 401 | Unauthorized | 没有凭据或凭据错误。尽管名字如此,它的意思是未认证。 |
| 403 | Forbidden | 凭据没问题,但这个用户无权执行此操作。 |
| 404 | Not Found | 这个 URL 上什么都没有,或者没有这个 ID 的记录。 |
| 405 | Method Not Allowed | URL 存在,但不支持这个方法。 |
| 409 | Conflict | 请求与当前状态冲突,比如邮箱重复。 |
| 422 | Unprocessable Content | 语法没问题,但数据违反了某条规则。 |
| 429 | Too Many Requests | 你触发了限流。放慢速度。 |
| 500 | Internal Server Error | 服务器上发生了未处理的错误。 |
| 502 | Bad Gateway | 代理从它背后的服务器收到了错误的响应。 |
| 503 | Service Unavailable | 服务器过载或正在维护。 |
不同的 API 对其中一些状态码有不同的用法。有的团队对所有校验错误都返回 400,有的则用 422。有的删除操作返回 200 并在响应体里附上消息,有的返回不带任何内容的 204。两者都没有错。你要检查的是:API 是否符合它自己的文档,以及不同端点之间是否保持一致。
试试各种状态码
/api/status/{code} 会用你放在路径里的状态码来响应。用它可以观察某个工具或客户端库在收到特定状态时的表现。试几个看看:
/api/status/201/api/status/503在 curl 里,503 看起来是这样的:
HTTP/1.1 503 Service Unavailable
Content-Type: application/json; charset=utf-8
retry-after: 5
{
"status": 503,
"statusText": "Service Unavailable",
"category": "server-error",
"message": "This is a 503 response. Use /api/status/{code} to request any status."
}注意 retry-after: 5 这个响应头。503 或 429 经常会告诉客户端在重试之前要等待多少秒,行为良好的客户端会遵守它。你要检查的是这个响应头是否存在,而不只是状态码。
超出有效范围的状态码会被拒绝。/api/status/999 返回 400,消息是 Status code must be between 200 and 599 (1xx cannot be sent as a final response). 而 /api/status/abc 返回一个响应体为空的 404,因为 abc 根本匹配不上这条路由。两个错误的输入,两种不同的失败方式。你会经常看到这种模式:类型不对的值往往在路由层就失败了,应用代码根本没有机会运行。
重定向
3xx 响应通常没有有用的响应体。重要的是 Location 响应头,它说明接下来要去哪里:
HTTP/1.1 302 Found
Content-Type: application/json; charset=utf-8
location: /api/echo浏览器和大多数 HTTP 库会自动跟随重定向,只给你看最终的响应。这很方便,直到你需要测试重定向本身的时候。curl 除非加上 -L 否则不会跟随重定向,所以它更适合做这件事。
响应头
响应头描述响应体,并给客户端下达指示。请求商品列表的第 3 页,每页两个商品:
/api/Products?limit=2&page=3HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
x-page: 3
x-per-page: 2
x-total-count: 20
x-total-pages: 10Content-Type 说明响应体是 UTF-8 编码的 JSON。x-page 和 x-total 这四个响应头携带分页的细节:共有 20 个商品、10 页,这是第 3 页。响应体只是一个包含两个商品的数组,所以忽略这些响应头的客户端根本无从得知后面还有更多。以 X- 开头的响应头是 API 自己定义的自定义响应头,由 API 而不是 HTTP 标准来说明。
有些响应头用 Unix 时间戳表示时间,即从 1970 年 1 月 1 日(UTC)起经过的秒数。带限流的 API 常常在 x-ratelimit-reset 中发送一个时间戳,告诉你额度何时重置。信任它之前先转换一下:重置时间在过去,或者在好几年之后,这种 bug 只有在你认真读响应头而不是跳过它们时才能发现。(时间戳转换工具可以帮你转换。)
你也许会期待一个 Content-Length 响应头。这个 API 大多改为发送 Transfer-Encoding: chunked,意思是响应体分块发送,总大小不会提前告知。两种方式都合法。一个响应只会有其中一种,不会两者都有。
响应体
响应体就是数据:一个商品、一个列表、一条错误消息、一张图片。Content-Type 说明如何读取它。你的大多数检查都会针对响应体,读懂 JSON 会详细讲解。在那之前,先看两个特殊情况。
完全没有响应体
204 表示成功且有意不带响应体。响应也没有 Content-Type,因为没有什么需要描述:
/api/formats/no-contentHTTP/1.1 204 No Content
x-powered-by: TestingAPIs总是调用 response.json() 的客户端代码在这里会崩溃,因为空字符串不是合法的 JSON。如果你编写会解析每个响应的测试辅助函数,它们需要能处理空响应体。
当响应头说谎时
状态、响应头和响应体由服务器的不同部分生成,没有任何机制强制它们保持一致。下面这个响应声称自己是 HTML:
/api/formats/wrong-content-typeHTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
{"id":1,"name":"Alice Johnson","email":"alice@example.com","roles":["admin","editor"],"active":true,"score":98.5,"manager":null}响应体是完全正常的 JSON,人读起来根本不会发现问题。浏览器会试图把它当作网页渲染,有些 HTTP 库会拒绝把它解析为 JSON,或者把它作为字符串返回。这个端点是故意做坏的。在真实系统中,同样的 bug 通常来自代理生成的错误页,或框架的默认设置。你的测试应该显式检查 Content-Type。
反过来的情况也会发生:状态码是 200,响应体却写着 {"success": false}。有些 API 就是这样报告所有错误的。统计 5xx 响应的监控工具永远看不到这些失败,只检查状态码的测试也一样。如果某个 API 这样做,你的测试就必须读取每个响应的响应体。
每个响应都要检查什么
在看任何一个数据字段之前,先确认基本情况。这只需要几秒钟,却能发现数量惊人的 bug。
- 状态码是文档针对这种情况承诺的那个。
Content-Type与你实际收到的响应体相符。- 文档提到的响应头(分页、
Location、Retry-After)存在且合理。 - 响应体可以解析,并且结构符合你的预期。
- 错误响应与这个 API 的其他错误使用相同的格式。