读完这篇,你能做什么
能读懂请求和响应,并按 HTTP 规则设计成功、失败和重试时的接口行为。
学习理由、准备要求与知识点
为什么学它
HTTP 规定浏览器、代理和后端怎样通信;状态码和幂等性(重复同一请求是否具有与请求一次相同的预期效果)会影响接口正确性。
先读这些内容
知识点清单
- 方法与请求目标
- 路径和查询参数
- 请求头与消息内容
- Content-Type 与 Accept
- 常见状态码
- 安全方法语义
- 幂等效果与响应区别
- 重复提交业务机制
运行前先确认HTTP 语义依据 RFC 9110;源码定位 OpenJDK 17u jdk-17.0.16+8 的 HttpRequestBuilderImpl。Java 17 算法模拟单进程资源状态和版本条件更新,不是 HTTP 服务器,也不实现完整 If-Match 语法或跨进程事务。
01先弄懂它是什么
读一个 HTTP 接口时,可以先找四部分:方法说明要做什么,URI 指向哪个资源,请求头说明内容类型、身份或缓存条件,正文带具体数据。响应也有状态码、响应头和可选正文。不能把所有成功失败信息都只塞进 JSON 的 code 字段,否则网关、缓存和通用客户端就无法按 HTTP 状态正确判断。
两个常见词要分开:“安全方法”按协议含义不要求改变业务状态,例如读取仍可产生访问日志;“幂等”表示同样请求重复多次,预期的最终效果与一次相同。DELETE 第一次删成功,第二次返回不存在,也可能仍然幂等。判断重点是服务端产生的效果,不是每次状态码或响应文字都必须一样。
以商户说明文档为例,客户端读取文本 A 和版本 v1,另一个客户端把它改成 B。第一个客户端若直接提交 C,可能覆盖别人已完成的编辑。HTTP 条件请求允许客户端说明“我是依据哪个版本修改的”。服务端必须保证只有版本仍匹配时,这次修改才会真正写入;如果先查版本,再不附条件地写入,中间仍可能插入别人的修改。
文中这几个词,先认一认
- 资源与表示
- 资源是要操作的对象;表示是当时传给客户端的内容,例如某商品对应的一份 JSON。
- 安全方法
- 按 HTTP 约定不要求改变业务状态的方法,允许访问日志等附带活动。
- 幂等
- 同样请求执行多次,预期服务端效果与执行一次相同;响应本身不必完全相同。
- 条件请求
- 只有资源仍满足指定条件时才执行,例如版本没变才允许覆盖编辑,或验证缓存是否可继续用。
02看看它怎么工作
接口收到请求后,要先弄清“交给谁处理、要做什么、谁在调用、允不允许、正文格式和数据对不对”。这些分别对应路由、方法、身份认证与权限、媒体类型及数据校验。响应状态也应说实话:200 表示成功,201 通常表示创建了资源,204 表示成功但没有内容;400 表示请求有错误,401 表示缺少有效认证凭据,403 表示拒绝执行,404 表示未找到目标等情况。
401 还要通过 WWW-Authenticate 响应头说明适用的认证方式,这叫认证挑战;不能把这些失败一律写成 200,再只在文字里解释。
两个人编辑同一份资料时,怎样避免后提交的人盖掉别人刚保存的内容?服务器读取成功时,可以把内容连同 ETag 标记一起返回;客户端保存时用 If-Match 带回标记,服务端只在条件符合时修改,否则报告前置条件失败。
ETag 是供比较的标记,客户端不用猜它的内部含义,也不能假定它就是数据库里递增的数字。If-Match 要做强比较:两边都得是强标记,且标记值相同;带 W/ 的弱标记即使文字相同,也不能这样匹配。下方算法只用整数版本模拟相等检查,不实现完整 HTTP 条件规则。
请求超时只说明调用方未在预算内得到预期结果,不说明服务端没有执行。对于安全读取可按策略重试,写操作则需要结合幂等语义、条件或稳定业务键。连接超时、读取超时和业务总截止时间不同,重试还会增加实际负载;协议语义为决策提供基础,却不会自动实现应用的可靠性。
正在加载流程图…
查看流程图源码(Mermaid)
sequenceDiagram
participant A as 客户端A
participant S as 资源服务
participant B as 客户端B
A->>S: 读取文档
S-->>A: 内容 A 与版本 v1
B->>S: 基于 v1 更新为 B
S-->>B: 更新成功,版本 v2
A->>S: 基于 v1 更新为 C
S-->>A: 前置条件失败
A->>S: 重新读取并决定如何合并按顺序看,每一步做了什么
- 先记录方法、目标、请求媒体类型与响应状态。
- 区分资源当前表示与客户端持有的旧表示。
- 更新时提交条件,在原子边界内比较并写入。
- 超时或冲突后根据操作语义恢复,不盲目把所有请求重复发送。
03对照实现代码
OpenJDK 17u,jdk-17.0.16+8 · jdk.internal.net.http.HttpRequestBuilderImpl#checkURI、method、build
已核验真实类与方法。下面为原创局部构建模型,展示 URI 协议/主机、正超时和构建状态检查;省略请求头完整校验、BodyPublisher、协议协商与网络发送,不是原样源码。
import java.net.URI;
import java.time.Duration;
class SourceModel {
record Plan(URI target,Duration timeout) {}
static Plan build(URI target,Duration timeout) {
if(target==null || timeout==null) throw new IllegalArgumentException("缺少构建参数");
String scheme=target.getScheme();
if(scheme==null || !(scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")))
throw new IllegalArgumentException("只支持 HTTP 协议");
if(target.getHost()==null) throw new IllegalArgumentException("缺少可识别主机");
if(timeout.isZero() || timeout.isNegative()) throw new IllegalArgumentException("超时必须为正");
return new Plan(target,timeout); // 只得到请求计划,尚未发送网络请求
}
}checkURI 在构建时检查协议是否为 http 或 https,以及能否获得主机。它验证的是该客户端支持的 URI 结构,不证明目标可信、域名可解析或服务可达。限制访问哪些外部目标属于应用安全策略,还要考虑解析结果和网络出站边界。
method 验证方法名称等条件,build 则要求已有 URI 并生成不可变请求。可变构建过程与构建后的请求对象分开,有助于防止发送时意外观察到半配置状态;但一个请求能成功 build,只能说明通过这层结构检查,不说明服务端一定支持该方法。
请求头也要符合名称、值和客户端保留字段的限制,但构建器不知道订单属于谁,也不理解任意 JSON 字段的业务含义。客户端负责把请求组织并传出去,服务端仍负责资源规则与权限检查。能构建、能发送,都不等于业务请求有效。
timeout 要求正值体现了早失败设计:不合法配置在构建时暴露,比在网络等待后得到模糊故障更容易定位。真实请求时间还受连接、重定向和处理过程影响,应用应读客户端具体超时契约,不能仅凭一个 Duration 推导整个跨服务业务的最长完成时间。
代码先调用谁,再调用谁
HttpRequest.newBuilder创建可配置构建器。- uri、method、header、timeout 等步骤验证局部配置。
- build 检查必需状态并生成请求对象。
HttpClient发送请求,业务结果由响应与应用规则共同判断。
04动手写一个例子
带版本条件的资源更新状态机
维护内存中的编号到文本资源。创建返回 201,重复创建返回 409;读取存在返回 200,不存在返回 404;更新仅在预期版本相等时成功,否则返回 412;删除已存在资源返回 204,再删返回 404。版本从实例内全局递增序列取得,避免删除重建后旧版本重新匹配。
- 输入 1
创建 A,读取版本 1,再基于 1 更新 B- 输出 1
201、200、200,版本变为 2
- 输入 2
资源版本 2,基于版本 1 更新 C- 输出 2
412,内容仍为 B
- 输入 3
删除同一资源两次- 输出 3
204、404,最终均不存在
- 输入 4
删除重建后使用旧版本更新- 输出 4
412
资源记录同时保存值和版本。更新把检查与替换放进同一个 synchronized 方法,因此在本单进程模型里不会有线程在两步之间写入。若先在一个方法检查再在另一个方法写入,就失去该保证。
版本不能简单在每个资源重建时从一开始,否则旧客户端持有的版本一可能错误匹配新资源。这里使用实例全局递增版本,删除后重建得到更大的值;进程重启和多副本的版本分配不在模型保证内。
返回结果同时说明状态与当前值,测试不仅检查状态码,也检查失败后资源没有变化。main 验证创建、重复创建、成功更新、旧版本拒绝、重复删除、重建与非法编号,从状态不变量观察幂等,而不是要求两次删除状态码相同。
为什么这样写是对的每次成功创建或更新获得新的实例内版本;条件失败不改变值与版本;删除任意次数后的目标状态都是不存在。
哈希表保存 r 个资源并做固定次数查询或更新;文本复制和网络成本不包含在这个内存状态模型中,全局锁会限制实际并行吞吐。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
record Item(String value,long version) {}
record Reply(int status,Item item) {}
static class Store {
final Map<Long,Item> items=new HashMap<>();
long revision=0;
void valid(long id) { if(id<=0) throw new IllegalArgumentException(); }
long next() { revision=Math.incrementExact(revision); return revision; }
synchronized Reply create(long id,String text) {
valid(id); Objects.requireNonNull(text);
if(items.containsKey(id)) return new Reply(409,items.get(id));
Item value=new Item(text,next()); // 重建也取得新版本,避免旧版本重用
items.put(id,value); return new Reply(201,value);
}
synchronized Reply get(long id) {
valid(id); Item item=items.get(id); return new Reply(item==null?404:200,item);
}
synchronized Reply update(long id,long expected,String text) {
valid(id); Objects.requireNonNull(text);
Item old=items.get(id);
if(old==null) return new Reply(404,null);
if(old.version()!=expected) return new Reply(412,old); // 失败不能改状态
Item fresh=new Item(text,next());
items.put(id,fresh); return new Reply(200,fresh); // 与比较在同一把锁内
}
synchronized Reply delete(long id) {
valid(id); return new Reply(items.remove(id)==null?404:204,null);
}
}
static void check(boolean ok) { if(!ok) throw new AssertionError(); }
public static void main(String[] args) {
Store s=new Store(); Reply created=s.create(1,"A"); check(created.status()==201);
long v1=created.item().version(); check(s.get(1).status()==200);
check(s.create(1,"other").status()==409);
Reply updated=s.update(1,v1,"B"); check(updated.status()==200 && updated.item().version()>v1);
check(s.update(1,v1,"C").status()==412 && s.get(1).item().value().equals("B"));
check(s.delete(1).status()==204); check(s.delete(1).status()==404);
check(s.get(1).status()==404);
Reply recreated=s.create(1,"new"); check(recreated.item().version()>updated.item().version());
check(s.update(1,v1,"stale").status()==412);
check(s.update(99,1,"missing").status()==404);
try { s.get(0); throw new AssertionError("应拒绝非法编号"); }
catch(IllegalArgumentException expected) { /* 输入边界已覆盖 */ }
System.out.println("PASS 12 resource assertions");
}
}这些特殊情况也要试一试
- 空文本可以是合法表示,
null引用在本题中拒绝。 - 删除重建不能复用旧验证器,否则出现资源代次混淆。
- 真实 If-Match 的语法、强比较与其他前置条件顺序应按 RFC 实现,不能直接用整数接口替代。
进阶选读 · 生产拓展:数据库条件更新与多副本验证器
场景多个应用实例同时编辑资源,单 JVM 锁不能形成共同互斥。
做法用数据库版本条件更新并检查影响行数,在同一事务内管理相关事实;为删除重建保留资源代次或使用不会复用的验证器。跨资源合并与外部副作用另行设计事务或补偿。
复杂度主键条件更新通常依赖索引定位及锁竞争;不能仅用内存 O(1) 代表数据库与网络延迟。
好处与代价乐观条件减少长时间持锁,但高冲突需要重读与合并;悲观锁能提前协调,却增加等待与故障恢复成本。
05项目里怎么用
接口应把 HTTP 状态与响应结构共同设计。统一错误体可以包含稳定错误码、字段路径和请求标识,但不能覆盖掉真实状态。不要把数据库堆栈、密码或内部网络细节返回给调用者;日志与客户端消息服务于不同受众。
自动重试前,分别检查方法含义、正文能否再发送,以及业务是否允许重复。POST 可以通过业务幂等键避免重复效果,但服务端必须真正保存并检查,光加请求头没用。客户端超时后可先查询已有业务状态,避免把结果未知当成确定失败,再次执行不可撤销动作。
拿着正确版本,也不代表就有权修改。保存订单前,仍要检查订单属于谁、当前状态允许怎样变化、金额是否符合规则。版本条件只能防住它所检查的那类并发覆盖,不能代替业务授权,也不能让多个服务的修改自动一起成功或失败。多人经常编辑冲突时,可以把冲突展示出来,让用户比较并合并内容;不要在背后无限重试,把别人的修改悄悄盖掉。
容易踩的坑
- 把安全、幂等、可缓存当作同一个属性。
- 要求幂等请求每次必须返回相同状态码。
- 连接或读取超时后直接认定服务端没有执行。
- 把客户端构建成功当作目标可信、业务有效或服务端已成功。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 无条件覆盖 | 实现与调用最简单。 | 并发编辑可能丢失他人更新。 | 明确允许最后写入覆盖且有相应业务约定。 |
| 版本条件更新 | 能显式发现基于旧状态的写入。 | 冲突后需要重读、合并或用户决策。 | 文档、库存状态等不允许静默覆盖的资源。 |
| 业务幂等键 | 能识别同一业务操作的重试。 | 需要持久记录、参数一致性和过期策略。 | 创建订单等跨超时重试场景。 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01DELETE 返回 204 后再返回 404,是否不幂等?
展开参考答案与推导
不能这样判断。应检查重复操作的预期服务端效果,删除后的资源都不存在即可符合相应幂等语义;响应不同并不自动改变最终效果。
02Content-Type 与方法是否互相决定?
展开参考答案与推导
不互相决定。方法表达操作语义,Content-Type 描述实际内容表示;POST 可以承载不同媒体类型,服务端需明确支持范围。
03为什么 If-Match 不能简单接受弱 ETag?
展开参考答案与推导
该条件使用强比较来约束表示匹配,弱验证器表达的等价程度不同。真实处理应遵守验证器语法和前置条件规则,不能只比较去掉前缀后的字符串。
换个情况,还会处理吗
01更新请求超时,用户点击重试,服务端已改过一次怎么办?
展开参考答案与推导
根据条件版本或业务幂等记录判断之前的结果,必要时查询当前资源。不能仅靠客户端超时断言第一次未执行,也不能无条件重复不可逆副作用。
02所有接口都返回 200,有哪些实际问题?
展开参考答案与推导
通用客户端、缓存、代理和监控难以按 HTTP 语义判断失败,认证挑战与重试策略也可能被破坏。应保留合适状态并提供统一错误表示。
03读取版本后再普通 UPDATE 为什么仍会丢更新?
展开参考答案与推导
另一个写入可以发生在检查与写入之间。应在同一原子边界执行版本条件更新并检查影响行数,不能把两次独立操作当作一次条件提交。
写代码试一试
01为资源状态机验证删除幂等效果。
- 输入 / 条件
create(1,A),delete(1),delete(1),get(1)- 输出 / 预期
201、204、404、404
提示 同时断言最终资源不存在。
展开参考答案与推导
测试两次删除返回不同状态但都不产生额外资源;关注最终状态,不把状态码相等作为幂等断言。
02复现版本重用导致的错误匹配。
- 输入 / 条件
创建版本1,删除,重新创建又用版本1,旧客户端更新- 输出 / 预期
错误设计会接受过时更新;正确设计应拒绝
提示 把资源代次与内容版本一起考虑。
展开参考答案与推导
使用新代次或不会复用的验证器,使重建资源与旧资源可区分;测试应明确覆盖删除再创建路径。
03给更新增加非空文本业务校验。
- 输入 / 条件
合法版本配合空字符串- 输出 / 预期
按契约返回输入错误,版本与内容不变
提示 校验失败不得推进版本。
展开参考答案与推导
在生成新版本和写入之前执行约束检查,随后断言存储状态未改变;不要先更新再用异常伪装回滚。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能读懂请求和响应,并按 HTTP 规则设计成功、失败和重试时的接口行为。
本篇重点加练
重点加练状态码与重复请求的业务语义,要求客户端能据响应作出正确动作。
- 常见状态码
- 幂等效果与响应区别
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01代码推演
阅读请求“POST
/notes/7?draft=true,Content-Type: application/json,Accept: text/plain,内容 {"title":"A"}”。逐项定位方法、路径、查询、头和消息内容,并规定服务支持或不支持各媒体类型时应怎样响应。- 提交内容
- 请求分解图和响应规则表。
- 怎样算完成
- 查询与消息内容不混淆
- 请求媒体类型和响应协商分开
练到的知识点方法与请求目标 · 路径和查询参数 · 请求头与消息内容 · Content-Type 与 Accept
- 作业 02设计评审
为读取笔记、创建笔记、无权读取和参数格式错误四个操作设计 HTTP 响应,说明哪些操作具有安全方法语义,并讨论通过 GET 触发删除的接口评审依据。
- 提交内容
- 接口清单和状态码理由。
- 怎样算完成
- 依据协议语义而非仅页面表现
- 区分安全语义与身份鉴权
练到的知识点常见状态码 · 安全方法语义
综合应用
- 作业 03面试场景
客户端在 t=0 提交创建请求,t=2 超时,t=3 重试;第一次提交在 t=4 才完成。定义业务希望达到的重复效果,设计需要记录和返回的信息。
- 提交内容
- 时间线、重复请求合同和验收场景。
- 怎样算完成
- 效果与响应内容分别描述
- 覆盖首次请求仍在执行的情形
练到的知识点幂等效果与响应区别 · 重复提交业务机制
- 作业 04编程实践
在本地实现一个仅操作内存笔记的 HTTP 演示服务,支持读取单条和新增;自行规定 ID、非法 JSON、未知路径及不支持方法的合同,用客户端程序或命令脚本验证。
- 提交内容
- 可运行服务、请求脚本、自写边界检查及实际状态/响应记录。
- 怎样算完成
- 响应能被客户端实际读取
- 错误合同与实现一致
- 只绑定本地练习端点
练到的知识点方法与请求目标 · 路径和查询参数 · 请求头与消息内容 · 常见状态码
- 作业 05原理问答
为 GET、PUT、POST、DELETE 各构造一个业务操作,逐项解释重复执行、缓存观察和资源最终状态之间的关系;不根据方法名称直接宣称业务实现满足合同。
- 提交内容
- 四种操作的前后状态表与条件说明。
- 怎样算完成
- 区分协议约定与实现事实
- 覆盖响应不同但效果判断独立的情况
练到的知识点安全方法语义 · 幂等效果与响应区别
- 作业 06编程实践
实现纯 Java HTTP 结果判定练习器:输入方法、状态码和客户端已知请求阶段,输出由你定义的“完成/需查询/明确失败”类别;业务仅限内存资源替换,先写合同。
- 提交内容
- Java 程序、自写正常/超时未知/重复响应测试及运行证据。
- 怎样算完成
- 类别依据协议与业务事实共同定义
- 不使用一个状态码概括全部方法
练到的知识点常见状态码 · 幂等效果与响应区别
- 作业 07设计评审
文件上传可能因内容过大、不支持格式、用户无权和服务暂不可用失败。为每类失败制定状态与可供客户端执行的下一步,不要求搭建上传服务。
- 提交内容
- 错误合同与客户端处理表。
- 怎样算完成
- 状态选择有场景依据
- 重试和修正输入等动作有明确条件
练到的知识点常见状态码
- 作业 08面试场景
PUT 上传同一文档两次,服务器为每次接收写一条审计日志。定义评估幂等性时观察的资源状态、审计行为和响应分别是什么。
- 提交内容
- 可观察效果清单及重复请求验收。
- 怎样算完成
- 系统效果的观察范围明确
- 结论不只比较两次响应字节
练到的知识点幂等效果与响应区别
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
完成数据库事务后:同一幂等标识被两个租户或两种请求内容使用。设计幂等记录的业务边界、有效期和冲突处理政策,给出崩溃恢复验收场景。
- 提交内容
- 记录模型、状态图与并发失败用例。
- 怎样算完成
- 身份范围明确
- 覆盖记录与业务提交之间的失败
练到的知识点幂等效果与响应区别 · 重复提交业务机制
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 条件变化
文档替换升级为“替换成功后自动发通知”。在相同请求重复到达、响应丢失的条件下,重新定义成功状态和可承诺的重复效果,并比较两个产品目标。
- 提交内容
- 事件时间线和两份接口合同。
- 怎样算完成
- 通知效果被纳入明确需求
- 冲突或可行性有具体场景支持
练到的知识点常见状态码 · 幂等效果与响应区别
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读规范英文
IETF RFC 9110:HTTP 语义 (新标签页)↗
阅读方法、状态码、表示元数据和幂等性;结合真实请求记录理解。
- 实践实践指南中文
MDN:使用 Fetch (新标签页)↗
练习构造请求、检查
response.ok、解析 JSON,并理解跨源请求和异常行为。 - 深入规范英文
WHATWG HTML:表单 (新标签页)↗
查阅 label、控件 name、提交方法与 enctype,定位浏览器到底发送哪些字段。
- 实践实践指南中文
输入网址后发生了什么:浏览器与服务器入门 (新标签页)↗
先读“客户端和服务器”和“到底发生了什么”。小练习:画出浏览器访问博客的四步过程,并给每个箭头写一句白话说明;区分网址、服务器和返回的文件。 正文公开;适合先理解网页如何传到浏览器,协议细节可在后续按需补充。
- 实践实践指南中文
HTTP 入门:读懂请求、响应和状态码 (新标签页)↗
先读“HTTP 流”和“HTTP 报文”,再读无状态与会话。小练习:从浏览器网络面板挑一条请求,分别指出方法、状态码、请求头和响应正文。 正文公开;概述中的文本报文主要帮助理解 HTTP
/1.x,不代表 HTTP/2、HTTP/3在网络上都按同样文本传输。 - 实践实践指南中文
浏览器网络面板:查清哪个请求慢了或失败了 (新标签页)↗
先做记录请求和查看 Headers、Response、Timing。小练习:记录博客中一个页面与一张图片的状态码、大小和耗时,再解释它们为何是两条请求。 教程公开,需要桌面浏览器开发者工具;界面名称可能随浏览器版本变化。
- 实践实践指南英文
418 茶壶短读:一个玩笑怎样留在协议记录里 (新标签页)↗
先读 RFC 2324 的日期、文档类别和 2.3.2,再读当前入口的 15.5.19,并在 IANA 表中找 418。写三行笔记,分别说明故事来源、当前登记名称、为什么不能将其当成通用业务状态。保留规范的边界:目前保留,不等于承诺永远不会重新分配。 公开英文文档,无需账号。正文可直接阅读;不需要向外部服务发送请求。
- 深入参考文档英文
Java 17 HttpRequest (新标签页)↗
核对请求对象、构建器和正文发布者的公开契约。
- 深入参考文档英文
OpenJDK 17u HttpRequestBuilderImpl (新标签页)↗
定位 URI、方法、头字段与构建状态检查。