程云来 / 杭州
返回文章·计算机网络
·约 13 分钟

网络面试(十五):HTTP 请求和响应在说什么?

读懂一轮创建订单的 HTTP/1.1 报文,区分请求体与消息边界、安全与幂等,再用具体场景判断 GET/POST、重定向和常见状态码。


TCP 送到了字节,谁解释“创建订单”?

上一章里,订单 O204 已经创建,客户端却没收到结果。现在先假设连接正常,看看双方怎样表达这件事。

客户端需要说清:操作哪个资源、希望执行什么操作、提交什么数据、接受什么格式。服务器需要回答:处理到了哪一步、结果是什么、下一步去哪里找。HTTP 为请求和响应提供共同的语义;TCP 本身不理解订单。

本章用设定的订单服务说明,报文不是线上抓包。先看 HTTP/1.1 的文本形式;HTTP/2、HTTP/3 使用不同的线上编码,不能直接套用下面的起始行和换行布局。

把一轮报文拆开读

客户端提交一份订单:

http
POST /orders HTTP/1.1
Host: shop.example.com
Content-Type: application/json
Accept: application/json
Content-Length: 31

{"book":"network","quantity":1}

shop.example.com 是教学域名。此处展示直接向源站发送的常见请求目标形式 /orders,不是代理请求等所有形式的全集。

报文位置本例内容回答的问题
请求行POST /orders HTTP/1.1执行什么方法、目标是什么、使用哪个版本?
Hostshop.example.com这个请求属于哪个主机?同一个 IP 可以服务多个站点
Content-Typeapplication/json我提交的内容是什么格式?
Acceptapplication/json我希望收到什么格式?服务器是否提供还要协商
Content-Length31本例请求体有多少字节?
空行之后一段 JSON这次操作提交的数据是什么?

目标资源不一定对应磁盘文件。/orders 可以由服务程序处理,服务器自行实现订单集合的行为。也不要把 Accept 与 Content-Type 对调:前者表达可接受的响应类型,后者描述本消息的内容类型。

假设服务已经创建 O204,并给出结果:

http
HTTP/1.1 201 Created
Date: Fri, 09 Oct 2026 08:00:00 GMT
Location: /orders/O204
Content-Type: application/json
Content-Length: 18

{"orderId":"O204"}

第一行是状态行。201 是供程序判断的状态码;Created 是说明文字,不应让程序靠匹配这个英文词判定成功。Location 指向新订单,正文给出订单号。本例约定这个响应表示订单已经创建;是否还包含支付、配送等后续流程,需要另看业务契约。

如果收到的是 202 Accepted,订单服务可能只把任务放入队列。此时客户端应按接口约定查询任务结果,不能直接展示“订单已经完成”。

请求体在哪里结束?

前面几章说过:TCP 是字节流,应用必须找边界。HTTP/1.1 也不例外。

展示时每行换行;对应的标准线上行结束符是 CRLF,也就是两个字节 \r\n。头部结束处是 \r\n\r\n。本例头部之后按 Content-Length 读取正文,长度只计算正文的字节,不把头部、分隔空行算进去。

请求 JSON 的 UTF-8 编码为 31 字节,响应 JSON 为 18 字节,均不含末尾换行。把正文改成有缩进的 JSON,或者增加中文,都必须重新按实际编码计算,不能继续照抄长度。

Content-Length 不是唯一规则。例如 HTTP/1.1 的 chunked 传输编码按块给出长度,用零长度块结束,末尾还可能有 trailer 字段。不能通过“这次 recv 返回多少字节”判定 HTTP 消息已经结束。HEAD 响应、204 与 304 响应没有消息体;HEAD 的 Content-Length 若出现,可以描述相应 GET 响应会有的长度,不能据此等待那些正文。

这些规则属于 HTTP/1.1 消息解析。遇到 Transfer-Encoding 与 Content-Length 冲突,不能随意挑一个或相加;代理与源站对边界理解不同,可能造成请求走私。这里先建立边界意识,不实现自制 HTTP 解析器。RFC 9112:消息格式、消息体长度与 chunked 编码

“安全”与“幂等”分别问什么?

想象客户端自动预览一个订单链接,又因为连接失败重新发送请求。我们需要分别判断两个问题:预览能否改变业务状态?重复执行会不会多产生一次效果?

安全性关注客户端请求的操作是否本质上只读;幂等性关注相同请求重复执行的预期效果是否与执行一次相同。 这里的“安全”不是加密、防攻击或权限校验。

常见方法典型意图安全幂等
GET获取资源的表示是是
HEAD获取与 GET 对应的元信息,不取正文是是
OPTIONS查询通信选项是是
PUT用提交的表示创建或替换目标资源状态否是
DELETE删除目标资源的关联否是
POST让目标资源按自身规则处理提交内容否不作一般保证
PATCH按修改指令改变资源否不作一般保证

这是方法语义,服务实现也要遵守。假如 GET /delete-order?id=O204 真会删除订单,把它写成 GET 不会让操作自动变成只读。搜索引擎、预取和链接预览可能触发这种错误设计。

读取订单时记录访问日志,不会因此让 GET 的语义变成“不安全”。判断的是请求所要求的业务效果,不是服务器内部有没有任何变化。

DELETE 第一次返回 204,第二次返回 404,也可以是幂等的:目标最终都是“订单不再存在”。幂等不要求响应状态码、时间戳或日志条数相同。 PUT 把数量设为 2,重复仍设为 2;“数量加 1”重复会继续增加,则要另做去重或条件控制。

PATCH 不能只凭方法名判定可重试。修改指令、当前版本及前置条件会影响结果;例如“设为 2”与“加 1”有不同效果,条件请求还可防止在旧版本上继续修改。RFC 5789:PATCH 与条件请求

上一章给 POST 配请求键 K17,是增加具体接口的幂等契约。客户端需要知道这个契约,不能推导出所有 POST 都可以自动重试。RFC 9110:方法属性与方法定义

GET 与 POST,别用“地址栏和请求体”背答案

获取订单可以写成 GET /orders/O204,创建订单可以写成 POST /orders 加 JSON。这个例子体现的是方法意图;参数放在哪里,还取决于接口设计。

POST 同样可以带查询参数,例如 POST /orders?dryRun=true。查询串不是 GET 专属,正文也不能作为 POST 的定义。

GET 请求内容没有普遍定义的语义,客户端不应自行假定各层都接受并正确解释它。浏览器 Fetch 的 Request 构造还明确拒绝给 GET/HEAD 设置非空 body,抛出 TypeError。协议语义、某个 API 的限制、某台服务器能否解析,是三个不同层面。WHATWG Fetch:Request 构造步骤

“GET 最多 2 KB,POST 没限制”也不适合作为协议结论。实际链路上的浏览器、代理和服务器各有长度限制;请求目标过长可能得到 414,内容过大可能得到 413。设计接口时要核对实际链路的配置。

把密码从查询串移到 POST 正文,有助于避免它出现在 URL 日志等位置,但不等于加密。明文 HTTP 的正文仍可被链路观察;HTTPS 对 HTTP 请求的传输提供保护,查询串也在保护范围内。终端自身的历史、日志与数据保存需要另行处理。

缓存也不能按“GET 一定缓存、POST 一定不缓存”背诵。是否能存、多久可用、是否需要验证,要看方法、响应与缓存规则。常见缓存主要处理 GET/HEAD;POST 的缓存有条件且支持情况不同,下一章单独展开。

状态码表达哪一步的结果?

先辨认第一位,再看具体代码和接口契约。不要只写一个“成功 / 失败”的布尔判断。RFC 9110:状态码

类别常见例子应怎样理解
1xx100 Continue阶段性信息;不是最终业务结果
2xx200、201、202、204按具体代码理解成功到哪一步
3xx301、302、303、304、307、308重定向或其他后续处理;304 是缓存验证结果
4xx400、401、403、404、405、409、429请求或访问条件存在问题;不等于所有问题都应责怪用户
5xx500、502、503、504服务端或上游处理问题;不表示操作一定没执行

几个最容易混的场景放在一起看:

场景合适的代码或区别客户端下一步
创建完成,有新资源地址201;本例有 Location 与 O204保存结果,按需访问新资源
仅接收后台任务202按契约查询任务进度
成功,不返回正文204不要继续解析 JSON 正文
缺少有效认证凭据401;响应须含 WWW-Authenticate 挑战按认证机制处理,避免无限重试
理解请求,但拒绝执行403检查访问政策;不简单等同于“已经登录”
资源不存在或不愿公开其存在404检查目标,不能仅凭状态码推断内部数据
目标资源不支持该方法405;响应须有 Allow根据允许的方法调整调用
与资源当前状态冲突409解决版本或业务冲突,再决定是否提交
一段时间内请求过多429降低频率,结合 Retry-After 与本地退避
网关收到无效上游响应502调查网关和上游之间的交互
服务暂时无法处理503判断过载或维护,结合可用的重试提示
网关没有及时收到所需上游响应504调查上游等待;业务结果仍可能未知

429 可以带 Retry-After,但不是每个响应都必须带;客户端仍要有次数和总时间上限。RFC 6585:429 Too Many Requests 502/504 指向网关角色,500 则是服务器遇到无法完成请求的意外情况。无论哪一种,创建订单的重试仍要遵守上一章的结果查询与幂等契约。

重定向后,POST 还是 POST 吗?

订单创建成功后,网站可能让浏览器转到订单详情。选哪个重定向代码,会影响后续请求的方法。

代码地址变化含义自动跟随后对方法的影响
301永久迁移出于历史兼容,POST 可能改为 GET
302临时迁移出于历史兼容,POST 可能改为 GET
303到另一个地址获取间接结果用获取请求 GET 或 HEAD;不是照搬原 POST
307临时重定向自动跟随时保留原方法
308永久重定向自动跟随时保留原方法

Fetch 自动重定向会把 301/302 的 POST 改为 GET,把 303 的非 GET/HEAD 改为 GET,并清除正文。WHATWG Fetch:HTTP redirect fetch 307/308 保留方法,也意味着客户端需要能够重发请求内容;流式正文可能不可重放,不能假设所有请求都一定能自动跟随。

本例的 201 加 Location 本身不要求浏览器自动重定向。若网站希望“提交后查看结果”,可以设计 303 跳到详情;若需要把同一个提交操作转交到新地址,307/308 更明确,但重复执行的业务风险仍须管理。

动手比较 Location 与后续请求

原请求始终是 POST。分别选 201、303、307/308,再看后续方法和正文:同一个字段为什么不能决定相同动作?

都有 Location,下一步都一样吗?

原请求固定为 POST /orders,正文为31字节JSON。切换响应,观察Fetch自动跟随时的方法与正文。

原请求

POST /orders HTTP/1.1
Host: shop.example.com
Content-Length: 31

{"book":"network","quantity":1}

收到的响应

HTTP/1.1 201 Created
Location: /orders/O204

响应代码决定如何解释字段,不能只看Location。

同源教学响应,不请求真实订单服务;假设自动跟随且POST正文可重放,忽略权限、重定向循环与次数上限。流式正文未必可自动重发。201与重定向是独立场景,不表示同一请求先后收到这些响应;HTTP/2和HTTP/3没有这里的文本起始行编码。

30—60 秒参考回答

HTTP 用方法、目标、字段和内容表达请求,用状态码、字段和内容表达响应。HTTP/1.1 文本报文有起始行、头部和分隔空行,消息体边界要按协议规则解析,不能按一次 TCP 读取判断结束。安全方法关注请求意图是否只读,幂等关注重复请求的预期效果,不要求响应相同。GET/POST 的区别主要是语义,不能背成参数位置、固定长度或安全等级。状态码还要看处理阶段:201 创建完成,202 仅接收,504 只说明网关等待超时,不证明业务没执行。

四个追问及解析

1. TCP 保证有序,为什么 HTTP 还要规定正文长度?

有序字节没有自带消息边界。一条连接可以承载多个消息;解析器必须知道哪些字节属于本次正文,哪些属于下一条消息。Content-Length、chunked 和特定响应的无正文规则,都在解决这个边界问题。

2. DELETE 第二次返回 404,是不是不幂等?

不一定。幂等比较预期效果;如果重复后资源仍被删除,就符合这个效果。响应可以因执行时的资源状态而变化。不要用“每次 JSON 完全相同”替代业务效果判断。

3. 看到 503,可以立刻无限重试吗?

不可以。要判断操作是否可安全重试,考虑 Retry-After、退避、总截止时间与尝试上限。创建请求还可能结果未知,重试没有去重契约会放大负载并重复创建订单。

4. 201 与 303 都有 Location,是不是一样?

不同。201 描述创建成功,Location 可以标识主要新资源;303 是让客户端到另一个地址获取间接结果。字段名相同不代表整个响应具有相同语义。

两个易错辨析

“HTTP 无状态,所以服务器不能保存登录状态。” 无状态描述协议本身不会自动把多次请求组织成业务会话。服务器可以保存会话数据,客户端通过 Cookie 等标识关联请求,具体机制到第 17 章解释。

“收到了 200,就代表所有业务都成功。” HTTP 层成功和业务响应契约要一起判断。有的接口会在 200 内容里表达业务拒绝,这需要客户端按接口约定处理;一个任意的 200 更不能证明外部支付、配送等流程完成。

下一章追踪“再次访问订单详情”,解释缓存何时直接复用,何时带 ETag 去验证,以及 304 为什么没有正文。