从请求到响应
https://api.example.com:8443/orders?source=web#detail 包含访问方案、目标主机、端口、资源路径、查询参数和客户端片段。真正发出请求时,客户端还要确定方法、请求字段和消息体,解析主机地址,建立或复用连接,再按 HTTP 语义发送协议消息。代理、容器和 Java Web 运行时把消息交给业务代码;数据库、缓存、消息系统和下游服务则参与形成业务结果。
一次 HTTP 请求
├─ 客户端
│ ├─ 解析 URL
│ ├─ 应用缓存、Cookie、同源与代理策略
│ └─ 生成 HTTP 请求
├─ 名称与连接
│ ├─ DNS 或服务发现
│ ├─ IP 路由与端口
│ ├─ TCP + TLS,或 UDP + QUIC
│ └─ HTTP/1.1、HTTP/2 或 HTTP/3
├─ 服务入口
│ ├─ CDN、WAF 与负载均衡
│ ├─ 反向代理、API 网关与集群入口
│ └─ 主机或容器网络
├─ Java 应用
│ ├─ Servlet 容器
│ ├─ Filter 与 Spring Security
│ ├─ DispatcherServlet
│ ├─ Controller 与 Service
│ └─ 数据库、缓存、消息与下游服务
└─ HTTP 响应
├─ 状态码与响应字段
├─ 表示与消息体
├─ 代理和网络回传
└─ 客户端读取与处理客户端不会为每次请求重走全部阶段:缓存可能直接命中,连接可能被复用,TLS 可能终止在网关,代理也可能自行生成响应。结构图列出潜在参与者;实际部署拓扑要根据连接、代理和运行配置确认。
域名、URL 与请求目标
请求由哪些客户端发起
HTTP 客户端是生成请求并接收响应的一方,不只包括浏览器。不同客户端使用相同的 HTTP 语义,但自动行为并不相同。
| 客户端 | 常见请求 | 自动行为 | 通常由调用方控制 |
|---|---|---|---|
| 浏览器 | 页面、静态资源、表单、Fetch/XHR | Cookie、缓存、同源策略、CORS、重定向、HSTS、Service Worker | 前端请求参数、凭据模式、取消与应用级重试 |
| 移动端或桌面应用 | API、文件、长连接 | 取决于网络库和操作系统 | 证书策略、连接池、超时、重试、缓存 |
| 命令行与 SDK | 调试、自动化、开放 API | 取决于工具默认值 | 方法、字段、请求体、代理、证书和输出处理 |
| 后端服务 | HTTP/RPC 服务间调用 | 客户端库可能自动池化、重试或负载均衡 | deadline、认证、重试责任、Trace 传播 |
| Webhook 发送方 | 事件通知 | 常带超时与重复投递 | 签名、投递策略、重试和事件标识 |
| 爬虫与健康检查 | 页面抓取、存活与就绪探测 | 通常行为简单、频率固定 | User-Agent、超时、状态码判断 |
CORS 和 Service Worker 属于浏览器的安全与运行模型。后端服务调用同一个 URL 时,服务器可能返回完全相同的响应,却没有浏览器对页面脚本读取跨源响应的限制。
URL 的组成
以一个 HTTPS URL 为例:
https://api.example.com:8443/orders?source=web#detail
├─ scheme:https
├─ authority:api.example.com:8443
│ ├─ host:api.example.com
│ └─ port:8443
├─ path:/orders
├─ query:source=web
└─ fragment:detailscheme 指定 URI 方案。https 表示按 HTTPS 语义访问资源,默认端口通常是 443;示例显式使用 8443,因此连接目标端口是 8443。随后客户端还要解析地址、建立网络连接并校验 TLS,方案名称本身不会完成这些动作。
authority 表示命名机构,HTTP URL 中最常见的组成是主机和可选端口。URI 通用语法还允许 userinfo,但把用户名、密码或令牌写入 URL 容易泄漏到历史记录和日志,现代 HTTP API 不应依赖这种方式传递凭据。
host 可以是域名、IPv4 地址或用方括号包围的 IPv6 地址。域名需要解析为候选地址;IP 字面量可以跳过普通域名解析,但仍可能经过代理,也仍需面对路由、端口和 TLS 身份校验。
path 表示分层路径。/orders 常被应用路由解释为订单资源,但 HTTP 并不规定这个业务含义。代理可以按路径转发,Servlet 容器和 Spring MVC 也会继续对路径做上下文、Servlet 映射与处理器匹配。
query 是请求目标的一部分。筛选、分页、排序和可选控制参数经常放在这里,但具体语义由接口契约规定。查询字符串容易进入浏览器历史、代理日志、访问日志、监控标签和缓存键,不适合承载密码、访问令牌或其他敏感值。
fragment 是客户端片段标识。浏览器可以用 #detail 定位页面区域或交给前端路由处理,但它不会作为 HTTP 请求目标发送给服务器。服务端访问日志通常只能看到 /orders?source=web。
URI 的通用组成和解析规则由 RFC 3986 定义;浏览器中的 URL 解析、规范化和序列化以 WHATWG URL Standard 为准。
域名的层级与组成
域名是 DNS 分层名称空间中的名称。圆点分隔的每一部分称为一个 label,书写顺序从最具体的名称到最上层名称。
api.shop.example.com.
├─ .:DNS 根
├─ com:顶级域名(TLD)
├─ example.com:注册域层级;example.com 是保留示例域名
├─ shop:子域
└─ api:更下一级标签,常被用作服务主机名末尾的点表示名称已经到达 DNS 根,这种形式称为绝对域名或完全限定域名。日常 URL 通常省略末尾点。顶级域名既有 .com 这类通用顶级域,也有国家或地区代码顶级域;完整委派信息可以在 IANA Root Zone Database 查询。
注册域名、子域、DNS Zone 和主机名描述不同对象。组织取得可注册域的管理权后,可以在其下创建子域或主机名,也可以把某个子域委派给另一组权威名称服务器。DNS Zone 是这组服务器实际负责的数据范围,可能覆盖整个注册域,也可能只管理其中一部分。
主机名是域名在标识主机或网络服务时的用法。api.example.com 可以作为 URL 的 host,但域名本身不包含 https、端口、路径和查询参数。一个域名可以没有 A/AAAA 地址记录,也可以返回多个地址;多个域名也可以共享同一个 IP,因此“域名”“服务器”和“IP 地址”不能互换使用。
国际化域名允许应用显示非 ASCII 字符,进入传统 DNS 基础设施前由 IDNA 转换为 ASCII 兼容的 A-label,常见形式以 xn-- 开头。转换规则和字符限制见 IDNA 协议。视觉相近字符可能造成欺骗,日志、安全规则和证书校验需要同时考虑显示形式与 ASCII 形式。
可注册边界也不总是“最后两个 label”。例如某些公共后缀本身含有多个 label。浏览器处理 Cookie 等安全边界时会使用公共后缀规则,应用不应自行截取最后两段来判断“主域名”。
| 对象 | 表示什么 | 是否包含端口 | 是否包含路径 | 主要管理或解析者 |
|---|---|---|---|---|
| 域名 | DNS 分层名称 | 否 | 否 | 域名持有者、注册体系和 DNS 管理者 |
| 主机名 | 用于标识主机或服务的域名 | 否 | 否 | DNS 与具体应用 |
| IP 地址 | 网络层接口或目标地址 | 否 | 否 | IP 网络与路由系统 |
| URL | 资源的定位与访问标识 | 可以 | 可以 | 客户端 URL 解析器 |
| Origin | scheme、host、port 三元组 | 是 | 否 | 浏览器安全模型 |
| DNS Zone | 一组权威管理的 DNS 数据 | 否 | 否 | 权威名称服务器 |
DNS 术语的精确定义见 RFC 9499。
同一个域名在不同协议层的作用
api.example.com 可能在多层重复出现,但每一层使用它解决的问题不同。
api.example.com
├─ URL host:客户端确定访问目标
├─ DNS query name:查询候选地址
├─ TLS SNI:握手时选择服务端证书或虚拟站点
├─ 证书 SAN:客户端校验服务端身份
├─ HTTP Host / :authority:应用层虚拟主机和路由
└─ Cookie Domain:限制 Cookie 可发送的域名范围这些值在简单部署中通常相同。经过别名解析、正向代理、TLS 终止、上游重写或服务网格后,它们可能不同。例如客户端对 api.example.com 做 DNS 查询,连接到 CDN 地址,在 TLS ClientHello 中发送相同 SNI;CDN 校验证书并终止 TLS 后,可以用另一条连接访问内部主机,但转发的 HTTP Host 或 :authority 仍可能保留原域名供上游路由。
URI、URL、URN 与资源标识
URI 是统一资源标识符的总称。URL 强调通过访问机制定位资源,URN 使用 urn scheme 在特定命名空间中标识名称。一个 URI 可以同时具有名称和定位作用。Web 后端最常直接处理 URL;接口设计操作的是资源或动作,数据库表、Java 类和 URL 路径之间没有必要机械对应。
同一个业务资源可以有多个 URL,同一 URL 也可以通过内容协商返回不同表示。URL 完成协议层标识;资源是否存在、当前主体是否有权访问,要等服务器处理后才能确定。
相对地址、路径归一化与字符编码
相对引用必须结合基准 URL 解析。以下三个引用含义不同:
基准 URL:https://api.example.com/v1/users/42
/orders → https://api.example.com/orders
orders → https://api.example.com/v1/users/orders
../orders → https://api.example.com/v1/orders百分号编码用于把不能直接出现在某个 URI 组成部分中的字节表示为 %HH。编码规则与所在部分有关:路径中的 / 是层级分隔符,查询中的 & 和 = 常被参数解析器解释为分隔符;对整个 URL 使用同一种“统一编码”会改变结构。
Unicode 文本通常先按 UTF-8 得到字节,再做百分号编码。客户端、代理和应用需要对“何时解码、解码几次、归一化后再匹配还是先匹配”保持一致。重复解码、混合编码和对 .、..、斜杠的不同规范化可能造成路由绕过或路径穿越。
Origin 与浏览器安全边界
Origin 由 scheme、host 和 port 组成:
origin = scheme + host + port| URL | 与 https://api.example.com/orders 是否同源 | 原因 |
|---|---|---|
https://api.example.com/users | 是 | scheme、host、port 相同,path 不参与判断 |
http://api.example.com/orders | 否 | scheme 不同 |
https://www.example.com/orders | 否 | host 不同 |
https://api.example.com:8443/orders | 否 | port 不同 |
同源规则决定浏览器怎样隔离页面的读取能力。它与网络可达、服务端授权分属不同层次;跨源请求是否需要预检、响应能否被脚本读取、Cookie 是否自动携带,还取决于 Fetch、CORS 和 Cookie 规则。
从 URL 得到 HTTP 请求目标
完整 URL 不一定原样写入请求行。客户端直接访问源服务器时,HTTP/1.1 最常使用 origin-form:
POST /orders?source=web HTTP/1.1
Host: api.example.com:8443绝对 URL 的 scheme 和 authority 没有消失:scheme 已影响连接与安全通道,authority 由 Host 字段表达。访问正向代理时可以使用 absolute-form;CONNECT 使用 authority-form;服务器级 OPTIONS * 使用 asterisk-form。请求目标的四种标准形式及其规则见 HTTP Semantics。
HTTP 请求的方法、字段与消息体
HTTP 请求的结构
HTTP/1.1 报文可以直接阅读。下面的请求包含请求行、请求字段、分隔空行和 JSON 消息体:
POST /orders?source=web HTTP/1.1
Host: api.example.com:8443
Authorization: Bearer <token>
Content-Type: application/json
Accept: application/json
Idempotency-Key: <operation-id>
traceparent: <trace-context>
Content-Length: <bytes>
{"sku":"BOOK-001","quantity":1}HTTP 请求
├─ method:POST
├─ request target:/orders?source=web
├─ protocol version:HTTP/1.1
├─ fields:Host、Authorization、Content-Type 等
└─ content:JSON 字节HTTP/2 和 HTTP/3 不使用相同的文本起始行。方法、scheme、authority 和 path 由 :method、:scheme、:authority、:path 等伪字段表达,普通字段和消息体由帧承载。线缆格式不同,不会把 POST、Content-Type 或状态码变成另一套应用语义。
HTTP 协议与接口契约
HTTP 规定方法、请求目标、字段、状态码和消息内容的通用语义;接口契约规定 /orders 表示什么、接受哪些参数、JSON 有哪些字段、字段能否为空、业务错误如何表示、兼容版本怎样演进。
HTTP 协议
├─ 方法、目标、字段、状态和缓存语义
└─ 消息如何交换
接口契约
├─ 资源或操作
├─ 参数位置与数据结构
├─ 认证授权要求
├─ 正常结果与业务错误
└─ 兼容与版本规则REST、RPC、GraphQL 和 HTML 表单都可以运行在 HTTP 之上。JSON 负责数据表示,POST 规定方法语义;二者都无法单独确定接口风格。OpenAPI 可以描述 HTTP API 的路径、操作、参数、请求体和响应,JSON Schema 可以描述 JSON 实例结构,认证、校验和业务规则仍由运行中的系统执行。
常用 HTTP 方法
| 方法 | 核心语义 | 常见用途 | 请求内容 |
|---|---|---|---|
GET | 获取目标资源的当前表示 | 查询详情、列表、下载 | 规范没有为 GET 内容定义通用语义,部分实现会拒绝 |
HEAD | 与 GET 相同,但响应不包含消息内容 | 探测元数据、缓存校验 | 通常无内容 |
POST | 让目标资源按自身语义处理提交内容 | 创建、命令、表单、批处理 | 常见 |
PUT | 用提交的表示创建或替换目标资源状态 | 按已知 URI 全量写入 | 常见 |
PATCH | 按补丁文档对资源做部分修改 | 局部更新 | 常见,语义取决于补丁媒体类型 |
DELETE | 请求移除目标资源与当前功能的关联 | 删除或停用资源 | 通常无内容,接口仍可定义 |
OPTIONS | 查询目标资源或服务器支持的通信选项 | 能力探测、CORS 预检 | 通常无内容 |
方法还有三个容易混淆的属性:
| 属性 | 含义 | 不能推出什么 |
|---|---|---|
| Safe | 客户端没有请求改变服务器状态 | 不代表没有日志、计费、统计或其他附带影响 |
| Idempotent | 多次执行相同请求的预期效果与一次相同 | 不代表只执行一次,也不保证每次响应字节相同 |
| Cacheable | 响应允许在满足条件时复用 | 不代表所有实现都会缓存,也不只由方法决定 |
GET、HEAD、OPTIONS 和 TRACE 定义为 safe;safe 方法也是 idempotent。PUT 和 DELETE 定义为 idempotent,但不是 safe。POST 通常不是 idempotent,不过接口可以通过稳定操作标识和服务端约束实现业务幂等。完整方法属性可以查询 IANA HTTP Method Registry。
CONNECT 用于让代理建立到目标 authority 的隧道,常见于 HTTPS 经正向代理;TRACE 用于回显请求链路诊断,生产入口通常因安全和信息暴露风险而禁用。CRUD 是数据操作分类,HTTP 方法是协议语义,两者没有强制一一对应关系。
请求数据的位置
| 位置 | 适合表达 | 是否进入 URL | 常见工程影响 |
|---|---|---|---|
| Path | 资源层级、资源标识 | 是 | 参与路由与缓存键,常进入访问日志 |
| Query | 筛选、排序、分页、搜索和可选控制 | 是 | 常进入日志、历史和缓存键,长度受实现限制 |
| Header field | 协议元数据、协商、认证、条件与追踪 | 否 | 代理可能读取或改写,字段大小有限制 |
| Cookie | 浏览器按作用域自动携带的状态 | 否 | 每次匹配请求都可能携带,涉及 CSRF 与隐私 |
| Message content | 表示、命令、表单、文件或数据流 | 否 | 受媒体类型、大小、缓冲、解析和安全限制 |
字段位置会直接改变协议语义和基础设施行为。资源标识放入 Body 会让通用路由、缓存和权限规则难以识别;访问令牌放进 Query 容易泄漏;大型筛选对象放进 Header 可能触发代理或容器的字段大小限制。能被某个框架解析,只解决了实现可行性。
请求字段的职责
HTTP 字段为请求补充目标、表示、状态和控制信息。字段名不区分大小写,但值是否可合并、是否允许重复,要由对应字段定义决定。
请求字段
├─ 目标与路由:Host / :authority
├─ 表示与协商:Content-Type / Content-Length / Accept / Accept-Encoding
├─ 身份与状态:Authorization / Cookie
├─ 缓存与条件:Cache-Control / If-None-Match / If-Modified-Since
├─ 来源与浏览器:Origin / Referer
├─ 范围与传输:Range / Expect
├─ 代理信息:Forwarded / Via / X-Forwarded-*
└─ 关联信息:traceparent / 请求标识 / 业务操作标识Host 或 HTTP/2、HTTP/3 的 :authority 用于选择目标虚拟主机和路由。它表达应用层 authority,不应从 TCP 目标 IP 反推,因为多个域名可以共享同一地址。
Content-Type 描述当前请求内容的媒体类型;Accept 表达客户端愿意接收哪些响应媒体类型;Accept-Encoding 表达可接受的内容编码。三者方向不同。
Authorization 携带当前请求的认证凭据,Cookie 携带客户端按域名、路径和安全属性筛选出的状态。代理和应用日志不得默认记录完整凭据。
条件请求字段把本地缓存状态带回服务器。例如 If-None-Match 携带 ETag,服务器可以返回 304 Not Modified;If-Match 可以把更新限制为某个已知版本,避免无条件覆盖并发修改。
Forwarded、Via 和常见的 X-Forwarded-* 描述代理路径或原始连接信息。来自不可信客户端的同名字段可以伪造,只有处在明确信任链中的代理写入值才适合用于安全判断。
traceparent 传播分布式追踪上下文;普通请求 ID 用于关联一次入口请求;Idempotency-Key 一类业务操作标识用于识别可重放写操作。三者目的不同,不能用一个随机字符串替代所有身份。
全部标准字段及状态可查询 IANA HTTP Field Name Registry。
请求体与媒体类型
HTTP 消息内容在传输层面是一串字节。接收方需要结合媒体类型、字符集、内容编码和消息边界,才能正确解释它。
消息内容字节
├─ Media Type:字节表示什么
├─ Charset:文本怎样解码
├─ Content-Encoding:内容是否经过 gzip 等编码
└─ Message Framing:内容在哪里结束| 请求内容 | 常见媒体类型 | 主要结构 | 常见用途与限制 |
|---|---|---|---|
| 无内容 | 无 | — | GET、HEAD、DELETE 等常见,但是否允许由方法和接口共同决定 |
| JSON | application/json | 对象、数组和基础值 | API 常用;需处理语法、类型、精度、大小和未知字段 |
| URL 编码表单 | application/x-www-form-urlencoded | 名值对 | HTML 表单常用;字符编码和重复键需要约定 |
| 多部分表单 | multipart/form-data; boundary=... | 多个 part | 字段与文件混合上传;每个 part 可有自己的字段和类型 |
| 文本或 XML | text/plain、application/xml 等 | 字符或文档树 | Webhook、旧系统和文档接口常见;字符集和解析安全重要 |
| 原始二进制 | application/octet-stream | 任意字节 | 文件或设备数据;业务类型常需额外字段说明 |
| 具体二进制 | image/png、application/zip 等 | 对应格式 | 接收方可按已知格式校验和处理 |
| 结构化二进制 | Protobuf、CBOR 等 | 模式或类型驱动 | 体积和解析效率较高,需要双方共享契约 |
| 流式内容 | 具体媒体类型 | 分段到达 | 大文件或持续数据;不能默认一次性加载到内存 |
JSON 的标准语法见 RFC 8259。解析成功说明字节符合 JSON 语法;字段契约、主体权限和业务条件由后续层次分别判断。
application/x-www-form-urlencoded 把表单字段编码为名值对;multipart/form-data 使用 boundary 分隔多个 part,每个 part 可以带 Content-Disposition、名称、文件名和媒体类型。boundary 只负责划分边界,客户端文件名和媒体类型都不可信。多部分表单的标准格式见 RFC 7578。
媒体类型描述内容的意义,消息分帧描述内容的结束位置。HTTP/1.1 请求通常通过 Content-Length 或 Transfer-Encoding: chunked 确定内容边界;两者都没有时,请求内容长度通常为零,不能依靠关闭连接来划分请求内容。部分 HTTP/1.1 响应可以由连接关闭确定结束位置。HTTP/2 和 HTTP/3 通过 DATA 帧及流结束标记承载内容。尾部字段 trailer 出现在内容之后,只适用于允许以尾部传递的字段。
多个中间层若对 Content-Length 与 Transfer-Encoding 的优先级或合法性理解不同,可能把同一字节流切成不同请求,形成 HTTP 请求走私。入口代理和源服务器必须遵守一致的协议解析规则,并拒绝含糊或非法的消息长度。
Cookie、Session、认证与浏览器策略
Cookie 是 HTTP 状态管理机制中的名值数据,由服务器通过 Set-Cookie 设置,客户端按 Domain、Path、Secure、SameSite、有效期等属性决定后续是否携带。Session 是服务端维护的会话状态;常见做法是 Cookie 只保存不透明会话标识,真正状态保存在应用、共享缓存或数据库中。
浏览器 Cookie
└─ session_id=<opaque-id>
└─ 服务端 Session Store
├─ 用户标识
├─ 登录状态
└─ 会话属性| 机制 | 凭据或状态位置 | 发送方式 | 主要注意事项 |
|---|---|---|---|
| Cookie + Session | Cookie 保存会话标识,状态在服务端 | 浏览器按作用域自动携带 | CSRF、Session 固定、共享存储、过期与退出 |
| Bearer Token | Token 自带或引用身份信息 | 常放 Authorization: Bearer | 泄漏者可直接使用,需控制有效期、受众和存储位置 |
| API Key | 调用方标识或密钥 | 常放专用字段或 Authorization | 轮换、权限粒度、日志脱敏和来源限制 |
| mTLS | 客户端证书和私钥 | TLS 握手期间 | 证书签发、信任链、轮换和服务身份映射 |
认证回答“调用方是谁或持有什么凭据”,授权回答“该主体能否执行当前操作”。TLS 服务端证书证明连接到的服务身份,不能代替用户登录和业务授权。
浏览器在实际发送请求前后还可能执行以下处理:
浏览器请求处理
├─ URL 解析与 HSTS 升级
├─ Service Worker 拦截或直接响应
├─ HTTP 缓存查找与条件请求
├─ Cookie 作用域和凭据模式
├─ 同源判断与 CORS 预检
├─ 连接复用或新建连接
└─ 重定向、响应暴露与缓存更新CORS 控制页面脚本能否读取跨源响应,服务端授权则判断主体能否执行动作。CSRF 利用浏览器自动携带 Cookie 等凭据诱导服务器执行操作,属于另一类风险。一个跨源请求可能已经改变服务器状态,最后只是在浏览器端因 CORS 检查失败而无法读取响应。
Service Worker 可以拦截请求、返回缓存内容或生成新的网络请求;浏览器缓存也可能在不访问源服务器的情况下直接得到表示。看到页面有结果,不能据此推断本次一定产生了新的服务端请求。Fetch 与 CORS 的处理模型见 WHATWG Fetch Standard,Cookie 基础语义见 RFC 6265。
DNS、IP、TCP/QUIC 与 TLS
域名注册、DNS Zone 与委派
域名出现在 URL 中,并不表示它已经配置了可访问的网站。公共域名从注册到可解析通常涉及以下角色:
域名持有者(Registrant)
└─ 通过注册商(Registrar)办理注册和管理
└─ 注册局(Registry)维护对应顶级域登记
└─ 父 Zone 通过 NS 记录完成委派
└─ 权威名称服务器发布该 Zone 的 DNS 记录域名持有者获得的是某段命名空间的管理权,不会自动得到服务器、IP 地址、DNS 记录或 TLS 证书。注册商负责面向持有者办理注册与变更,注册局维护顶级域下的登记数据,DNS 托管服务负责运行权威名称服务器;同一家公司可以同时提供多种服务,但角色仍然不同。
DNS domain 是名称空间中的节点或子树,DNS Zone 是由一组权威服务器实际管理的数据范围。example.com 与它的子域可以放在同一个 Zone,也可以通过 NS 记录把 shop.example.com 委派给另一组权威服务器。父 Zone 只需知道子 Zone 由谁负责;子 Zone 再发布自己的主机和服务记录。
如果被委派的名称服务器名称本身位于这个子 Zone 内,父 Zone 还需要提供相应地址作为 glue,避免解析器为了找到名称服务器地址而陷入循环依赖。DNSSEC 则通过签名和信任链验证 DNS 数据的来源与完整性;它不加密查询内容,也不能代替 HTTPS 的服务器身份校验。
常用 DNS 记录
DNS 保存多种带类型记录,地址映射只是其中一部分。
| 记录 | 回答的问题 | 后端系统中的常见用途 |
|---|---|---|
A | 该名称对应哪个 IPv4 地址 | Web、API 和基础设施地址 |
AAAA | 该名称对应哪个 IPv6 地址 | IPv6 服务地址 |
CNAME | 该名称是哪个规范名称的别名 | CDN、托管平台或多环境别名 |
NS | 哪些名称服务器对该 Zone 权威 | Zone 委派 |
SOA | Zone 的管理与序列信息是什么 | 权威区起点、刷新和负缓存相关参数 |
TXT | 该名称关联哪些文本数据 | 域名验证、邮件安全策略和服务验证 |
MX | 邮件应投递到哪些服务器 | 邮件路由,不用于普通 HTTP 路由 |
SRV | 某服务在哪个主机和端口 | 部分服务发现协议 |
CAA | 哪些证书机构可为域名签发证书 | 约束公开证书签发 |
SVCB / HTTPS | 服务端点和连接参数是什么 | 服务绑定、替代端点与 HTTP 连接提示 |
CNAME 在 DNS 层把一个名称指向另一个名称。客户端解析到最终地址后继续连接,地址栏保持原样;HTTP 3xx 发生在收到服务端响应之后,客户端据此访问新的 URL。
TXT 记录能存放文本,不表示其中内容天然可信。SRV、SVCB 和 HTTPS 是否生效取决于客户端和应用协议是否实现对应规则。全部 DNS 参数和资源记录类型可查询 IANA Domain Name System Parameters,SVCB 与 HTTPS 记录的语义见 RFC 9460。
主机名解析
应用把 api.example.com 交给名称解析系统后,常见查询关系如下:
应用通常调用操作系统或运行时提供的解析接口,由解析链处理迭代查询。本机 hosts 文件、本地缓存、浏览器缓存、JVM 缓存、企业 DNS 和递归解析器都可能提前给出结果。浏览器或应用也可能使用加密 DNS 服务;查询传输和解析器选择随之变化,域名层级与权威数据关系保持不变。
递归解析器在没有缓存时,根据根、顶级域和权威委派逐级找到负责目标名称的服务器,再查询 A、AAAA 或其他所需记录。遇到 CNAME 时继续查询其目标名称。TTL 告诉缓存可以复用数据的时间;名称不存在或记录不存在也可以被负缓存。
DNS 为连接提供候选地址。A/AAAA 查询成功后,端口监听、路由、防火墙、TLS、HTTP 和应用状态仍要继续验证。抓包中没有出现新的 DNS 查询时,客户端可能使用了缓存结果或已有连接。
一个名称可以返回多个 IPv4 和 IPv6 地址。客户端会按自身地址选择策略尝试连接,现代实现常错峰竞争 IPv6 与 IPv4 连接,避免某一协议栈异常拖慢整体访问,这类策略由 Happy Eyeballs 规范化。CDN、全局流量调度、分区 DNS 和企业内外网解析还可能根据解析器位置或策略返回不同答案。
DNS 的概念与查询模型见 RFC 1034,当前术语汇总见 RFC 9499。
IP、路由与端口
IP 地址用于网络层寻址,端口用于在一台主机或网络命名空间中标识传输层端点。访问 203.0.113.10:8443 至少需要同时满足地址可路由、目标端口可达和对应协议存在。
客户端端点 服务端端点
192.0.2.20:53124 ───────────→ 203.0.113.10:8443
源 IP 源端口 目标 IP 目标端口TCP 连接通常由源 IP、源端口、目标 IP、目标端口和协议共同区分。QUIC 运行在 UDP 之上,还使用连接 ID 支持连接识别和网络路径变化,不能完全按 TCP 四元组理解其连接生命周期。
路由表决定数据包的下一跳;局域网链路还需要把下一跳 IP 解析为链路层地址;NAT 可能改写源或目标地址与端口;主机防火墙、云安全组、网络 ACL 和 Kubernetes NetworkPolicy 可以在不同位置放行或丢弃流量。域名解析正确并不能绕过这些网络条件。
连接错误携带的线索各不相同。立即收到 connection refused,通常表示数据到达某个目标网络栈,但该端口没有接受连接或被设备主动拒绝。连接超时更常见于数据包或返回路径被丢弃,也可能由目标严重过载引起。no route to host 指向本机或中间网络缺少可用路由;具体错误仍受操作系统和网络设备行为影响。
TCP 连接与连接复用
TCP 为上层提供可靠、有序的双向字节流。建立连接时,双方通过三次握手确认初始序列号和收发能力:
TCP 会对数据分段、编号、确认和重传,并通过流量控制避免接收方缓冲区被压垮,通过拥塞控制适应网络承载能力。它只提供字节流,不保留 HTTP 消息、JSON 对象或文件块的天然边界;HTTP 必须使用自己的分帧规则区分消息。
连接成功后,传输通道已经成立,HTTP 处理尚未因此完成。服务端可能还没收到完整请求,TLS 可能仍在握手,反向代理也可能无法连接上游。TCP 建连时间、TLS 握手时间和 HTTP 首字节时间应分别观察。
连接关闭也有双方状态和半关闭过程。主动关闭方通常会经历 TIME_WAIT,用于处理迟到报文并避免旧连接数据污染新连接。大量短连接会增加握手、内核状态和端口消耗,因此 HTTP 客户端、代理和数据库驱动通常使用连接池复用连接。
HTTP Keep-Alive 允许连接在一次请求—响应后继续承载 HTTP 消息;TCP keepalive 则通过探测判断长时间空闲的传输连接是否仍存活。池中的连接可能已被对端或中间设备关闭,复用时仍要处理失效。TCP 的标准行为见 RFC 9293,更深入的状态、内核队列和超时进入连接建立、复用与关闭。
TLS 握手与服务端身份
HTTPS 是在受 TLS 保护的连接上交换 HTTP。TLS 同时协商密码参数、建立会话密钥并验证通信身份,但它不判断接口权限和业务数据是否合法。
SNI 在握手早期告诉服务端客户端希望访问的主机名,使同一 IP 和端口可以为多个域名选择证书或虚拟站点。证书通常在 Subject Alternative Name 中列出允许的 DNS 名称或 IP 标识;客户端需要验证证书链是否到达受信任根、证书是否有效、用途是否合适,以及目标 host 是否匹配证书标识。
证书校验同时包含信任链和目标身份。链条有效而主机名不匹配时,客户端仍应拒绝连接;直接连接 IP 字面量时,证书也必须对该 IP 标识有效。域名曾解析到这个地址,不会把域名证书自动转换成 IP 身份。
ALPN 用于在 TLS 握手中协商上层协议,例如 HTTP/1.1 或 HTTP/2。会话恢复可以减少后续握手成本,但恢复仍有安全策略和有效期。mTLS 还要求服务端验证客户端证书,常用于服务身份;证书身份如何映射成应用主体,仍由网关或应用认证系统决定。
TLS 1.3 的协议定义见 RFC 8446,SNI 扩展见 RFC 6066,ALPN 见 RFC 7301。证书、HTTPS 与常见握手故障的进一步处理见DNS、TLS 与 HTTPS。
HTTP/1.1、HTTP/2 与 HTTP/3
三个版本共享 HTTP 方法、状态码、字段和内容语义,主要差异在消息怎样编码、连接怎样承载并发以及丢包如何影响流。
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 下层传输 | TCP;HTTPS 时再使用 TLS | TCP + TLS 是浏览器中的常见部署 | QUIC over UDP,集成 TLS 1.3 |
| 消息表达 | 文本起始行和字段,内容按规则定界 | 二进制帧、流和伪字段 | QUIC 流上的 HTTP 帧和伪字段 |
| 同连接并发 | 常通过多个连接;流水线部署较少 | 多个 HTTP 流复用一条连接 | 多个流复用一条 QUIC 连接 |
| 字段压缩 | 无协议级动态字段压缩 | HPACK | QPACK |
| 流量控制 | 主要依赖 TCP | 连接级和流级 HTTP/2 控制 | QUIC 连接级和流级控制 |
| 丢包影响 | 同一 TCP 连接后续字节等待重传 | 多个 HTTP 流受 TCP 队头阻塞影响 | 一个流的数据丢失通常不阻塞其他流的数据交付 |
| 常见协商 | 明确选择或 TLS ALPN | TLS ALPN h2 | Alt-Svc、HTTPS 记录或既有配置后建立 QUIC |
HTTP/2 的多路复用消除了 HTTP/1.1 应用层按序等待的许多问题,但所有流仍共享一条 TCP 字节流,底层丢包会影响整条连接的数据交付。HTTP/3 把 HTTP 映射到 QUIC 流,不再使用 TCP;QUIC 连接 ID 还允许在一定条件下处理客户端网络路径变化。
HTTP/3 的收益取决于 UDP 可达性、握手与恢复状态、丢包特征、服务器实现、代理支持和负载模式,因此要用目标网络下的实测结果评估。HTTP/2 与 HTTP/3 的完整帧、字段压缩和流状态分别见 RFC 9113 与 RFC 9114,QUIC 传输见 RFC 9000。
一次请求不一定重新解析和建连
客户端准备发送请求时,会先检查能否直接使用已有结果和通道:
连接是否可复用至少取决于 scheme、authority、代理配置、协议版本、证书与连接状态。HTTP/2 或 HTTP/3 可以在同一连接中并发承载多个流;代理还会分别维护客户端连接和上游连接池。一次客户端请求可能复用前端连接,却触发代理新建上游连接,反之亦然。
DNS TTL 控制缓存记录的复用时间,不会主动关闭已有连接;缓存中仍有地址时,旧连接也可能已经失效。名称、地址、连接和 HTTP 请求各有生命周期,排障与容量分析需要分别计时。
代理、容器网络与应用监听
请求进入应用前的转发链路
客户端建立的连接不一定直接终止在业务进程。一个公开 API 的常见入口由多个可选节点组成:
客户端
├─ 可选:正向代理
└─ 公网入口
├─ 可选:CDN / DDoS 防护 / WAF
├─ 云负载均衡器
├─ 反向代理或 API 网关
├─ 可选:Kubernetes Ingress / Gateway
├─ Service 与后端端点
└─ 应用进程的监听 socket部署层级随系统规模和平台变化。小型服务可以由反向代理直接转发到单个应用进程;云原生系统可能同时存在云负载均衡、Ingress Controller、Service 和 Pod。真实链路应根据 DNS 答案、连接终止位置、代理配置和运行环境还原。
正向代理代表客户端访问外部服务,服务端看到的直接对端通常是代理。反向代理代表后端服务接收客户端流量,客户端访问的是代理公开的地址。CDN 是面向内容分发和边缘处理的反向代理网络;API 网关通常还承担接口路由、认证、限流、配额和协议转换。名称可以重叠,关键是它实际终止了哪条连接、读取了哪些协议数据、执行了什么策略。
四层转发与七层代理
“四层”和“七层”描述代理主要依据哪一层信息转发,同一设备仍可能处理多个协议层。
| 类型 | 主要依据 | 能够直接识别的内容 | 常见能力 |
|---|---|---|---|
| 四层负载均衡 | IP、端口、传输协议和连接状态 | TCP 或 UDP 流量,通常不理解 HTTP 路径 | 连接转发、地址转换、健康检查、源地址保持 |
| 七层 HTTP 代理 | scheme、host、方法、路径、字段和内容元数据 | HTTP 请求与响应 | 按域名或路径路由、重写、鉴权、限流、缓存、压缩 |
四层转发通常不修改 HTTP 消息,但 NAT 或代理模式可能改变应用看到的源地址。七层代理分别维护“客户端到代理”和“代理到上游”两段连接,两段连接可以使用不同 IP、端口、TLS 状态甚至 HTTP 版本。客户端的 HTTP/2 可能在代理处终止,内部链路则按另一种 HTTP 版本和加密策略连接应用。
TLS 在哪里终止
TLS 终止点决定谁持有证书私钥、谁能读取 HTTP 内容,以及后续链路是否仍受 TLS 保护。
| 模式 | 客户端侧 | 代理到应用侧 | 主要特征 |
|---|---|---|---|
| TLS termination | HTTPS | HTTP | 代理解密后转发明文 HTTP,内部网络必须有相应信任边界 |
| TLS re-encryption | HTTPS | HTTPS | 代理解密并执行七层策略,再用新的 TLS 连接访问上游 |
| TLS passthrough | HTTPS | 原始 TLS 流量继续转发 | 中间层通常不能读取 HTTP 路径和字段,只能依据连接信息或有限握手信息转发 |
| mTLS to upstream | HTTPS 或 mTLS | mTLS | 上游除加密外还验证代理或调用方的客户端证书 |
TLS 在代理终止后,应用从本地连接属性看到的可能是 HTTP。若应用需要生成绝对 HTTPS 地址、判断安全 Cookie 或记录原始主机,必须从可信代理传递的信息恢复外部请求上下文。不能无条件信任客户端自行提交的 Forwarded 或 X-Forwarded-* 字段,否则可能造成主机伪造、错误重定向、日志污染或安全策略绕过。
代理对 HTTP 请求的处理
七层代理收到完整或部分请求后,可以按配置执行一组有顺序的处理:
连接与 TLS 策略
└─ HTTP 解析与大小限制
└─ 域名、路径和方法匹配
├─ 认证、授权或 WAF 检查
├─ 限流、配额与并发控制
├─ 路径、主机和字段重写
├─ 缓存命中:直接返回响应
└─ 选择健康上游并转发
├─ 连接池与协议选择
├─ 请求或响应缓冲
├─ 超时、重试和熔断
└─ 响应字段处理与返回路由匹配可以使用主机名、路径前缀、精确路径、方法或字段。路径重写会使应用看到的路径不同于客户端 URL;主机重写会改变上游 Host 或 :authority。认证和限流也可能在业务代码运行前结束请求。
请求体大小限制可能存在于 CDN、WAF、网关、反向代理、Servlet 容器和应用框架多个位置,最终可接受上限是整条链路中实际生效的最小限制。代理缓冲完整请求体后再转发,可以保护上游免受慢速上传影响,却会增加首包到达应用的等待和代理磁盘或内存消耗;关闭缓冲可以支持流式传输,但会让上游连接占用更久。
代理通常为不同阶段设置独立超时:建立上游连接、发送请求、等待响应头、读取响应数据以及保持空闲连接。代理返回 504 Gateway Timeout 时,上游任务可能仍在运行;自动重试非幂等请求还可能造成重复操作。
Forwarded 是标准化的转发信息字段,可以表达原始协议、主机和客户端地址;X-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Host 是广泛使用的非标准字段。可信代理应删除或规范化来自不可信网络的同名字段,再追加自己确认的信息。应用则应配置可信代理数量或地址范围,从正确位置读取原始客户端信息。Forwarded 的标准语义见 RFC 7239。
负载均衡与健康检查
负载均衡器从可用后端集合中选择端点。轮询、最少连接、一致性哈希和带权选择解决的是不同分配目标,不能仅凭算法名称判断效果;长连接、请求耗时差异、连接复用和会话状态都会改变实际负载。
发现的后端端点
├─ 启动探针:应用是否已经完成启动
├─ 就绪探针:是否可以接收新流量
├─ 存活探针:是否需要重启进程或容器
└─ 业务健康检查:关键依赖是否满足服务条件健康检查记录的是检查路径在当时的结果。路径过浅时,无法处理真实业务的实例仍会留在流量池;路径过深又可能因共享依赖短暂波动同时摘除大量实例。停止服务时应先退出就绪状态,等待负载均衡停止分配新请求并处理存量连接,再结束进程,以减少发布期间的连接重置和间歇性错误。
粘性会话把同一客户端尽量路由到同一实例,可以暂时承载进程内会话状态,但会降低均衡能力并增加实例故障影响。更常见的可扩展设计是把必要会话状态放到共享存储,或者使用服务端可验证的令牌,同时明确撤销和过期机制。
Linux socket 与进程监听
应用接收 TCP 请求前,必须先在所在网络命名空间创建监听 socket,并绑定本地地址与端口。
网卡收到数据包
└─ Linux 内核网络栈
├─ 路由、防火墙与连接跟踪
├─ 根据目标地址、端口和协议查找 socket
├─ 未完成连接队列:等待 TCP 握手完成
└─ 已完成连接队列:等待应用 accept
└─ 应用获得已连接 socket
├─ 阻塞线程读取
├─ 非阻塞事件循环读取
└─ 交给协议解析和工作线程bind 指定进程在哪个本地地址和端口接收流量,listen 把 TCP socket 置为监听状态,accept 从已完成连接队列取得一条连接。监听 socket 与每个已连接 socket 是不同的内核对象;同一个监听端口可以连续接受大量连接,每条连接仍由各自的端点和状态区分。
常见绑定地址的含义如下:
| 绑定地址 | 含义 | 常见影响 |
|---|---|---|
127.0.0.1 | 仅 IPv4 回环接口 | 同一网络命名空间内可访问,宿主机或其他容器通常不能直接访问 |
0.0.0.0 | 所有可用 IPv4 本地地址 | 可从哪些网络访问仍由路由、防火墙和端口发布决定 |
| 某个具体 IP | 只在对应本地地址监听 | 地址变化或绑定到错误接口会导致其他入口不可达 |
::1 | 仅 IPv6 回环接口 | 只接受 IPv6 本地访问 |
:: | 所有可用 IPv6 本地地址 | 是否同时接受 IPv4 映射连接取决于系统配置 |
0.0.0.0 是服务端绑定“所有本地 IPv4 地址”时使用的通配地址,客户端应选择一条实际可达地址。监听状态说明内核存在匹配 socket;应用线程池、队列、协议处理和外部依赖还会继续影响请求结果。
内核和进程资源会在业务代码之前形成容量上限。等待连接队列过小会在突发建连时丢弃或延迟连接;文件描述符上限限制进程同时持有的 socket 和文件;线程池或事件循环过载会延迟读取;接收缓冲区、请求解析器和服务器连接上限也会产生背压。扩容应用实例不能自动消除单个入口代理、NAT 表或共享负载均衡器的上限。
Docker 的网络命名空间与端口发布
Docker 容器通常拥有独立网络命名空间、虚拟网卡、IP 地址、路由表和端口空间。因此,宿主机与容器内各自的 127.0.0.1:8080 指向两个监听范围。
外部客户端
└─ 宿主机地址:宿主机端口
└─ Docker 端口发布 / NAT
└─ 容器地址:容器端口
└─ 容器内应用监听地址:应用端口运行参数 -p 8080:8080 表示把宿主机端口 8080 发布到容器端口 8080,不负责启动应用,也不会修改应用实际监听端口。如果 Java 进程只绑定容器内 127.0.0.1:8080,转发到容器网卡地址的流量通常仍无法进入;容器服务一般需要监听 0.0.0.0:8080 或合适的具体接口。
EXPOSE 8080 在镜像中记录端口元数据和约定;宿主机端口仍要通过运行配置显式发布。同一用户定义网络中的容器通常通过服务名和容器端口通信,无须绕到宿主机发布端口。端口发布的当前行为与安全提示见 Docker port publishing。
Kubernetes 中的 Pod、Service 与入口
Kubernetes 把应用端点、稳定服务地址和外部入口拆成不同对象:
外部请求
└─ 云负载均衡器或集群入口
└─ Ingress / Gateway 的路由规则
└─ Service:稳定虚拟地址与服务发现名称
└─ EndpointSlice:可用 Pod 端点集合
└─ Pod IP:targetPort
└─ 容器内应用监听 socketPod IP 标识具体工作负载实例,Pod 重建后可能改变。Service 为一组后端提供稳定名称和虚拟访问入口,并通过选择器或显式端点关联 EndpointSlice。port 是 Service 暴露的端口,targetPort 是后端 Pod 接收流量的端口;二者可以不同。Ingress 或 Gateway 再依据主机名和路径把集群外 HTTP 流量路由到 Service。
Kubernetes Service 只有在端点与网络路径同时成立时才能转发请求。选择器错误会得到空端点集合,就绪探针失败会排除 Pod,targetPort 错误会把流量送往未监听端口,NetworkPolicy 还可能阻断命名空间或 Pod 之间的通信。CNI 插件负责实现 Pod 网络,Service 转发可能由 kube-proxy、内核规则、eBPF 数据面或云实现完成;排障应确认集群采用的实际方案。
Kubernetes 网络模型见 Cluster Networking,Service 与 EndpointSlice 见 Service,HTTP 入口资源见 Ingress 与 Gateway API。
请求到达业务代码前已经可能结束
业务方法没有留下日志时,请求仍可能已经到达更早的服务器节点,并在那里被缓存、拒绝、排队、超时或改写到其他上游。
| 最后确认的位置 | 已经证明 | 尚未证明 |
|---|---|---|
| DNS 返回地址 | 名称解析得到候选端点 | 连接可达、目标就是预期实例 |
| TCP 或 QUIC 连接建立 | 传输通道成立 | TLS、HTTP 和应用正常 |
| TLS 握手完成 | 已建立受保护连接并完成相应身份校验 | 请求通过代理或业务授权 |
| 边缘代理访问日志 | 代理解析到请求 | 请求已成功转发到应用 |
| 上游连接建立 | 代理连到某个后端端点 | 后端已读取完整请求 |
| 应用访问日志 | 应用服务器处理到 HTTP 层 | Controller 和事务已经执行 |
反向代理返回的 400、413、429、502、503 或 504,外观与应用返回的 HTTP 响应相同,但产生位置和含义可能完全不同。响应字段、代理日志、应用日志和分布式追踪需要共同标明实际处理节点。
Servlet 与 Spring MVC 请求处理
Java Web 把网络服务器接收的 HTTP 请求转换为 Java 对象,再通过标准容器和应用框架分派到业务入口。其他语言和框架的类名不同,但仍要完成协议解析、中间件处理、路由匹配、参数转换、业务调用和响应编码这些职责。
Web 服务器与 Servlet 容器的边界
Spring Boot 内嵌 Tomcat、Jetty 或 Undertow 时,应用进程同时包含监听网络端口的服务器和运行 Servlet 应用的容器。外置 Servlet 容器部署则由独立容器加载应用。两种形式的部署边界不同,请求处理责任基本相同。
已连接 socket
└─ Connector / 网络协议处理器
├─ 读取字节与管理连接
├─ 解析 HTTP 起始行、字段和消息体
├─ 执行字段、请求体和超时限制
└─ 创建 Servlet 请求与响应对象
└─ 选择 Web 应用和 FilterChain
└─ 调用目标 ServletConnector 把网络字节还原为 HTTP 消息。无效方法、格式错误的请求行、非法字段、超限请求体或读取超时可能直接在这一阶段失败。Servlet 容器随后依据请求 host、context path 和 servlet mapping 选择 Web 应用与 Servlet,并负责线程调度、请求对象生命周期、会话支持和响应提交。
HttpServletRequest 提供方法、URI、查询参数、字段、Cookie、输入流和连接信息;HttpServletResponse 用于设置状态、字段、Cookie 和输出内容。它们是当前请求在 Servlet API 中的可变视图,不等同于原始网络报文。代理重写、容器解码和参数解析都可能让对象中的值与客户端原始字节不同。
Servlet 规范定义的是容器与应用组件之间的契约,不规定 Controller、Service、Repository 这些项目分层。Servlet API 的请求分派、过滤、会话和异步处理语义见 Jakarta Servlet Specification。
Filter、Session 与安全过滤链
Filter 位于 Servlet 调用之前和之后,可以检查或包装请求与响应,也可以不再继续过滤链而直接返回。
容器
└─ Filter 1:请求 ID、编码或日志上下文
└─ Filter 2:跨域、认证或安全策略
└─ Filter 3:自定义审计或限流
└─ DispatcherServlet
← Filter 3 响应阶段
← Filter 2 响应阶段
← Filter 1 响应阶段Filter 的注册顺序会改变行为。读取请求体的 Filter 如果没有提供可重复读取的包装,后续 Controller 可能得到空输入流;过早写入并提交响应,会使后续异常处理无法修改状态和字段;异步分派还会涉及 Filter 支持的 dispatcher 类型。
Session 通常由会话标识关联服务端状态。浏览器常通过 Cookie 发送会话标识,Servlet 容器或会话组件再查找对应数据。会话标识只是索引或凭据,不应包含可预测值;登录后应防止会话固定攻击,并为过期、注销、并发会话和跨实例存储建立规则。
认证确认当前主体及其凭据,授权再判断这个主体能否对当前资源执行当前动作。Spring Security 通常通过独立 Filter 链读取 Cookie、Bearer Token、客户端证书映射结果或其他凭据,建立安全上下文,再按 URL、方法或方法级规则授权。身份可信之后,订单归属、租户范围和数据行级权限仍需由业务规则判断。
DispatcherServlet 的分派流程
Spring MVC 的前端控制器是 DispatcherServlet。它不直接实现每个业务接口,而是协调一组可替换组件完成请求映射和响应生成。
DispatcherServlet 的一次 REQUEST 分派(可能转入异步)
├─ HandlerMapping:按路径、方法、字段和映射条件选择 Handler
├─ HandlerInterceptor.preHandle
└─ HandlerAdapter:执行注解控制器
├─ 参数解析与转换
│ ├─ HandlerMethodArgumentResolver
│ ├─ 类型转换、格式化和数据绑定
│ ├─ HttpMessageConverter 读取请求体
│ └─ 按注解、参数类型与配置触发 Bean Validation
├─ Controller 方法
└─ HandlerMethodReturnValueHandler
├─ @ResponseBody / ResponseEntity
│ ├─ 确定状态、字段与媒体类型
│ ├─ ResponseBodyAdvice(匹配时)
│ └─ HttpMessageConverter 写响应体,响应可能已经 committed
├─ ModelAndView / 视图名:交回 DispatcherServlet
└─ Callable / DeferredResult:启动异步处理并退出当前分派
DispatcherServlet 接管 Handler 执行结果后
├─ 同步且无异常:HandlerInterceptor.postHandle
│ ├─ 响应体分支:不再渲染视图,响应可能已经提交
│ └─ 视图分支:ViewResolver 选视图并渲染
├─ 同步异常:HandlerExceptionResolver 尝试转换为响应或错误视图
├─ 同步请求完成:HandlerInterceptor.afterCompletion
└─ 已转异步:afterConcurrentHandlingStarted,退出当前分派
└─ 此时不调用 postHandle 和 afterCompletion,等待 ASYNC 再分派HandlerMapping 先查找匹配处理器。对于注解控制器,匹配条件可以包含路径、方法、查询参数、请求字段、Content-Type 和可接受响应类型。路径相同但方法不匹配、媒体类型不支持和无法生成客户端可接受类型,是不同的协议问题,不应统一伪装成“接口不存在”。
HandlerAdapter 隔离 DispatcherServlet 与不同处理器模型。对 @RequestMapping 方法,它会解析参数、执行转换和按需校验,再调用 Controller,并在自身内部执行返回值处理。视图型返回值先形成 ModelAndView,所以 postHandle 仍发生在视图渲染之前;@ResponseBody 和 ResponseEntity 则会在 HandlerAdapter 内经 HttpMessageConverter 写出,调用 postHandle 时响应可能已经提交。需要统一修改响应体或在写出前调整字段时,应使用匹配的 ResponseBodyAdvice、控制器通知或更早的响应包装机制,不能依赖 postHandle。
异步返回值是另一条生命周期。Controller 返回 Callable 或 DeferredResult 后,Spring MVC 调用 Servlet 异步处理;初次分派调用 AsyncHandlerInterceptor.afterConcurrentHandlingStarted,而不是 postHandle 和 afterCompletion,随后 DispatcherServlet 与当前 Filter 调用退出,但响应保持打开。异步结果就绪后,容器发起新的 ASYNC 分派,Spring MVC 从结果或异常继续处理,最终才完成响应。拦截器的同步时序见 Spring MVC Interception,响应体写出扩展点见 ResponseBodyAdvice,异步分派见 Spring MVC Asynchronous Requests。
拦截器围绕 Handler 执行,适合处理依赖 Spring MVC 映射结果的逻辑;Filter 工作在更外层,适合 Servlet 级请求和响应处理。二者生命周期、分派类型和能否赶在响应提交前修改内容都不同。
Spring MVC 的详细执行顺序见 DispatcherServlet processing sequence。
路径、查询、字段和请求体怎样进入方法参数
不同来源的数据应保持各自语义,框架不会因为最终都转换成 Java 值就消除协议差异。
| 来源 | Spring MVC 常见入口 | 典型用途 | 常见失败 |
|---|---|---|---|
| 路径变量 | @PathVariable | 资源标识 | 路由不匹配、类型转换失败 |
| 查询参数或表单字段 | @RequestParam、模型绑定 | 过滤、分页、简单表单 | 缺少必填值、格式错误、重复参数语义不清 |
| HTTP 字段 | @RequestHeader | 条件请求、协商、调用上下文 | 字段缺失或格式无效 |
| Cookie | @CookieValue | 会话和客户端状态 | Cookie 缺失、过期或签名无效 |
| Servlet 对象 | HttpServletRequest 等 | 访问底层请求上下文 | 过度耦合容器语义 |
| 请求体 | @RequestBody | JSON、XML 等结构化文档 | 媒体类型不支持、语法错误、反序列化失败 |
| 上传部件 | MultipartFile 等 | 文件和表单组合 | 边界错误、大小超限、临时存储失败 |
| 身份主体 | Principal、安全上下文 | 当前认证主体 | 未认证或上下文未传播 |
请求体转换通常先依据 Content-Type 选择 HttpMessageConverter,再由 JSON 或其他解析器把字节转换为 Java 对象。字段不存在、值为 null、空字符串和零值是不同状态;数据绑定规则若不明确,可能把客户端错误悄悄转换成默认值。
校验也分层发生。HTTP 语法和媒体类型由服务器或框架判断,结构约束由 Bean Validation 等机制检查,资源存在性、状态流转和跨字段业务规则则由业务层决定。把所有失败都返回同一个 400 会损失可诊断性;把客户端输入错误抛成 500 又会错误归责服务器。
Controller、Service 与 Repository
常见 Java 后端把应用职责分成接口适配、业务编排和数据访问:
Controller / HTTP adapter
├─ 接收已经解析的接口输入
├─ 调用应用服务
└─ 把结果或错误转换为 HTTP 表达
↓
Service / application use case
├─ 校验业务前置条件
├─ 执行授权与状态流转规则
├─ 划定事务边界
└─ 协调仓储和外部服务
↓
Repository / data gateway
├─ 构造数据库或存储操作
├─ 映射持久化数据
└─ 隔离具体驱动与查询细节这些名称来自常见工程组织方式,HTTP、Servlet 和 Spring 并不强制采用。真正需要守住的是依赖方向:Controller 不把 HTTP 对象渗透到核心业务规则,Repository 不决定接口状态码,业务服务也不依赖某个 JSON 字段名表达领域规则。
请求 DTO、命令对象、领域对象、持久化实体和响应 DTO 可以在小型系统中适度合并,但它们承担的约束并不相同。直接把数据库实体作为外部请求和响应模型,容易造成越权字段写入、内部结构泄露、懒加载问题和接口随表结构意外变化。
同步 Servlet、Servlet 异步与响应式处理
“异步”可能指释放 Servlet 请求线程、异步执行业务任务、非阻塞网络 I/O 或消息驱动处理,不能混为同一种模型。
| 模型 | 请求线程 | I/O 方式 | 适合场景 | 主要风险 |
|---|---|---|---|---|
| 同步 Servlet | 通常在处理期间占用一个容器工作线程 | 依赖调用常为阻塞式 | 普通数据库和短时 HTTP 请求 | 慢依赖耗尽线程池 |
| Servlet 异步 | 初始线程可返回容器,结果完成后再分派或写响应 | 业务依赖未必非阻塞 | 延迟结果、长轮询、与异步 API 集成 | 超时、上下文传播和重复完成 |
| 响应式服务器 | 少量事件循环线程处理大量连接 | 需要整条调用链支持非阻塞 | 高并发 I/O、流式数据 | 在事件循环中混入阻塞调用导致整体停顿 |
| 后台任务 | HTTP 请求只负责提交任务 | 任务由独立执行器或消费者处理 | 耗时、可排队、可重试工作 | 请求成功与任务成功被混淆 |
把 Controller 返回值包装成异步类型,不会自动把 JDBC、文件访问或第三方 SDK 变成非阻塞。线程模型必须从入口一直核对到每个依赖;异步执行还要显式传播安全上下文、日志上下文、Trace 信息和取消信号。
业务逻辑、事务与外部依赖
Controller 执行后,业务结果仍有提交过程
Controller 被调用只说明请求通过了前面的协议、路由和框架阶段。一个写操作真正成立,通常还要经过主体授权、业务条件判断、数据并发控制、事务提交以及必要的外部副作用确认。
接口输入
└─ 主体与租户上下文
└─ 资源级授权
└─ 业务规则与当前状态
└─ 本地事务
├─ 数据读取与锁定
├─ 状态变更
├─ 约束检查
└─ commit 或 rollback
└─ 外部效果
├─ 缓存更新
├─ 消息发布
├─ 下游 HTTP / RPC
└─ 文件、搜索或第三方系统认证、接口授权和业务授权应连续但不可互相代替。已经登录的用户仍可能无权访问另一租户的订单;具有“订单读取”角色,也不表示订单当前状态允许取消;输入中的 tenantId 或 ownerId 不能直接当作可信授权结论。
HTTP 连接与数据库连接是两条不同链路
应用接收客户端请求使用的是网络服务器连接,访问数据库通常从另一个连接池借用 JDBC 连接:
客户端
└─ HTTP 连接
└─ Java 应用
└─ JDBC 连接池
└─ 数据库连接
└─ 数据库会话与事务客户端断开 HTTP 连接,不会天然关闭正在执行的数据库语句或回滚已经提交的事务。数据库连接断开也不会立即让客户端连接消失,应用仍需把数据库异常映射成 HTTP 响应。两个连接有独立的池、握手、认证、超时、保活和容量限制。
连接池减少反复建立数据库连接的成本,但不能创造数据库容量。池耗尽时,请求会等待可用连接;等待超时应与查询超时、事务超时和 HTTP 超时区分。池设置过大可能把排队从应用转移到数据库,增加上下文切换、锁竞争和故障放大。
事务边界、隔离与提交
数据库事务把一组操作组织成提交或回滚单元。Spring 的声明式事务通常由代理或拦截器在方法调用边界开始事务,根据返回或异常规则提交、回滚。
进入事务方法
├─ 从连接池取得连接
├─ 关闭自动提交或加入既有事务
├─ 执行查询和修改
├─ 正常完成:尝试 commit
└─ 满足回滚规则:rollback
└─ 归还连接池@Transactional 通过事务拦截器包围符合条件的调用。是否经过代理、传播行为、异常是否被吞掉、回滚规则、数据源能力和外部系统参与情况,都会改变最后的提交结果。同一对象内部绕过代理的调用,在常见代理模式下可能不会创建预期事务。
隔离级别决定并发事务能观察到什么,但不会自动解决全部业务竞争。更新丢失、重复扣减、状态重复流转等问题还需要条件更新、唯一约束、悲观锁、乐观版本或其他并发控制。数据库约束是最终数据防线之一,不能只依赖“先查后写”的应用判断。
事务提交成功表示数据库接受了提交,不表示客户端一定收到成功响应,也不表示消息、缓存和第三方系统已经同步成功。反过来,应用捕获到连接错误时,提交是否已经在数据库生效有时可能无法仅从本地异常确定。Spring 声明式事务的工作方式见 Declarative transaction management。
缓存处在多个层级
“缓存命中”必须同时说明缓存的位置、键、有效期和一致性策略。
| 缓存层 | 常见键 | 主要作用 | 典型风险 |
|---|---|---|---|
| 浏览器或客户端缓存 | URL、方法、请求字段和响应缓存指令 | 避免网络请求或条件验证 | 旧数据、用户隔离错误 |
| CDN / 反向代理缓存 | host、路径、查询参数、Vary 相关字段 | 减少源站访问 | 私有响应被共享、缓存键不完整 |
| 应用本地缓存 | 业务键 | 低延迟读取 | 多实例数据不一致、重启丢失 |
| 分布式缓存 | 业务键和命名空间 | 跨实例共享热点数据 | 热点键、穿透、雪崩和序列化兼容 |
| 数据库缓存 | 数据页、执行计划等内部键 | 减少磁盘与计算开销 | 容量竞争、错误估算真实查询成本 |
Cache-Aside 模式通常先读缓存,未命中再读数据库并回填;写入时更新数据库后删除或更新缓存。数据库提交与缓存变更拥有两个提交点,期间会出现短暂不一致,需要用版本、消息、失效策略和业务容忍度控制。唯一约束和事务事实仍保留在权威数据库。
缓存空值可以缓解不存在键反复穿透,互斥重建或逻辑过期可以降低热点同时失效,随机 TTL 可以减轻批量过期冲击。这些策略改变的是负载与陈旧窗口,需要与数据正确性要求一起选择。
调用下游 HTTP 或 RPC
当服务调用另一个服务时,它在入站链路中是服务端,在出站链路中又成为客户端:
原始客户端
└─ 入站 HTTP
└─ 服务 A
└─ 出站 HTTP / RPC
└─ 服务 B
└─ 服务 B 的数据库或其他依赖出站调用重新经历名称解析、连接池、TLS、请求编码、下游代理、认证、超时和响应解析。入站请求剩余时间应约束下游 deadline;如果每一层都配置相同的独立超时,内层尚未结束时外层可能已经放弃,造成无效计算和资源堆积。
重试只适合明确可重试且能够承受重复执行的操作。连接建立失败、收到 503、响应读取中断分别意味着不同程度的不确定性;已经把请求体发送给下游但未收到响应时,不能仅凭“没有成功响应”断定下游没有执行。退避、抖动、重试次数上限和总体 deadline 用于限制重试风暴,但不替代幂等设计。
跨服务身份可以使用短期令牌、mTLS 身份或经过签名的调用凭据。把原始用户令牌无差别传给所有下游,会扩大权限和泄露范围;服务需要明确是代表用户调用,还是以自身工作负载身份调用,并在下游重新执行资源级授权。
消息队列与后台任务
消息系统把提交工作与执行工作解耦,使请求可以在任务完成前返回,但也引入新的确认边界。
HTTP 请求
└─ 应用创建任务或业务记录
├─ 返回任务标识和当前状态
└─ 发布消息
└─ Broker 确认接收
└─ 消费者取得消息
├─ 执行业务
├─ 记录结果
└─ ack / retry / dead-letter生产者调用成功、Broker 持久化成功、消费者确认消息和业务结果成功是四个不同事实。HTTP 202 Accepted 表示请求已经被接受处理,不表示最终任务已经完成。客户端需要用任务资源、回调或事件订阅获取最终状态,服务端则需要为任务建立稳定标识和可查询状态。
常见消息投递语义允许重复消费。消费者应使用业务唯一键、去重记录或状态条件保证重复消息不会重复产生不可接受的副作用。先提交数据库再发布消息可能在两步之间崩溃,先发布消息再提交数据库又可能让消费者看到尚未成立的数据;事务 Outbox 把待发送事件和业务变更写入同一本地事务,再由独立发布器转发,用于缩小这一不一致窗口。
消息积压意味着生产速度持续高于消费能力或消费者受阻。只增加消费者数量还受分区数、下游容量、顺序约束和热点任务限制。重试队列必须设置退避与上限,永久失败消息进入死信或人工补偿流程,并保留失败原因和原始业务标识。
文件、对象存储、搜索与第三方系统
文件系统、对象存储、搜索索引、邮件、支付和短信等依赖各有自己的确认语义。把文件写入本地容器磁盘,通常不能保证容器迁移后仍存在;对象存储上传成功也不表示数据库引用已经提交;搜索索引更新常是异步的,读模型可能暂时落后于事务数据库。
第三方支付或通知请求超时后尤其不能盲目重发。应使用业务幂等键、第三方查询接口、回调验签和对账任务确认最终状态。回调是新的入站请求,需要独立验证来源、签名、时间窗口、防重放和事件幂等,不能因为 URL 难猜就信任内容。
本地事务不能包住整个分布式系统
数据库本地事务无法回滚已经发送的邮件、已经被第三方接受的支付或另一个服务独立提交的数据。把远程调用放在长数据库事务中,还会延长锁持有时间,并让网络波动占用稀缺连接。
需要跨系统维持的业务结果
├─ 幂等键:识别同一次业务意图
├─ 唯一约束或状态条件:阻止本地重复生效
├─ Outbox / Inbox:可靠转发和去重接收
├─ 补偿动作:撤销或抵消已经发生的效果
├─ 状态机:显式表达处理中、成功、失败和待确认
└─ 对账:以权威事实修正长期不一致这些机制不提供瞬时的全局原子性,而是把中间状态、重复消息和局部失败变成可以识别和恢复的系统状态。设计接口时,应先确定哪个系统拥有最终事实、一次业务操作的稳定标识是什么,以及客户端在结果不确定时如何安全查询或重试。
HTTP 响应的状态、字段与消息体
响应消息的组成
服务端通过响应报告对当前请求的处理结果,并携带表示、控制信息或错误详情。HTTP/1.1 响应的可读形式如下:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 67
Cache-Control: no-store
X-Request-Id: 7e8b2b5f0bd14b6b
{"id":"ord_123","status":"created","amount":19900,"currency":"CNY"}HTTP 响应
├─ 协议版本:HTTP/1.1
├─ 状态码:200
├─ 原因短语:OK,仅用于可读描述
├─ 响应字段
│ ├─ Content-Type:内容的媒体类型和字符集
│ ├─ Content-Length:传输内容长度
│ ├─ Cache-Control:缓存策略
│ └─ X-Request-Id:应用定义的请求标识
└─ 空行之后的消息体:JSON 表示HTTP/2 和 HTTP/3 使用 :status 伪字段和二进制帧表达相同的状态与字段语义,不发送 HTTP/1.1 原因短语。原因短语也不是程序判断依据;客户端应读取三位状态码和响应契约。
状态码描述 HTTP 请求的处理结果类别。200 可以携带查询结果,也可能携带设计不当的业务错误对象;500 则可能由应用、代理或网关生成。客户端还要结合传输结果、媒体类型、消息体解析和接口定义的业务字段,才能得到完整业务结论。
状态码类别与常用状态
状态码的第一位划分响应类别:
| 类别 | 协议含义 | 常见处理方向 |
|---|---|---|
1xx | 临时信息,处理仍在继续 | 继续发送或等待最终响应 |
2xx | 请求已经成功接收、理解并接受 | 按状态和响应体使用结果 |
3xx | 需要重定向,或条件请求可复用已有表示 | 跟随位置、使用缓存或按客户端策略处理 |
4xx | 请求侧存在语法、身份、权限、状态或频率问题 | 修改请求、凭据或调用时机 |
5xx | 服务端或中间网关未能完成看似有效的请求 | 判断是否暂态、结果是否未知以及能否安全重试 |
常用状态需要按协议语义选择,不能把数字当作任意业务编码:
| 状态 | 适用事实 | 容易混淆之处 |
|---|---|---|
200 OK | 请求成功,响应内容按方法语义解释 | 不等于消息体中的业务字段必然成功 |
201 Created | 已创建资源 | 通常用 Location 指向新资源 |
202 Accepted | 已接受请求,但处理尚未完成 | 不承诺最终任务成功 |
204 No Content | 已成功处理且没有响应内容 | 不能再发送消息体 |
206 Partial Content | 满足范围请求并返回部分表示 | 需要正确的 Content-Range |
301 Moved Permanently | 资源具有新的永久 URI | 非 GET 方法的自动处理受客户端历史行为影响 |
302 Found | 资源临时位于另一 URI | 需要保留方法时优先明确使用 307 |
303 See Other | 到另一 URI 使用 GET 获取结果 | 适合提交后跳转到结果资源 |
304 Not Modified | 条件 GET/HEAD 可继续使用缓存表示 | 不是成功响应体,不能携带消息内容 |
307 Temporary Redirect | 临时重定向并保留方法和内容 | 客户端重发非幂等请求仍需谨慎 |
308 Permanent Redirect | 永久重定向并保留方法和内容 | 会影响长期缓存和客户端配置 |
400 Bad Request | 请求语法或可解释性存在一般性问题 | 不应承载所有输入错误 |
401 Unauthorized | 缺少有效认证凭据 | 通常配合 WWW-Authenticate,实际含义是未认证 |
403 Forbidden | 已理解请求但拒绝授权 | 更换相同凭据重试通常无效 |
404 Not Found | 未找到目标资源,或服务端不愿透露其存在 | 可能是路由、资源或隐藏授权信息 |
405 Method Not Allowed | 资源存在但不支持该方法 | 应返回 Allow 指明支持的方法 |
409 Conflict | 请求与资源当前状态冲突 | 适合版本冲突或状态流转冲突 |
412 Precondition Failed | If-Match 等前置条件不成立 | 可用于乐观并发控制 |
413 Content Too Large | 请求内容超过接收方上限 | 可能由代理、容器或应用生成 |
415 Unsupported Media Type | 请求内容类型不受支持 | 与响应协商失败不同 |
422 Unprocessable Content | 内容语法可识别,但指令在语义上无法处理 | 适合结构校验或语义输入问题 |
429 Too Many Requests | 调用频率超过限制 | 可用 Retry-After 提示等待 |
500 Internal Server Error | 服务端遇到未归类的内部失败 | 不应把内部堆栈直接暴露给客户端 |
502 Bad Gateway | 网关从上游收到无效响应 | 说明响应产生者是代理角色 |
503 Service Unavailable | 服务暂时无法处理 | 可能是过载、维护或无健康后端 |
504 Gateway Timeout | 网关等待上游响应超时 | 上游操作可能仍在继续 |
全部已注册状态码及对应规范见 IANA HTTP Status Code Registry。接口文档还应规定每个状态在具体资源上的触发条件和消息体结构。
响应字段与内容表达
响应字段控制内容解释、缓存、重定向、认证挑战、客户端状态和连接行为。
| 字段 | 主要作用 | 关键约束 |
|---|---|---|
Content-Type | 声明响应内容的媒体类型和字符集 | JSON 通常使用 application/json;不要依赖客户端猜测 |
Content-Encoding | 声明 gzip、br 等内容编码 | 客户端解压后才得到媒体类型表示 |
Content-Length | 声明当前消息内容的字节长度 | 是编码后的传输内容长度,不是 Java 字符数 |
Location | 指向创建的资源或重定向目标 | 可以是绝对 URI,也可能按具体状态允许相对引用 |
WWW-Authenticate | 描述访问资源所需认证方案 | 与 401 响应配合 |
Allow | 声明资源支持的方法 | 405 响应必须携带 |
Retry-After | 提示再次请求前等待时间 | 不代表客户端必须重试,也不保证届时成功 |
Set-Cookie | 要求用户代理存储 Cookie | 属性决定作用域、有效期和安全策略 |
Content-Disposition | 提示内联展示或下载及文件名 | 文件名仍需客户端安全处理 |
ETag | 当前表示的实体标签 | 用于缓存验证和条件修改 |
Vary | 声明缓存键还受哪些请求字段影响 | 缺失会造成协商结果错误共享 |
Content-Type 描述消息内容的格式,Content-Encoding 描述为传输或存储施加的额外编码。一个 gzip 压缩的 JSON 响应仍是 JSON 表示,只是接收方需要先按 Content-Encoding: gzip 解码。HTTP/1.1 的传输编码和消息定界不属于媒体类型。
响应体并非所有状态都允许存在。对 HEAD 的响应不发送内容,但字段应表达对应 GET 可能返回的表示元数据;1xx、204 和 304 响应不包含消息内容。客户端不能仅靠连接关闭判断现代 HTTP 响应是否结束,应遵守具体版本的消息定界或流结束规则。
内容协商与统一错误结构
同一个资源可以有 JSON、HTML、不同语言或不同编码的表示。客户端用 Accept、Accept-Language、Accept-Encoding 等字段表达偏好,服务端选择能够生成的表示,并在响应中用 Content-Type、Content-Language、Content-Encoding 说明实际结果。
客户端可接受的表示
∩
服务端能够生成的表示
↓
选定响应表示
├─ 没有可接受的响应类型:406 Not Acceptable
└─ 请求体类型无法读取:415 Unsupported Media Type接口错误至少需要稳定的机器可读类型、面向调用方的说明、具体实例标识和必要的字段级信息。错误体不应暴露 SQL、目录、堆栈、内部服务名或密钥。application/problem+json 定义了 type、title、status、detail、instance 等通用成员,并允许接口扩展字段;规范见 RFC 9457。
业务错误码与 HTTP 状态码承担不同职责。HTTP 状态供通用客户端、代理、缓存和监控理解结果类别,业务错误类型供特定接口调用方采取动作。二者应稳定映射,不能永远返回 200 再让所有基础设施解析 JSON 才知道失败。
缓存、新鲜度与条件请求
HTTP 缓存保存响应,并在满足规则时复用,或向源服务器验证已有表示是否仍有效。
GET 请求
└─ 缓存中是否存在匹配响应
├─ 不存在:访问源服务器并按规则存储
└─ 存在
├─ 仍然新鲜:直接复用
└─ 已陈旧:携带验证条件访问源服务器
├─ 304 Not Modified:更新元数据并复用原内容
└─ 200 OK:用新表示替换缓存Cache-Control: max-age=60 表示响应在相应缓存语境下可保持新鲜的秒数。no-cache 允许存储,但要求复用前验证;no-store 要求缓存不存储请求或响应相关内容。private 限制共享缓存复用,public 表明响应可以由共享缓存存储,其他缓存规则仍然同时生效。
ETag 是表示验证器。客户端可在 GET 中发送 If-None-Match,匹配时服务端返回 304;更新接口可用 If-Match 要求资源仍处于客户端已知版本,不匹配时返回 412,从而避免无条件覆盖并发修改。强实体标签要求字节级等价,弱实体标签表示语义等价但不满足所有范围和并发用途。
Last-Modified 与 If-Modified-Since 使用时间验证,精度和可靠性通常弱于应用生成的版本标签。Vary: Accept-Encoding 告诉共享缓存必须把请求的编码偏好纳入缓存键;对认证、Cookie、语言和 Origin 产生不同内容时,缓存策略若不完整可能造成数据串用。完整缓存语义见 RFC 9111。
重定向、Cookie 与范围响应
重定向响应用 Location 提供新目标。客户端是否自动跟随、最多跟随多少次、是否保留认证字段、Cookie 和请求体,取决于状态码、安全边界和客户端策略。跳转到不同 Origin 时不应无条件转发敏感凭据;循环重定向必须由客户端限制。
服务端用 Set-Cookie 写入 Cookie,浏览器以后按 Domain、Path、Secure、SameSite 和过期规则决定是否自动携带。HttpOnly 阻止普通脚本读取 Cookie,但允许浏览器随匹配请求发送;Secure 要求安全连接;SameSite 限制部分跨站请求携带。Cookie 保存在客户端,服务端 Session 则保存服务端会话状态,两者经常通过会话标识组合使用。
下载大对象或断点续传可以使用 Range 请求字段。服务端接受范围后返回 206 Partial Content 和 Content-Range;无法满足的范围通常返回 416 Range Not Satisfiable。范围是对选定表示字节区间的请求,压缩、动态生成内容和多范围响应会改变实现复杂度。条件请求、内容协商、状态和范围的通用语义统一见 RFC 9110。
从 Controller 返回值到客户端结果
Controller 方法返回以后,视图、响应体和异步请求不会经过完全相同的回程。三条常见路径如下:
视图返回
Controller
└─ HandlerAdapter 返回 ModelAndView
└─ postHandle
└─ DispatcherServlet 解析并渲染视图,可能写出或提交响应
└─ afterCompletion
└─ Filter 的 chain.doFilter 返回段
@ResponseBody / ResponseEntity
Controller
└─ HandlerAdapter 内的返回值处理
├─ ResponseBodyAdvice(匹配时)
└─ HttpMessageConverter 写出响应体,可能已经 committed
└─ postHandle:此时不能假定仍可修改状态和字段
└─ afterCompletion
└─ Filter 的 chain.doFilter 返回段
Callable / DeferredResult
初次 REQUEST 分派
└─ startAsync → afterConcurrentHandlingStarted
└─ DispatcherServlet 与当前 Filter 调用退出,响应保持打开
└─ 异步结果或异常就绪
└─ 新的 ASYNC 分派
└─ Spring MVC 返回值处理或异常解析
└─ 完成回调与为 ASYNC 配置的 Filter 返回段
每条路径的后续网络处理
├─ 若响应尚未 committed:容器提交状态、字段和缓冲字节
├─ 若响应已经 committed:容器只能继续刷新或结束输出
└─ 网络写入 → 反向代理处理 → 客户端接收与解析Filter 在 chain.doFilter 返回后继续执行响应方向代码,但此时响应可能已经在 Controller、返回值处理、视图渲染、流式写出或异常处理期间提交。必须改变的字段应在调用下游前设置,或者通过响应包装明确缓冲与提交策略。代理还可能压缩、缓存、重写字段,或在上游响应无效时生成自己的错误。Controller 的正常返回日志因此只能定位到应用处理阶段,完整写出与客户端读取还要另行观察。
对象序列化还可能因循环引用、懒加载、类型适配或输出流错误而失败。同步 Servlet 分派、Servlet 异步分派和流式响应的提交点不同,诊断时必须同时观察返回值类型、DispatcherType、响应是否 committed、异常解析结果和实际写出字节。
响应一旦 committed,状态行和字段已经发送或确定,后续代码通常不能再改成结构化错误响应。流式响应在发送一部分内容后失败,只能关闭流或发送协议允许的结束信息,客户端会同时面临“已收到部分数据”和“整体未完整成功”。
客户端收到全部字节后仍可能失败:压缩格式错误、字符集不符、JSON 结构与模型不兼容、状态码被库配置成异常、浏览器 CORS 阻止脚本读取,或者业务字段指示操作未完成。因此“服务端返回”“网络送达”“客户端解析”“用户看到结果”是四个不同完成点。
长连接、流式传输与异步交互
常见交互方式的边界
HTTP 请求—响应不只承载 JSON API,也承载页面、表单、文件、事件流、RPC 和协议升级。交互模型决定连接持续时间、消息边界、失败恢复和扩容方式。
| 方式 | 方向与生命周期 | 适合场景 | 客户端怎样确认结果 |
|---|---|---|---|
| 普通 HTTP | 一次请求对应一个最终响应 | 页面、REST、普通 RPC | 状态码、字段和完整响应体 |
| 短轮询 | 客户端周期性发起独立请求 | 低频状态更新 | 每次响应给出当前状态 |
| 长轮询 | 服务端暂时挂起请求,变化或超时后响应 | 兼容普通 HTTP 的准实时更新 | 收到响应后立即建立下一次请求 |
| SSE | 一次 HTTP 响应持续发送文本事件 | 服务端到浏览器的事件通知 | 事件 ID、断线重连和业务序号 |
| WebSocket | 握手后在长连接上双向发送消息 | 聊天、协作、实时控制 | 应用级确认、序号和重连同步 |
| gRPC unary | 一条 RPC 请求消息对应一条响应消息 | 内部强类型服务调用 | gRPC 状态、响应消息和 trailing metadata |
| gRPC streaming | 客户端流、服务端流或双向流 | 高效服务间数据流 | 流内消息与最终 gRPC 状态 |
| Webhook | 服务端在事件发生后反向调用订阅方 | 跨系统事件通知 | 接收方响应、重试记录和事件查询 |
| 异步任务资源 | 提交请求与任务执行分离 | 长耗时和可排队任务 | 任务 ID、状态资源、回调或事件 |
GraphQL 通常仍通过 HTTP 发送查询或变更文档,单个端点内部选择字段和操作;它改变接口查询模型,不绕过 DNS、TLS、代理和 HTTP。传统 RPC、JSON-RPC 和 gRPC 也只是把远程操作映射为不同契约与消息格式,网络失败和结果不确定性仍然存在。
上传与下载是受流量控制的数据传输
小型 JSON 请求常被框架一次性读入内存,但大文件上传不应默认如此处理。上传链路可能依次经过浏览器、边缘代理、网关、容器临时目录和应用,每层都有大小、速率、空闲时间与存储限制。
客户端发送内容
├─ 固定长度或流式消息定界
├─ 代理缓冲或直接转发
├─ 容器读取到内存或临时文件
├─ 应用逐块校验和处理
└─ 持久化到文件系统或对象存储应用应限制总大小、单个部件大小、文件数量、文件名和允许的内容类型,并把客户端声明的文件名和 Content-Type 当作不可信输入。需要计算摘要、病毒扫描或解析压缩包时,还要限制解压后体积、嵌套深度和处理时间。
下载可以逐块从存储读取并写入响应,避免把整个对象载入内存。真正的流式处理要求各层不要无意中完整缓冲;反向代理、压缩器和框架包装器都可能改变首字节延迟。客户端读取速度慢时,TCP、HTTP/2 或 QUIC 流量控制最终会形成背压,服务端必须限制被慢客户端长期占用的连接、缓冲和文件句柄。
Server-Sent Events
SSE 使用普通 HTTP 响应和 text/event-stream 媒体类型,服务端以文本事件持续向客户端推送。浏览器 EventSource 能在连接断开后重连,并可通过最后事件 ID 帮助服务端恢复位置。
HTTP GET
└─ 200 OK + Content-Type: text/event-stream
├─ event / id / data 字段组成事件
├─ 空行结束一个事件
├─ 注释或心跳维持活跃检测
└─ 连接断开后按策略重连SSE 是单向的服务端到客户端通道;客户端向服务端发送命令仍使用新的 HTTP 请求。事件 ID 只有在服务端保留可恢复事件日志时才有意义,单纯重连不能保证不丢不重。代理缓冲、空闲超时和压缩可能延迟事件或关闭连接,需要针对流式路径单独配置。浏览器端协议模型见 HTML Standard: Server-sent events。
WebSocket
WebSocket 先通过 HTTP 握手建立协议连接,成功后双方交换 WebSocket 帧,不再是一问一答的普通 HTTP 消息。
HTTP opening handshake
└─ 服务端接受协议切换或扩展连接
└─ WebSocket 连接
├─ text message
├─ binary message
├─ ping / pong
└─ close frameWebSocket 提供消息分帧和双向通道,业务级投递语义需要应用自己设计。服务端把字节写入 socket 后,客户端业务逻辑仍可能尚未处理;可靠流程通常还需要消息 ID、确认、顺序、去重和重连后的状态同步。
浏览器会在握手中发送 Origin,服务端应校验允许来源;Cookie 也可能随握手自动发送,不能忽略跨站连接风险。长连接还会影响负载均衡、发布摘流和扩容:连接建立后通常固定在某个实例,新增实例不会自动接管旧连接,实例终止前需要通知或排空连接。WebSocket 帧、控制消息和握手规则见 RFC 6455。
gRPC 与流式 RPC
gRPC 使用服务定义描述方法与消息,常以 Protocol Buffers 编码,并基于 HTTP/2 承载 unary、服务端流、客户端流和双向流四种调用形式。gRPC status 与 HTTP status 分属两套状态空间;代理看到 HTTP 层成功时,RPC 最终状态仍可能不是 OK。
gRPC call
├─ initial metadata
├─ request message 或 request stream
├─ response message 或 response stream
└─ final status + trailing metadata客户端 deadline 应向服务端和下游传播。deadline 到达或调用被取消后,已提交的数据库事务和已经发生的外部副作用不会自动撤销;服务端需要感知取消信号,停止不再有价值且可安全中止的工作。流量控制限制发送方超前写入,但应用仍要控制内存队列和单条消息大小。gRPC 的调用类型、deadline、取消和终止语义见 Core concepts, architecture and lifecycle。
Webhook 是新的入站请求
Webhook 发送方在事件发生后调用接收方 URL。接收端验证并可靠记录事件后通常快速返回 2xx,后续业务可以继续异步执行。发送方应把这个状态解释为通知已被接受,而非所有处理已经结束。
事件源
└─ 生成稳定 event_id
└─ 签名并发送 HTTP 请求
└─ 接收方验证签名、时间和重放
├─ 重复 event_id:返回已接受结果
└─ 新事件:可靠记录后返回 2xx
└─ 异步执行业务并记录最终状态发送方通常会在超时或非成功响应后重试,所以接收方必须假设重复和乱序。签名校验应覆盖原始请求体及协议规定的时间信息,读取并重新序列化 JSON 后再验签可能改变字节。密钥轮换、允许时间窗口、重放记录、来源网络限制和事件查询接口共同构成可维护的接收机制。
长连接需要单独的容量与故障模型
普通接口关注每秒请求数和单次延迟,长连接还要关注同时连接数、每连接订阅数、消息速率、发送积压、心跳、断线重连和连接驻留时间。大量客户端在服务恢复后同时重连会形成重连风暴,需要指数退避、抖动和服务端接入限速。
心跳能发现长时间无数据的失效连接,并保持部分中间设备的状态,但过于频繁会制造额外流量。应用心跳、WebSocket ping/pong、HTTP/2 ping 和 TCP keepalive 位于不同层,不能用一个配置替代全部检测。代理空闲超时必须大于协议允许的静默周期,否则健康连接也会被关闭。
流式响应开始后通常不能再用另一个 HTTP 状态报告中途失败,错误需要通过流内消息、gRPC 最终状态或连接关闭表达。客户端必须区分正常结束、带错误结束、超时、主动取消和无结束标志的断开,并决定从哪个业务序号继续。
请求失败的分层判断
总耗时由多个阶段组成
“接口耗时十秒”无法直接说明时间消耗在哪一层。一次新建 HTTPS 连接并访问动态接口,至少可以拆成:
客户端排队
└─ DNS 解析
└─ TCP 或 QUIC 建连
└─ TLS 与协议协商
└─ 请求发送
└─ 入口代理排队与转发
└─ 应用容器排队
└─ Filter / MVC / 业务处理
├─ 连接池等待
├─ 数据库与锁等待
├─ 缓存、消息或下游调用
└─ 序列化
└─ 首字节返回
└─ 响应体传输
└─ 客户端解码与处理已有 DNS 缓存和复用连接时,前面的阶段可能不存在;流式响应的首字节很快,完整结束却可能很晚。监控应分别记录 DNS、connect、TLS、time to first byte、download,以及应用内部各依赖耗时,不能只保留一个平均总时长。
百分位比平均值更能暴露尾部延迟。请求量很低时高百分位样本可能不稳定;请求量很高时少量慢请求也可能对应大量受影响用户。延迟指标应同时带上状态码、路由模板、方法、上游和实例等有限维度,避免把订单号或完整 URL 作为高基数标签。
不同超时保护不同等待
| 超时 | 限制的等待 | 超时后仍可能发生的事 |
|---|---|---|
| DNS 超时 | 名称解析 | 其他解析器或缓存可能仍返回结果 |
| connect timeout | 建立 TCP、QUIC 或代理连接 | 服务端未必收到应用数据 |
| TLS handshake timeout | 完成安全握手 | TCP 连接可能已经成立 |
| request write timeout | 发送请求字段和内容 | 服务端可能已经收到部分或全部请求 |
| response header timeout | 等待响应状态和字段 | 服务端业务可能仍在执行 |
| read timeout | 等待后续响应数据 | 已收到的部分内容不能当作完整响应 |
| pool acquire timeout | 等待 HTTP、JDBC 或线程池资源 | 依赖本身可能没有变慢,只是资源被占满 |
| transaction timeout | 数据库事务执行 | 外部副作用不一定自动取消 |
| overall deadline | 整个调用允许的最长时间 | 内层如果不传播取消,仍可能继续工作 |
| idle timeout | 连接多久没有数据 | 不等于单个业务操作总时限 |
外层超时应覆盖并约束内层阶段,给响应传输和清理保留时间。客户端十秒超时、网关六十秒超时、应用下游三十秒超时会导致客户端离开后服务端继续占用资源。只延长超时可能暂时减少报错,却会增加并发占用并推迟失败反馈。
取消、重试与幂等
取消表示调用方不再等待,不表示已经发生的效果回滚。Java 线程中断、Servlet 异步取消、Reactor cancellation、gRPC cancellation 和数据库语句取消都需要各组件显式支持;即使语句停止,先前独立提交的事务仍然有效。
一次失败调用是否重试
├─ 请求是否允许重复发送
├─ 服务端是否提供幂等语义或幂等键
├─ 失败发生前可能发送了多少数据
├─ 是否已经收到明确的服务端拒绝
├─ 当前错误是否为暂态
├─ 剩余 deadline 是否足够
└─ 重试是否会放大过载GET、HEAD、OPTIONS、TRACE、PUT、DELETE 在 HTTP 语义上是幂等的,但接口实现仍必须满足这种预期;幂等也不等于每次响应完全相同。POST 可以通过业务操作 ID 或接口约定的幂等键实现重复提交去重,服务端需要把键与主体、目标、请求摘要和已知结果关联,并规定有效期与冲突处理。
自动重试应优先针对连接尚未建立、明确的暂态拒绝或服务端明确声明可重试的情况。非幂等请求在响应丢失时重试风险最高。所有层同时重试会成倍放大流量:客户端两次、网关两次、服务 A 两次下游调用,最坏情况下会产生多次执行尝试。重试预算应在调用链上统一设计。
客户端结果未知与事务结果未知
请求失败后,首先区分“客户端不知道结果”和“执行系统也不知道结果”。
| 观察到的情况 | 可以确认 | 不能确认 |
|---|---|---|
| DNS 失败 | 未获得目标地址 | 服务端业务状态是否因其他请求变化 |
| 建连失败且未发送请求 | 当前连接没有建立 | 负载均衡后其他端点是否可用 |
| 请求发送中连接断开 | 连接未完成正常响应 | 服务端收到多少内容、是否开始执行 |
| 服务端明确返回输入错误 | 该服务端按响应语义拒绝当前请求 | 客户端是否还会被其他层自动重试 |
| 客户端等待响应超时 | 客户端没有及时得到最终响应 | 服务端是否执行、提交或稍后返回 |
| 应用记录数据库 commit 成功 | 本地事务已经提交 | 响应是否送达、外部系统是否成功 |
| 数据库连接在 commit 时中断 | 应用没有拿到明确提交确认 | 数据库端究竟提交还是回滚 |
结果未知时,使用稳定业务操作 ID 查询权威状态。支付、订单、库存等关键操作应提供按操作 ID 或资源 ID 查询的接口,并通过唯一约束、状态机和对账处理长期未知状态;把所有异常都翻译成“失败后重新创建”容易产生重复副作用。
日志、指标与 Trace 各自回答什么
可观测性用不同信号回答不同问题,重复记录同一段文本很难补足缺失的状态。
| 信号 | 擅长回答 | 必需上下文 |
|---|---|---|
| 访问日志 | 哪个入口在何时收到什么方法和路由,返回何状态 | 请求 ID、路由模板、状态、耗时、上下游地址 |
| 应用事件日志 | 业务处理到了哪个明确状态 | 操作 ID、资源 ID、错误类型和安全主体的非敏感标识 |
| 指标 | 错误率、吞吐、延迟分布和资源饱和趋势 | 稳定低基数维度和直方图边界 |
| 分布式 Trace | 一次请求跨服务与依赖的因果和耗时结构 | Trace/Span 上下文、服务名、操作名、状态和事件 |
| 审计日志 | 谁对受保护资源执行了什么动作 | 主体、动作、资源、决策、结果和防篡改策略 |
Request ID 可以关联同一入口链路的日志,业务 operation ID 用于跨重试识别同一业务意图,Trace ID 用于连接分布式调用图。三者可以关联但不应强行合并:一次业务操作可能产生多次 HTTP 请求和多个 Trace,一次 Trace 也可能包含多个内部步骤。
W3C Trace Context 规定 traceparent 和 tracestate 的跨系统传播格式。服务收到不可信来源的 Trace 字段时应验证格式并应用采样与信任策略,不能把它当作认证凭据。Trace 采样会导致部分调用没有完整链路,因此关键业务事实仍需独立日志或审计记录。标准见 W3C Trace Context。
日志不得记录密码、完整令牌、Session ID、私钥、支付敏感信息或未经处理的个人数据。请求体和响应体只能在明确的数据分类、脱敏、访问控制、保留期和采样规则下记录。排障便利不能覆盖数据安全边界。
用最后一个可靠事实定位故障层
排障先定位最后一个已经确认成功的阶段,再验证紧邻的下一阶段。这样可以逐步缩小范围,避免一开始就从 Controller 或数据库猜原因。
| 现象 | 先检查 | 继续判断 |
|---|---|---|
| 域名无法解析 | 本机解析结果、递归解析器、权威记录和 TTL | 是名称不存在、记录缺失、委派错误还是本地解析链问题 |
| 连接拒绝 | 目标 IP/端口、应用监听地址、端口发布 | 是无监听、错误端口还是设备主动拒绝 |
| 连接超时 | 路由、防火墙、安全组、NetworkPolicy 和返回路径 | 数据包在哪一跳停止,是否只有某地址族失败 |
| TLS 失败 | SNI、证书名称、证书链、有效期和 ALPN | 是身份不匹配、信任链问题还是协议不兼容 |
404 | 响应产生层、host、代理路径重写、应用路由 | 是入口规则未命中还是资源不存在 |
413 | CDN、网关、代理、容器和应用各自大小限制 | 哪一层最先拒绝,请求是否到达应用 |
502 | 网关上游地址、连接、协议和响应格式 | 上游是否监听,TLS/HTTP 是否匹配 |
503 | 健康端点、就绪状态、过载保护和连接池 | 是无可用实例还是主动限载 |
504 | 网关上游超时与应用内部耗时 | 超时后业务是否仍执行,是否发生代理重试 |
应用 500 | 异常类型、对应 Trace、依赖状态和响应是否提交 | 是确定失败还是结果未知 |
| 客户端解析失败 | 实际状态、Content-Type、编码和原始响应长度 | 是错误页、截断、压缩问题还是契约不兼容 |
| 业务重复 | operation ID、幂等记录、代理和客户端重试 | 是重复请求、重复消息还是并发条件缺失 |
安全拒绝也要按层归因。WAF 依据流量规则拒绝、网关凭据校验失败、Spring Security 未认证、方法级授权失败、业务资源越权和数据库行级策略拒绝,可能都表现为 401、403 或被隐藏成 404。对外可以控制信息暴露,对内必须保留明确决策点和非敏感原因。
浏览器开发者工具:观察浏览器实际发送了什么
浏览器页面出现“请求失败”时,JavaScript 错误、浏览器安全策略和 HTTP 失败可能使用相似提示。Chrome、Edge 等 Chromium 浏览器可以按 F12 或 Ctrl+Shift+I 打开开发者工具;macOS 常用 Command+Option+I。不同浏览器的名称和布局略有差异,核心观察对象都在 Network、Console、Application/Storage 和 Security 一类面板中。
Network 面板的最小检查顺序如下:
| 顺序 | 操作 | 目的与判断 |
|---|---|---|
| 1 | 打开 Network,确认左上角录制按钮处于启用状态 | 需要观察页面跳转、登录回调或重定向链时启用 Preserve log;它会跨页面加载保留请求 |
| 2 | 需要排除浏览器缓存时勾选 Disable cache,再重新加载页面 | 该选项通常只在开发者工具打开时生效,而且会改变客户端条件;应分别比较正常缓存策略和禁用缓存两次结果 |
| 3 | 清空旧记录,只复现一次问题 | 使用 Fetch/XHR、Doc、JS 等类型过滤器或接口路径筛选目标请求,避免把大量静态资源误当成业务接口 |
| 4 | 选择请求,依次检查 Headers、Payload、Response、Initiator 和 Timing | 请求发生重定向时逐个查看每一跳,不能只看最后一行 |
| 5 | 用请求 ID、Trace ID、时间窗口和实际命中地址关联代理与应用日志 | 浏览器记录是客户端观察,不单独证明事务结果 |
下面是在隔离浏览器配置中访问 https://example.com/ 后的实际 Network 记录。选中请求并切换到 Timing,可以直接看到排队、连接、等待首字节和内容下载等客户端可见阶段;连接被复用时,DNS、TCP 或 TLS 阶段可能不会单独出现。

各区域回答的问题不同:
| 位置 | 重点字段或现象 | 可以判断 | 常见误判 |
|---|---|---|---|
| 请求列表 | Name、Status、Type、Size、Time、Waterfall | 请求是否出现、何时开始、是否缓存、是否连续重定向 | 列表出现请求不表示页面脚本能读取响应 |
| Headers / General | Request URL、Request Method、Status Code、Remote Address | 浏览器最终请求的 URL、方法、HTTP 状态与直接连接地址 | Remote Address 可能是代理或 CDN,不一定是业务实例 |
| Request Headers | Host/:authority、Origin、Cookie、认证和协商字段 | 浏览器实际附带的协议元数据 | “Provisional headers” 或界面汇总值不一定等于已在线路发送的完整原始报文 |
| Payload | Query、Form Data、Request Payload | 参数最终位于查询、表单还是消息体,序列化结果是什么 | Preview 中的对象不等于原始字节和服务端反序列化结果 |
| Response / Preview | 原始响应文本或解析后的预览 | 服务端或代理返回了什么内容 | Preview 能显示不等于接口契约正确,错误页也可能返回 200 |
| Initiator | 触发请求的文档、脚本调用栈或重定向 | 请求由哪个页面资源或调用关系发起 | 只能解释浏览器内发起关系,不能代替服务端 Trace |
| Timing | Queueing、DNS、Initial connection、SSL、Request sent、Waiting、Content Download | 时间主要消耗在哪个客户端可见阶段 | 连接复用时 DNS、连接或 SSL 阶段可能不出现;Waiting 不全等于 Controller 执行时间 |
Network 中没有目标请求时,先检查筛选条件、录制状态、Console 错误、表单前端校验以及 Service Worker。请求显示 (memory cache)、(disk cache) 或 ServiceWorker 来源时,页面结果可能没有经过新的源站访问。请求已有 HTTP 状态但 Console 报 CORS,则服务器或代理已经响应,只是浏览器没有把响应开放给页面脚本;此时重复修改服务端业务方法通常不能解决问题。
Console 适合确认 JavaScript 异常、CORS、Mixed Content 和内容安全策略错误;Application 或 Storage 面板适合检查 Cookie、站点存储、Cache Storage 和 Service Worker;Security 面板适合查看当前页面的证书与安全连接信息。浏览器 UI 中显示的 Cookie 还要结合 Domain、Path、SameSite、Secure、有效期和凭据模式判断本次是否应该发送。
右键请求执行 Copy as cURL,可以得到接近该浏览器请求的命令,用于脱离页面复现。复制结果可能包含 Cookie、Authorization、签名、查询参数和请求体,执行前应删除或替换敏感值,分享前必须脱敏。HAR 同样可能包含 URL、字段、Cookie 和内容;优先导出 sanitized HAR,只有在受控环境中才处理带敏感数据的版本。Network 面板的字段、Timing、复制与 HAR 操作见 Chrome DevTools Network reference。
curl:脱离页面复现 HTTP 请求
curl 适合验证 URL 解析、代理、TCP/TLS、HTTP 方法、字段、请求体、重定向和响应,不依赖页面 JavaScript。它不会执行浏览器的同源读取限制,也不会自动拥有浏览器中的 Cookie、Local Storage、Service Worker 和前端运行状态。因此,curl 成功而页面失败时,应优先比较两者的 URL、字段、Cookie、代理、证书环境和浏览器安全策略;curl 也失败时,再按共同的 DNS、连接、TLS、HTTP 或服务端链路继续定位。
以下命令在 Linux Bash 中以普通开发或运维账号执行,要求已安装 curl。先检查当前构建支持的协议、TLS 后端和功能,再设置目标:
curl --version
export TARGET_URL='https://api.example.com/orders'
export TARGET_HOST='api.example.com'
export TARGET_PORT='443'
export TARGET_IP='203.0.113.10'curl --version 应输出 curl、libcurl、TLS 后端、支持协议和 Features。若环境只提供精简构建,HTTP/2、HTTP/3、特定代理或认证功能可能不可用;命令报 option ... is unknown 时,应先核对该构建能力,而不是判断服务端不支持。
使用详细模式观察完整连接与 HTTP 交换:
curl -sv \
--connect-timeout 3 \
--max-time 10 \
"$TARGET_URL" \
-o /dev/null详细输出通常依次显示代理使用情况、候选地址、实际连接地址、TLS 证书校验、ALPN、请求字段、响应状态和响应字段。-o /dev/null 只丢弃响应体,不丢弃 -v 的诊断信息。Could not resolve host 指向名称解析,Connection refused 指向目标端口拒绝,TLS 校验错误指向证书或主机名,出现 < HTTP/... 则说明已经获得 HTTP 响应。
-v 和更详细的 --trace 可能输出认证字段、Cookie、URL 参数以及收发内容。包含真实凭据的输出不得直接粘贴到公开聊天、Issue 或工单;curl 官方的安全边界见 Known risks: verbose logs。
使用 --write-out 把总耗时拆到关键协议节点:
curl -sS -o /dev/null \
-w 'code=%{http_code} remote=%{remote_ip} http=%{http_version}\ndns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
"$TARGET_URL"正常输出形态如下,数值由实际环境决定:
code=200 remote=<remote-ip> http=<http-version>
dns=<seconds> connect=<seconds> tls=<seconds> ttfb=<seconds> total=<seconds>公开分享诊断结果时,可以省略 remote_ip,避免把代理或内网地址带入文章、Issue 和工单。下面是在 Ubuntu 22.04 的 Bash 中对 https://example.com/ 实际执行的结果;耗时只代表该次请求样本,不是服务性能基线。

这些时间都是从传输开始累计到相应节点,不是互相独立的阶段耗时:
| 变量 | 结束节点 | 判断方式 |
|---|---|---|
time_namelookup | 名称解析完成 | 数值异常增大时检查本机解析链、递归解析器和地址族 |
time_connect | TCP 连接到目标或代理完成 | 减去名称解析时间,可近似观察新建 TCP 连接阶段 |
time_appconnect | TLS 等应用层握手完成 | HTTPS 中减去 connect,可近似观察 TLS 阶段;HTTP 无 TLS 时可为零 |
time_starttransfer | 收到第一个响应字节 | 包含此前全部阶段;与 appconnect 的差值还混合代理排队、应用和依赖处理 |
time_total | 整个传输完成 | 与 starttransfer 的差值主要反映响应体传输和客户端接收 |
经过正向代理、重定向、连接复用或多地址尝试时,计时含义会受实际连接过程影响。需要跟随重定向时显式加 -L,再同时观察 url_effective、num_redirects 和 time_redirect;不能把最后一次响应的状态与整条重定向链混为一体。完整变量定义见 curl --write-out manual。
DNS 返回多个地址或怀疑某个后端节点异常时,可以用 --resolve 为指定 host 和 port 临时提供地址,但只有控制了代理路径,实验才能回答“直连哪个地址”。curl 会读取 http_proxy、HTTPS_PROXY、ALL_PROXY、NO_PROXY 等环境变量,也可能从默认配置文件读取选项。先在 Linux Bash 中以当前执行账号检查代理环境:
env | grep -Ei '^(http|https|all|no)_proxy=' || true没有输出只表示这些环境变量未设置,不排除默认 curlrc、透明代理或网络出口设备。输出了代理变量时,普通 curl 与 --resolve 请求都可能先连接代理;此时不能从请求结果推断客户端直接连接了 TARGET_IP。
需要比较默认解析地址与指定节点、目标网络允许直连并且执行账号有权限访问该地址时,对两次请求使用完全相同的直连条件。-q 必须作为第一个选项,用于禁止读取默认 curlrc;--noproxy '*' 仅对本次命令禁用所有显式代理:
curl -q -sv \
--noproxy '*' \
--connect-timeout 3 \
--max-time 10 \
"$TARGET_URL" \
-o /dev/null
curl -q -sv \
--noproxy '*' \
--resolve "${TARGET_HOST}:${TARGET_PORT}:${TARGET_IP}" \
--connect-timeout 3 \
--max-time 10 \
"$TARGET_URL" \
-o /dev/null第二条命令为 TARGET_HOST:TARGET_PORT 提供 TARGET_IP,同时仍使用 URL 中的 TARGET_HOST 生成 HTTP authority、TLS SNI 并校验证书。详细输出应出现 Trying <TARGET_IP>:<TARGET_PORT>,随后出现连接到该地址的信息,而且不应出现连接代理或 HTTP CONNECT 隧道的记录;若实际地址不符、仍显示代理,或者企业网络禁止直连导致超时,应停止地址对比,先处理网络路径和策略。
只有上述直连条件已经成立时,指定地址成功而同条件的普通解析请求失败,才把差异收敛到 DNS 答案、地址选择或不同入口节点;两者都失败,再检查共同的路由、TLS、HTTP 和应用路径。透明代理、服务网格出口或其他不可见中间设备仍可能改变路径,所以 curl 输出只能证明客户端可观察到的连接目标。不要用 -k 或 --insecure 把证书错误处理成“请求成功”,因为这会关闭关键的服务端身份校验。--resolve、代理环境变量和 --noproxy 的准确语义见 curl --resolve manual、curl proxy environment 与 curl --noproxy manual。
curl 常见选项也有容易误判的语义:
| 操作 | 实际行为 | 使用时的判断 |
|---|---|---|
-I / --head | 发送 HEAD 请求 | HEAD 成功不保证 GET 的响应体生成和传输正常;查看 GET 响应字段可用 -D - -o /dev/null |
-L / --location | 按规则跟随重定向 | 最终 200 可能掩盖前面的 301、302 或跨 Origin 跳转,应同时看重定向次数和最终 URL |
-H / --header | 添加或替换请求字段 | 自定义敏感字段跟随重定向可能带来泄漏风险,不能盲目复制浏览器全部字段 |
--compressed | 声明支持 curl 可解码的内容编码并自动解码 | 解压后的大小和内容不同于线路上的压缩字节 |
-4 / -6 | 只使用 IPv4 或 IPv6 | 单独成功只能说明某个地址族路径可用,不代表默认地址选择正常 |
--noproxy '*' | 对所有目标绕过已配置代理 | 适合比较代理路径与直连路径,前提是环境允许直连 |
| 默认退出码 | HTTP 4xx、5xx 仍可能退出为零 | 零表示传输完成,不等于业务成功;脚本应检查状态码,或按需要使用 --fail-with-body |
Postman、Apifox、Insomnia 或 IDE 的 HTTP Client 适合保存环境变量、认证方式、请求集合和断言,便于接口联调与回归。它们仍然是独立 HTTP 客户端,不执行浏览器页面的 CORS 与 Service Worker 逻辑。图形工具和 curl 结果不一致时,应比较它们最终生成的 URL、代理配置、Cookie、认证字段、证书信任和重定向策略,而不是只比较界面中填写的参数。
常用诊断工具的观察边界
工具应按待确认的协议层选择。重复使用同一工具,只会反复得到同一观察面的结果。
| 工具或数据 | 主要观察层 | 能确认 | 不能单独确认 |
|---|---|---|---|
| 浏览器 Network | 浏览器策略、HTTP 与客户端计时 | 页面实际发起关系、请求与响应、缓存、CORS 表现 | 服务器内部执行和事务结果 |
curl | URL、代理、连接、TLS、HTTP | 独立客户端能否完成协议交换,具体状态与字段 | 浏览器 Cookie、Service Worker、CORS 和页面代码行为 |
getent ahosts | Linux 系统名称解析 | 应用经 NSS/hosts/DNS 等系统配置得到的候选地址 | 指定权威 DNS 服务器的原始回答 |
dig | DNS 协议查询 | 某解析器或权威服务器对记录的回答、TTL 和响应码 | 应用最终是否使用 hosts、缓存或另一解析器 |
ping | ICMP 可达性与往返时间 | 目标是否响应相应 ICMP 报文 | ICMP 被禁不等于服务不可达;成功也不证明 TCP 端口和 HTTP 可用 |
tracepath / traceroute | IP 路径探测 | 部分中间跳与路径 MTU 线索 | 未响应的中间跳不等于该处断路,返回路径也可能不同 |
openssl s_client | TLS | SNI、证书链、主机名验证和 ALPN 结果 | 完整 HTTP 与业务处理是否正常 |
nc | TCP/UDP 端口 | 基本端口连接或收发是否成立 | TLS 身份、HTTP 语义和应用健康 |
ss / lsof | 本机 socket 与进程 | 监听地址、端口、连接状态和所属进程 | 外部路由、防火墙与代理路径是否可达 |
| API 客户端 / IDE HTTP 文件 | 独立 HTTP 调用与接口集合 | 配置后的请求能否重复执行、断言是否通过 | 浏览器策略和服务端内部状态 |
| 代理 access/upstream 日志 | HTTP 入口与上游转发 | 代理是否收到请求、选择哪个上游、返回什么状态和耗时 | 应用事务是否提交、客户端是否完整读取 |
| 应用日志与 Trace | Servlet、业务和下游调用 | 请求经过哪些应用节点、内部耗时与已记录结果 | 未采样或未记录阶段是否发生,客户端最终体验 |
tcpdump / Wireshark | 数据包与传输协议 | 地址、端口、握手、重传、关闭和明文协议内容 | HTTPS 加密后的业务内容;抓到响应包也不等于客户端程序处理成功 |
getent 更接近普通 Linux 应用使用的系统解析结果,dig 适合直接询问 DNS 并检查委派、记录和 TTL。两者答案不同时,继续检查 /etc/hosts、NSS 顺序、企业 split DNS、容器 DNS、缓存和指定解析器。
抓包适合在客户端、代理和服务端日志仍无法解释连接问题时使用。生产抓包通常需要提升权限,并可能采集 IP、域名、Cookie、明文请求或其他敏感数据;应限制接口、host、port、时长和文件权限。HTTPS 下通常只能直接观察 DNS、IP、TCP/QUIC、TLS 握手元数据与包时序,不能因为看不到 JSON 就判断服务端没有发送内容。
在 Linux 与 Docker 环境建立最小诊断闭环
以下操作面向常见 Linux 宿主机和 Docker 容器。身份为普通运维或开发账号;读取 Docker 状态需要该账号拥有 Docker daemon 访问权限,查看其他进程详情可能需要受控的 sudo 权限。命令只读取状态,不修改服务。
沿用 curl 小节中的 TARGET_URL、TARGET_HOST 和 TARGET_PORT,再设置应用端口与容器名称:
export APP_PORT='8080'
export CONTAINER_NAME='orders-api'变量应替换为实际环境值,不要把示例域名当作生产地址。诊断顺序和判定如下:
| 阶段 | 环境与身份 | 命令 | 预期输出 | 异常判断与下一步 |
|---|---|---|---|---|
| 系统解析 | Linux,普通账号 | getent ahosts "$TARGET_HOST" | 至少一个 IPv4 或 IPv6 候选地址 | 无输出:检查 /etc/nsswitch.conf、/etc/resolv.conf、本机 hosts 和权威记录;地址错误:核对 split DNS、缓存和当前网络 |
| HTTPS 全链路 | Linux,普通账号,已安装 curl | curl -sv --connect-timeout 3 --max-time 10 "$TARGET_URL" -o /dev/null | 诊断输出包含目标地址、TLS 结果、请求行和最终响应状态 | Could not resolve 回到 DNS;Connection refused 查监听和端口;超时查网络或阶段耗时;证书错误查 SNI 与链;HTTP 错误查响应产生层 |
| TLS 与 ALPN | Linux,普通账号,已安装 OpenSSL | openssl s_client -connect "${TARGET_HOST}:${TARGET_PORT}" -servername "$TARGET_HOST" -verify_hostname "$TARGET_HOST" -verify_return_error -alpn 'h2,http/1.1' </dev/null | 显示证书链、主机名与信任链校验成功、协商协议和 Verify return code: 0 | 校验失败查目标名称、中间证书和信任库;无协商协议查服务端与代理协议配置 |
| 宿主机监听 | Linux 宿主机,普通账号;进程名可能需 sudo | ss -lntp "sport = :${APP_PORT}" | 出现 LISTEN、本地绑定地址和端口 | 无输出:应用未监听或端口错误;只绑定 127.0.0.1:外部或容器转发通常不可达;使用 sudo 后再确认所属进程 |
| 容器与端口发布 | Docker 宿主机,拥有 daemon 读取权限 | docker ps --filter "name=^/${CONTAINER_NAME}$" --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | 容器处于 Up,Ports 显示预期宿主机到容器端口映射 | 无容器:核对名称和启动状态;Restarting:查看退出原因;无端口映射:确认是否应由宿主机直接访问 |
| 容器内监听 | Docker 宿主机,拥有 exec 权限;镜像内含 ss | docker exec "$CONTAINER_NAME" sh -c 'ss -lnt' | 应用端口处于 LISTEN,并绑定 0.0.0.0、:: 或预期容器接口 | 命令不存在:使用镜像提供的等效只读工具或临时诊断容器;仅回环监听:修改应用监听配置后按发布流程重启 |
| 容器日志关联 | Docker 宿主机,拥有 daemon 读取权限 | docker logs --since 10m "$CONTAINER_NAME" | 找到同一请求 ID、Trace ID 或时间窗口内的入口与错误记录 | 无记录:请求可能未到容器或日志条件不匹配;有入口无完成:沿对应 Trace 检查线程池、数据库和下游;避免把敏感内容复制到工单 |
生产环境执行请求诊断、docker exec 和读取日志应遵守最小权限与审计要求。需要保存响应体时,应使用访问权限受控的临时路径,并按数据分级规则处理和清理。
命令输出只覆盖它观察到的层。宿主机端口发布正确后,下一步仍要检查容器内应用健康;看到容器访问日志后,还要追踪事务提交;收到 HTTP 200 后,客户端解析结果仍需单独确认。每一步都记录目标地址、实际命中实例、请求标识和时间窗口,再从最后一个可靠事实继续向下验证。
权威资料与规范地址
下列地址可用于查阅相关协议规范、注册表、框架文档和工具手册。表内保留可直接复制的完整链接;状态码、字段、方法和协议扩展等完整枚举,以对应注册表或规范为准。
URL、域名与 DNS
| 主题 | 资料 | 地址 |
|---|---|---|
| URI 通用语法 | RFC 3986 | https://www.rfc-editor.org/rfc/rfc3986.html |
| 浏览器 URL 解析 | WHATWG URL Standard | https://url.spec.whatwg.org/ |
| DNS 根区与顶级域 | IANA Root Zone Database | https://www.iana.org/domains/root/db |
| 国际化域名 | RFC 5891 | https://www.rfc-editor.org/rfc/rfc5891.html |
| DNS 术语 | RFC 9499 | https://www.rfc-editor.org/rfc/rfc9499.html |
| DNS 参数注册表 | IANA Domain Name System Parameters | https://www.iana.org/assignments/dns-parameters/ |
| HTTPS 与 SVCB 记录 | RFC 9460 | https://www.rfc-editor.org/rfc/rfc9460.html |
| 双栈地址选择 | RFC 8305,Happy Eyeballs | https://www.rfc-editor.org/rfc/rfc8305.html |
| DNS 概念与设施 | RFC 1034 | https://www.rfc-editor.org/rfc/rfc1034.html |
HTTP、传输与安全
网络入口、容器与 Servlet
| 主题 | 资料 | 地址 |
|---|---|---|
| Docker 端口发布 | Docker port publishing | https://docs.docker.com/engine/network/port-publishing/ |
| Kubernetes 集群网络 | Cluster Networking | https://kubernetes.io/docs/concepts/cluster-administration/networking/ |
| Kubernetes Service | Service | https://kubernetes.io/docs/concepts/services-networking/service/ |
| Kubernetes Ingress | Ingress | https://kubernetes.io/docs/concepts/services-networking/ingress/ |
| Kubernetes Gateway | Gateway API | https://gateway-api.sigs.k8s.io/ |
| Servlet 请求、过滤与异步契约 | Jakarta Servlet Specification 6.1 | https://jakarta.ee/specifications/servlet/6.1/jakarta-servlet-spec-6.1 |
Spring MVC 与事务
| 主题 | 资料 | 地址 |
|---|---|---|
| DispatcherServlet 处理顺序 | Spring MVC Processing | https://docs.spring.io/spring-framework/reference/7.0/web/webmvc/mvc-servlet/sequence.html |
| HandlerInterceptor 时序 | Spring MVC Interception | https://docs.spring.io/spring-framework/reference/7.0/web/webmvc/mvc-servlet/handlermapping-interceptor.html |
| 响应体写出扩展点 | ResponseBodyAdvice API | https://docs.spring.io/spring-framework/docs/7.0.9/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyAdvice.html |
| Servlet 异步分派 | Spring MVC Asynchronous Requests | https://docs.spring.io/spring-framework/reference/7.0/web/webmvc/mvc-ann-async.html |
| 声明式事务 | Declarative transaction management | https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-decl-explained.html |
流式交互、观测与诊断工具
| 主题 | 资料 | 地址 |
|---|---|---|
| Server-Sent Events | HTML Standard: Server-sent events | https://html.spec.whatwg.org/multipage/server-sent-events.html |
| WebSocket | RFC 6455 | https://www.rfc-editor.org/rfc/rfc6455.html |
| gRPC 调用与流 | Core concepts, architecture and lifecycle | https://grpc.io/docs/what-is-grpc/core-concepts/ |
| 分布式追踪上下文 | W3C Trace Context | https://www.w3.org/TR/trace-context/ |
| 浏览器 Network 面板 | Chrome DevTools Network reference | https://developer.chrome.com/docs/devtools/network/reference |
| curl 详细日志风险 | Known risks: verbose logs | https://curl.se/docs/knownrisks.html#verbose-logs |
| curl 分阶段输出 | curl --write-out manual | https://curl.se/docs/manpage.html#-w |
| curl 指定解析地址 | curl --resolve manual | https://curl.se/docs/manpage.html#--resolve |
| curl 代理环境变量 | curl Environment | https://curl.se/docs/manpage.html#ENVIRONMENT |
| curl 禁用代理 | curl --noproxy manual | https://curl.se/docs/manpage.html#--noproxy |
