在浏览器输入 HTTPS URL 后:从本地检查到响应首字节
在地址栏输入 https://example.com/products?id=42 并按下回车,看起来只是让浏览器“打开一个页面”。实际上,在页面内容开始返回之前,浏览器可能需要解析地址、检查本地响应、寻找目标 IP、选择传输协议、建立加密通道,再让请求穿过边缘网络到达真正处理它的服务。
但这不是一条每次都完整执行的固定流水线。缓存可以让请求不出网,已有连接可以跳过 DNS、TCP 和 TLS,DNS 查询与候选地址连接可以交错进行,HTTP/3 又不经过 TCP。本文先沿一条便于理解的时间主线展开,再在每个节点说明真实路径可能怎样缩短、并行或分叉。
🧭 先明确起点与终点
本文的起点是浏览器已经得到一个 HTTPS URL,终点是浏览器收到响应的第一个字节,也就是 Navigation Timing 中的 responseStart 附近。
这个终点不等于“网页已经显示出来”。首字节到达后,浏览器还要继续接收响应体、解析 HTML、发现 CSS 和 JavaScript、构建 DOM 与 CSSOM、布局、绘制和合成。那些属于页面加载与渲染阶段,不在本文范围内。
还要区分三种描述:
- 协议要求: RFC 或 Web 标准规定双方必须遵守的行为。
- 常见实现: 主流浏览器和操作系统经常采用的缓存、连接池与并发策略。
- 一次访问的实际路径: 受浏览器版本、代理、网络、缓存状态、服务器能力和隐私分区策略共同影响。
所以下文的编号表达的是因果关系和观察顺序,不承诺所有步骤在时间线上严格串行。
1️⃣ 浏览器先解析 URL
浏览器首先把输入解析成结构化 URL。以 https://example.com/products?id=42 为例:
| 部分 | 值 | 作用 |
|---|---|---|
| scheme | https | 要求使用受 TLS 保护的 HTTP |
| host | example.com | 用于域名解析和服务器身份验证 |
| port | 未显式填写,默认 443 | 目标服务端口 |
| path | /products | HTTP 请求目标的一部分 |
| query | id=42 | HTTP 请求目标的一部分 |
URL 中的 #fragment 不会发送给服务器,它只在客户端用于定位文档片段或交给前端逻辑处理。用户名、密码、非 ASCII 域名、百分号编码和相对路径还会触发额外的 URL 解析规则。
本文假设最终 URL 已经是 HTTPS。若用户输入的是 http:// 或省略了 scheme,浏览器可能先根据地址栏规则、历史记录、搜索策略或 HSTS 将其升级为 HTTPS。HSTS 预加载可以在发出明文 HTTP 请求之前完成升级,从而避免一次容易被劫持的 HTTP 跳转。
重定向发生在更晚的 HTTP 响应阶段。服务器若返回 301、302、303、307 或 308 和新的 Location,浏览器会解析新 URL,并对新目标重新执行必要步骤。新旧 URL 若主机不同,DNS、连接和 TLS 都可能重新发生。
🛑 请求可能根本不会出网
在“DNS 一定是第一步”之前,浏览器先进入 Fetch 与导航处理。这里存在几条本地快速路径。
Service Worker
如果当前站点在对应 scope 下有已激活的 Service Worker,导航请求可以触发它的 fetch 事件。Service Worker 可以:
- 直接构造一个
Response。 - 从 Cache API 返回离线页面或已缓存资源。
- 修改请求后继续访问网络。
- 同时读取缓存和网络,采用先返回者或自定义回退策略。
因此 Service Worker 更像浏览器内部由站点代码控制的一层代理,不等同于普通 HTTP 缓存。长时间未运行的 Service Worker 还可能产生启动开销。
HTTP 缓存
若请求可以由新鲜的 HTTP 缓存响应满足,浏览器可以直接复用响应,无需连接源站。缓存项过期时,浏览器也可能发送带 If-None-Match 或 If-Modified-Since 的条件请求;服务器返回 304 Not Modified 后继续复用本地正文。
开发者工具中常见的 memory cache、disk cache 是实现层面的存储标签,并不是 HTTP 规范定义的两套缓存语义。现代浏览器还会按顶级站点等维度对缓存和连接状态分区,以降低跨站跟踪风险。
已有连接
即使必须发出网络请求,浏览器也可能已经持有到目标源或可合并源的可用连接。此时域名信息、TCP、TLS 或 QUIC 不一定重新执行。在 Navigation Timing 中,DNS 和连接阶段的起止时间可能相同,这往往表示缓存或连接复用,而不是浏览器“漏记了耗时”。
2️⃣ 域名怎样变成目标地址
只有在需要网络访问且没有足够的可复用连接信息时,浏览器才需要把主机名转换成可连接的目标。
先查可用的本地信息
常见实现可能利用浏览器 DNS 缓存、操作系统解析器缓存、hosts 配置或安全 DNS 组件。具体查找顺序不是 Web 应用可以依赖的接口;企业策略、VPN、代理和浏览器的 DoH 设置都可能改变它。
如果没有可用结果,客户端把问题交给递归解析器。解析器会尽量使用自己的缓存;缓存未命中时,它才沿 DNS 委派关系查询根、顶级域和权威 DNS。对站点连接而言,常见结果包括:
A:IPv4 地址。AAAA:IPv6 地址。CNAME:别名,需要继续解析最终名称。HTTPS/SVCB:可能提供替代端点、端口或应用协议提示。
TTL 控制 DNS 数据可以被缓存多久,但浏览器、操作系统和递归解析器可能设置自己的上下限。CDN 也可能根据递归解析器、EDNS Client Subnet、Anycast 或其他调度信息返回不同地址,所以“同一个域名”并不保证所有用户连接同一台机器。
DNS 不一定全部使用明文 UDP
传统 DNS 常使用 UDP 53;响应过大、需要重试或特定操作时可以使用 TCP。DoT 把 DNS 放进 TLS,DoH 则通过 HTTPS 发送查询。浏览器启用安全 DNS 后,查询路径甚至可能绕过操作系统默认递归解析器。
A 与 AAAA 不是简单地先等一个再等另一个
双栈客户端通常同时或交错获取 IPv6 与 IPv4 候选地址,再按策略排序并发起连接尝试。Happy Eyeballs 的目标是在偏好 IPv6 的同时,避免某个地址族故障导致用户长时间等待。它允许异步 DNS 查询和错峰连接竞争;某个候选连接成功后,其他尝试会被取消。
因此开发者看到一条最终连接,并不代表浏览器只查询过一个地址或只尝试过一次连接。
3️⃣ 复用连接,还是建立新连接
得到候选目标后,浏览器会结合代理配置、连接池、源站身份和协议能力选择路径。企业环境可能先执行 PAC 规则或连接显式代理;HTTPS 经过 HTTP 代理时,浏览器通常先用 CONNECT 建立隧道,再在隧道内与目标完成 TLS。
先决定下一跳
若目标不在本地链路,操作系统根据路由表选择网卡和下一跳网关。发送以太网或 Wi-Fi 帧前,还要通过 ARP(IPv4)或 Neighbor Discovery(IPv6)找到下一跳的链路层地址。家庭或办公网络常在出口执行 NAT,把私网地址和端口映射成公网连接。
这些动作可能已有缓存,例如 ARP/邻居表和 NAT 映射,因此不会在每次导航中都完整出现。
HTTP/1.1 与 HTTP/2 的常见 HTTPS 路径
如果没有可复用连接,传统路径先建立 TCP 连接。客户端发送 SYN,服务端返回 SYN-ACK,客户端回复 ACK。三次握手的主要目标是确认双方收发能力、初始化序列号并协商 TCP 选项。
TCP 成功后,HTTPS 再在这条可靠、按序的字节流上执行 TLS 握手。HTTP/1.1 可以在持久连接上连续发送请求;HTTP/2 则在一条连接里建立多个并发流,避免为每个请求单独创建连接。
HTTP/2 的多路复用不意味着 TCP 的队头阻塞消失:一个 TCP 包丢失时,后续字节必须等待重传,连接上的多个 HTTP/2 流都可能暂停。
HTTP/3 的路径不同
HTTP/3 使用 QUIC 作为传输,而 QUIC 通常运行在 UDP 之上。它把可靠传输、多路复用、拥塞控制和 TLS 1.3 握手组合在 QUIC 连接建立过程中,因此不存在“先 TCP 三次握手,再单独做 TLS,最后升级 HTTP/3”这条路径。
浏览器可以通过之前保存的替代服务信息、Alt-Svc、HTTPS DNS 记录或其他先验信息发现 HTTP/3 端点。实际实现还可能让 QUIC 与 TCP 路径竞争,并在 UDP 受阻时回退到 HTTP/2 或 HTTP/1.1。
QUIC 把可靠性放到流级别。某个流的数据丢失,不必像 TCP 那样阻塞同一连接中所有其他流;但拥塞控制、连接级流量控制和网络质量仍会影响整体性能。
4️⃣ TLS 1.3 怎样建立安全通道
截至 2026 年,TLS 1.3 的现行基础规范是 RFC 9846,它以兼容更新的方式取代了 RFC 8446。TLS 不只是“把内容加密”,它要为连接提供三类核心属性:服务器身份认证、机密性和完整性。
ClientHello 提供什么
客户端首先发送 ClientHello,其中通常包含:
- 支持的 TLS 版本和密码算法参数。
- 用于协商共享密钥的 key share。
- 签名算法等能力列表。
- SNI 或等价机制,用来表达目标主机。
- ALPN,用来协商
h2、http/1.1等应用协议。 - 会话恢复所需的 PSK 标识(如果存在)。
传统 SNI 会暴露主机名;支持并成功协商 ECH 时,客户端可以保护 ClientHello 中更敏感的部分。但不能假设每次连接都已使用 ECH。
服务端证明“我是谁”
服务端选择参数,返回自己的 key share,并在常见的证书认证模式下发送证书链、证书签名证明和 Finished 消息。浏览器需要验证的并不只是“证书能不能解密”,而是:
- 证书链能否连接到本地信任的根。
- 当前时间是否在证书有效期内。
- 证书标识是否匹配请求主机名。
- 签名和握手 transcript 是否有效。
- 浏览器策略要求的证书透明度、撤销信息或其他约束是否满足。
撤销检查的具体机制和失败策略因浏览器而异,不能简单描述成每次握手都同步请求一次 OCSP。
验证成功后,双方从握手产生的共享材料派生流量密钥,后续 HTTP 数据以 TLS record 保护。被动观察者仍能看到 IP、端口、包长度和时序等元数据;TLS 不会把整条网络路径变得不可观察。
完整握手、恢复与 0-RTT
TLS 1.3 的常规完整握手通常可以在一次网络往返后让客户端发送受保护的应用数据。访问过该服务的客户端还可能使用会话票据恢复连接,减少计算和部分握手成本。
0-RTT 允许恢复连接时更早发送应用数据,但这部分 early data 不具备完整的重放保护。服务端和应用必须限制可接受的操作,不能把涉及资金、状态变更或不可幂等行为的请求默认当成安全的 0-RTT 数据。
5️⃣ HTTP 请求怎样发出去
安全通道可用后,浏览器根据 Fetch、Cookie、CSP、Referrer Policy 和凭据策略构造真正的导航请求。概念上它包含:
GET /products?id=42 HTTP/1.1
Host: example.com
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, br, zstd
Cookie: session=...这只是便于阅读的 HTTP/1.1 文本表示。实际传输形式取决于协议:
- HTTP/1.1: 使用文本形式的起始行和头字段,默认支持持久连接。
- HTTP/2: 把请求映射为二进制帧和流,使用 HPACK 压缩头字段,并在连接上多路复用。
- HTTP/3: 把请求映射到 QUIC 流,使用 QPACK 压缩头字段。
浏览器不会无条件附带所有 Cookie。Secure、SameSite、Domain、Path、过期时间和第三方 Cookie 策略都会参与选择。授权信息、客户端提示和优先级信息也可能改变请求头。
对于地址栏导航,常见方法是 GET,通常没有请求体。浏览器把请求交给已选择的连接后,数据还会被 TLS/QUIC、传输层、IP 层和链路层逐层封装。网卡发送的是帧,路由器转发的是 IP 包,远端 TLS 终止点最终恢复出 HTTP 消息。
6️⃣ 请求怎样到达 CDN 或源站
IP 包先到本地网关和运营商网络,再经过一系列路由器到达目标网络。BGP 参与不同自治系统之间的可达性与路径选择,但浏览器不会为每次请求亲自运行 BGP;它只把包交给操作系统选定的下一跳。
中途每台路由器根据转发表选择下一跳,IP 的 TTL 或 Hop Limit 随转发递减。实际去程和回程可以不对称,也可能因拥塞、故障或流量工程发生变化。
目标 IP 经常不是业务应用服务器本身,而是边缘入口:
浏览器
↓
本地网关 / NAT
↓
ISP 与互联网路由
↓
CDN / WAF / 四层或七层负载均衡
↓
反向代理 / API 网关
↓
应用服务这条链并非所有站点都具备。小型站点可能直接在一台服务器终止 TLS;大型站点则可能在 CDN 边缘终止客户端连接,再通过另一条受保护连接回源。客户端看到的 remoteAddress、证书和协议,只能直接描述客户端到 TLS 终止点这一段。
WAF 可以检查和阻断恶意请求,负载均衡把流量分配到健康实例,反向代理负责路由、压缩、缓存或协议转换。它们每增加一跳都可能增加处理与排队,但也能通过就近接入、连接池和缓存减少总体延迟。
7️⃣ 边缘命中与源站处理
到达 CDN 后,边缘节点会按缓存键、缓存新鲜度、请求方法、鉴权信息和站点规则判断能否直接响应。命中时,请求可以不回源,用户通常得到更低的网络往返延迟。
未命中或不可缓存时,边缘节点向源站发起请求。源站内部可能依次经过:
- 网关匹配路由和安全策略。
- 应用恢复会话并执行认证授权。
- 读取进程内缓存、Redis、数据库或下游服务。
- 生成状态码、响应头和正文。
- 把首批响应数据交回代理或客户端连接。
这同样不是必经清单。静态文件可能由 Web 服务器直接返回,SSR 页面可能等待多个后端,流式响应则可能先发送头部或页面骨架,再继续计算后续内容。
从客户端看,“Waiting for server response”不纯粹等于应用代码耗时。它还包含请求剩余传输、边缘与代理处理、回源网络、排队、后端计算,以及首批响应数据返回客户端的时间。
8️⃣ 什么叫“响应开始返回”
服务端或边缘节点开始响应时,会先产生状态码和响应头,然后才是响应体。例如:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: public, max-age=60
Content-Encoding: br对 Navigation Timing 而言,responseStart 表示用户代理开始收到响应数据的时间点。传统 TTFB 通常按下面的方式理解:
TTFB = responseStart - navigationStart它可能包含重定向、Service Worker 启动、缓存检查、DNS、连接、TLS、请求发送、服务端与边缘处理,以及首字节返回的网络时间。TTFB 高并不能直接证明数据库慢;DNS、丢包、连接建立、代理排队和跨区域回源都可能是原因。
103 Early Hints 让“第一个响应”更复杂
服务器可能先发送 103 Early Hints,提示浏览器预加载关键资源,再发送最终的 200 等响应。不同工具和浏览器版本对 TTFB 与 interim response 的呈现曾有差异。较新的 Resource/Navigation Timing 模型提供 interimResponseStart 和 finalResponseHeadersStart 等字段,用于区分首个临时响应和最终响应头;使用性能数据时应先确认目标浏览器与采集工具的口径。
本文到这里结束:浏览器已经开始收到响应,但页面尚未完成接收、解析或渲染。
🌡️ 冷访问、热访问与缓存命中
同一个 URL 的实际链路可能差别很大:
| 场景 | DNS | 新连接 | TLS / QUIC 握手 | 服务器处理 | 典型路径 |
|---|---|---|---|---|---|
| 首次冷访问 | 通常需要 | 通常需要 | 完整握手 | 通常需要 | 最接近本文完整主线 |
| DNS 已缓存 | 可跳过外部查询 | 可能需要 | 可能需要 | 通常需要 | 从候选地址开始连接 |
| 已有 HTTP/2/3 连接 | 通常无需重新暴露 | 不需要 | 不需要 | 通常需要 | 直接创建新流并发送请求 |
| TLS/QUIC 会话恢复 | 可能缓存 | 需要新连接 | 恢复握手 | 通常需要 | 连接成本低于完整握手 |
| HTTP 缓存新鲜命中 | 不需要 | 不需要 | 不需要 | 不需要 | 浏览器本地返回 |
| Service Worker 本地响应 | 由脚本策略决定 | 不需要 | 不需要 | 不需要 | Worker 构造或读取响应 |
| CDN 边缘命中 | 客户端侧可能需要 | 视连接状态 | 视连接状态 | 无需回源应用 | 边缘直接返回 |
“刷新页面”也不是单一行为。普通刷新、强制刷新、禁用缓存、无痕窗口和关闭后重新打开浏览器,对 HTTP 缓存、Service Worker、DNS 缓存、连接池和会话票据的影响各不相同。
🧪 用开发者工具定位慢在哪里
Chrome、Edge、Firefox 等开发者工具的字段名称略有不同,但通常可以看到 Queueing/Stalled、DNS、Initial connection、SSL、Request sent、Waiting/TTFB 和 Content Download。
一个实用的判断顺序是:
- Queueing / Stalled 高: 检查浏览器连接限制、请求优先级、代理、Service Worker 和本机资源争用。
- DNS 高: 检查解析器、TTL、CNAME 链、DoH 路径和用户地域。
- Initial connection 高: 检查网络 RTT、IPv6/IPv4 失败回退、丢包和跨地域入口。
- SSL 高: 检查证书链、TLS 协商、会话恢复和 TLS 终止点距离。
- Waiting / TTFB 高: 再拆分 CDN 是否命中、回源耗时、代理排队和后端处理。
- Content Download 高: 这已经越过本文首字节边界,重点转向响应大小、压缩、带宽和流式传输。
页面内也可以读取 Navigation Timing:
const [navigation] = performance.getEntriesByType('navigation');
console.table({
redirect: navigation.redirectEnd - navigation.redirectStart,
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
tcpAndTls: navigation.connectEnd - navigation.connectStart,
tls: navigation.secureConnectionStart > 0
? navigation.connectEnd - navigation.secureConnectionStart
: 0,
requestToFirstByte: navigation.responseStart - navigation.requestStart,
totalTtfb: navigation.responseStart - navigation.startTime,
nextHopProtocol: navigation.nextHopProtocol,
});解释这些数字时注意:
- DNS 或 connect 为
0,可能表示缓存、连接复用或时间字段折叠。 connectEnd - connectStart通常包含传输连接和安全握手,不应再与 TLS 时间重复相加。nextHopProtocol描述浏览器直接连接的下一跳协议,不一定等于边缘到源站的协议。- 跨源资源需要服务器允许 Resource Timing 暴露详细信息;主文档导航不受完全相同的限制。
- 实验室的一次瀑布图不能代表所有用户,应结合真实用户监控的地域和分位数。
🧠 最终心智模型
可以把整个过程压缩成五个问题:
- 本地已经有答案吗? Service Worker、HTTP 缓存或本地资源能否直接返回。
- 应该连接谁? DNS、代理、候选地址和边缘调度确定目标。
- 能否复用安全通道? 连接池决定复用,冷连接才建立 TCP + TLS 或 QUIC。
- 请求最终由谁处理? CDN、WAF、代理、网关或源站应用逐层决定。
- 首字节为何此时才到? 客户端准备、网络往返、排队和服务端处理共同组成等待。
因此,“输入 URL 后发生了什么”的标准答案不应只背诵 DNS → TCP → TLS → HTTP。更准确的模型是:先寻找可复用的本地与连接状态,再为缺失部分执行网络工作;浏览器、操作系统、边缘网络和源站共同决定最终路径。
📚 参考资料
- WHATWG URL Standard
- WHATWG Fetch Standard
- W3C Navigation Timing Level 2
- W3C Resource Timing
- RFC 9846:TLS 1.3
- RFC 8305:Happy Eyeballs Version 2
- RFC 9000:QUIC
- RFC 9110:HTTP Semantics
- RFC 9111:HTTP Caching
- RFC 9113:HTTP/2
- RFC 9114:HTTP/3
- RFC 9525:Service Identity in TLS
- MDN:HTTP caching
- MDN:Service Worker API
- web.dev:Time to First Byte