从 URL 到页面的请求链
沿地址解析、连接、协商、资源读取和渲染定位一次页面请求。
本页沿 HTTP 的具体接口和通信行为展开。沿用归档中的消息与代码示例,归档所称的实测仅作为原作者历史记录;2026-10-05 整理未向外部站点发送实验请求,也未操作浏览器。命令输出随服务、网络、版本和代理条件变化。
地址栏敲下字符、按下回车,页面"唰"地出现了。这不到一秒,是浏览器跑完的一条流水线:读懂网址 → 查地址 → 接线路 → 验身份 → 发请求 → 收数据 → 抓资源 → 拼页面。本章沿它走一遍,把"网页打开慢"拆到具体某一站。
一、读懂网址:URL 的每一段都有用途
网络世界的URL 用于指认或定位资源;同一资源可以有多个 URL,相对 URL 需要结合基准地址解释——网址,学名 URL(Uniform Resource Locator,统一资源定位符)。标准骨架:
scheme://[username:password@]domain:port/path?key=value#fragment方括号里都是可省略的部分:username:password@ 允许把登录凭据直接写进网址(如今已很少这么用);端口的省略规则见下面。拿真实例子逐段拆开:
https://developer.mozilla.org/en-US/search?q=URLhttps是协议(scheme),约定"用什么方式通信";如今地址栏默认补全的就是它。developer.mozilla.org是主机名,即要访问的机器的名字(下一节细讲)。/en-US/search是路径,指资源在站内的位置;早年对应物理文件,如今大多只是 Web 服务器处理的抽象路径。?q=URL是查询串(query),?开头的键值对,多个用&分隔,用来传参数。- 端口:HTTP 默认 80、HTTPS 默认 443,用默认值可省略,非默认必须写出——端口由协议约定,不是随便选的。
#之后是锚点(fragment),只由浏览器本地处理(如滚动定位),永远不会随请求发给服务器——所以点页内目录跳转不产生新请求。
澄清两个误解:①「
#xxx会发给服务器」——不会,它只活在浏览器里;②「www是必需部分」——不是,www只是普通子域名标签,要不要由网站决定,不带 www 的developer.mozilla.org同样是正常域名。
二、查 DNS:先把名字翻译成门牌号
很多人以为浏览器"直接连到网址上"。其实网络只认 IP 地址(形如 198.18.2.63 的数字),域名只是给人看、还可能变化的别名——不翻译无法通信。翻译由 DNS(Domain Name System,域名系统)完成,它被称作"互联网的电话簿"。
域名从右往左分级:最右是顶级域名(TLD,top-level domain,如 .org),往左是二级域名(如 mozilla.org),再往左是子域名标签(如 developer)。每个标签 1–63 个字符;同一域下可自建子域名,developer.mozilla.org 与 support.mozilla.org 就是同门兄弟。
解析先查缓存。另一个常见误解是"每次都要从根服务器一级级问下来",真实顺序是先查三层缓存、任一层命中即返回:
- 浏览器自己的 DNS 缓存;
- 操作系统的存根解析器(stub resolver)缓存——查询离开本机前的最后一站;
- 递归解析器(recursive resolver,常由 ISP 或公共 DNS 运营)的缓存。
各级缓存按记录的生存时间(TTL,time-to-live)失效——这既解释了改 DNS 为何不是立即生效(TTL 未过,缓存仍返回旧记录),也解释了二次访问同一网站为何快得多。
全未命中才走完整链条:递归解析器替你迭代地问——根域名服务器(root nameserver)→ TLD 服务器 → 权威域名服务器(authoritative nameserver,最终持有记录);根和 TLD 通常不直接给 IP,只回复"下一步问谁"的指引(referral)。一次无缓存查找约 8 步、涉及这 4 类服务器。根服务器只有 13 个标识符(A–M,12 家机构运营),借任播(anycast)扩成约 2045 个实例(2026 年 9 月数据);而且解析器从根得到的 TLD 委派记录可缓存 2 天(172800 秒),根自身的 NS 记录更长、可达 6 天(518400 秒)——所以日常查询极少真的压到根上。
亲眼看一次(dig):
$ dig developer.mozilla.org
;; ANSWER SECTION:
developer.mozilla.org. 1 IN A 198.18.2.63
;; SERVER: 114.114.114.114#53(114.114.114.114)
;; Query time: 0 msec三处细节:#53 表明 DNS 走 53 端口,是独立于 HTTP 的协议,与 80/443 无关;Query time: 0 msec 说明应答几乎零耗时即返回——来自本机就近的解析器缓存,你复现时耗时与 TTL 都会是另一组数字(TTL 由记录的设置方决定);至于 IP,198.18.2.63 出自归档作者本机代理环境的 DNS 应答,落在 IETF 保留的测试网段 198.18.0.0/15 内,并不是真实公网地址——你运行时得到的才会是该域名当时的真实 IP。
三、TCP 三次握手:先接通一条可靠线路
有了 IP,浏览器就能发起连接。但 HTTP 自己不管传输,只要求底层可靠——消息不能丢。TCP(Transmission Control Protocol,传输控制协议)提供可靠传输(UDP 则不保证),HTTP 报文就跑在 TCP 连接(或 TLS 加密的 TCP 连接)上。
建立 TCP 连接要经过三次握手(three-way handshake),像打通一个电话:
- 客户端发 SYN(synchronize,同步):「请求连接,我的初始序号是 X」;
- 服务器回 SYN-ACK:「确认 X(ACK,acknowledgment),我的初始序号是 Y」;
- 客户端再回 ACK:「确认 Y」。
三条报文交换完,连接打开,可以收发数据。
为什么恰好三次? 握手的本质是让双方同步彼此的初始序列号(ISN,initial sequence number):每一方都要"发出自己的序号并得到确认、收到对方的序号并回以确认"。TCP 的权威定义写在 RFC 793 里——RFC(Request for Comments,请求评论)是互联网技术规范的编号文集,一个编号对应一份标准,后文出现的 RFC 编号均可照此理解。RFC 793 用四步描述这一交换(SYN-X → ACK-X → SYN-Y → ACK-Y),第 2、3 步由服务器合并成一个 SYN-ACK,故最少三条。只握两次不够:序列号不与网络中的全局时钟绑定(RFC 793),收到第一个 SYN 的一方无法区分它是新请求,还是延迟到达的旧重复报文;第三条报文正是用来排除这种旧重复请求的混乱。
澄清一个误解:三次握手由双方交换三条报文完成——服务器把"确认客户端"和"自己的同步请求"合并在第二条里。
四、TLS 握手:验明正身,约定加密(HTTPS)
TCP 只保证"送到",不防窃听与假冒。所以在 HTTPS 中,TCP 建好之后、HTTP 请求发出之前,还要进行一次 TLS(Transport Layer Security,传输层安全)握手,完成四件事:① 商定 TLS 版本;② 商定加密套件(cipher suite);③ 借服务器证书与 CA 数字签名验证服务器身份;④ 生成会话密钥,供握手后的数据加密使用。
流程上,客户端先发 Client Hello(协议版本、客户端随机数、加密套件列表),服务器先回 Server Hello(选定的套件、服务器随机数),再紧跟着单独发出自己的证书——证书是紧随其后的另一条握手消息,不是 Server Hello 的组成部分;随后双方验证证书、交换参数、以 Finished 收尾。TLS 1.3 删掉了不安全套件,还允许客户端在 Client Hello 里直接带上密钥交换参数,把握手从 TLS 1.2 的 2 次往返(RTT,round-trip time)压缩到 1 次;重访连过的站点甚至可 0-RTT(IETF 2018 年 8 月发布)。
口径提示:两套数字并不矛盾——Cloudflare 的 2-RTT / 1-RTT 只数 TLS 握手本身;MDN《How browsers work》的"5 次往返"是它对 TLS 协商阶段的估算,"累计 8 次"则是把 DNS、TCP、TLS 都算上、到能发出请求为止的总往返(该文未说明精确计数方式)。本章采用 Cloudflare 的分版本口径,MDN 的数字仅作"冷启动往返次数可观"的保守提醒。
澄清一个误解:HTTPS 不是"全程一把钥匙"。握手阶段用非对称机制(证书验证 + 密钥交换)协商出会话密钥,握手后的实际数据才用对称加密传输。细节留待 HTTPS 一章展开。
五、发请求、收响应:HTTP 报文长什么样
通道就绪,主角 HTTP(HyperText Transfer Protocol,超文本传输协议)登场。它是应用层的客户端-服务端协议:请求永远由客户端(通常是浏览器)发起,服务端的应答叫响应。两类报文结构相同:起始行 → 可选的头部("名称: 值",不分大小写)→ 空行(标志头部结束)→ 可选的体。
请求的起始行叫请求行(request line):方法 + 请求目标 + 协议版本。
GET /en-US/docs/Web/HTTP/Guides/Messages HTTP/1.1GET 用于索取资源,本身不带请求体——只有 PATCH、POST、PUT 才有请求体。POST 常用于提交数据,比如注册新用户(注意中间的空行,Content-Length: 49 标明体的长度):
POST /users HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
name=FirstName+LastName&email=bsmth%40example.com响应的起始行叫状态行(status line):协议版本 + 状态码(status code)+ 原因短语。创建成功时:
HTTP/1.1 201 Created
Content-Type: application/json
Location: http://example.com/users/123
(随后的报文体是 JSON 格式的新用户数据)常见状态码:200 成功;301 永久重定向;400 服务器无法理解请求;403 无权限;404 找不到资源;503 服务器暂时无法处理。
澄清一个误解:服务器不是把整个网页打成一个"大包裹"一次性发来。文件被拆成许多小包(packet),像一批编号的快递分车运送——各包可走不同路线,接收方按 TCP 头部的序列号把它们排回原序再组装;丢一件只补那一件。
六、浏览器接手:一个页面其实是几十上百次请求
第一个响应只带回 HTML 骨架,浏览器并不停手:一边解析 HTML、构建 DOM(Document Object Model,文档对象模型),一边发现新的"待办"。以 MDN 首页为例(归档实验记录):约 12 万字符的 HTML 引用了 20 个 CSS、6 个 JS、2 个 woff2 网页字体(woff2 是浏览器常用的压缩字体格式)等共约 30 个子资源,如:
/static/client/styles-global.13616ebb1fc8ee2f.css
/static/client/runtime.29cc7b70f34a4f45.js
/static/client/inter-latin.9a3b1bc220d426ef.woff2所以「打开一个网页 = 一次请求」是本章最大的误解。HTTP Archive 是一个持续抓取大量真实网站并做统计的项目,其年度报告《Web Almanac》显示:2024 年中位数页面桌面端 71 次请求、移动端 66 次,JavaScript 已超过图片成为请求数最多的资源类型;按 2025 年版数据,把页面按请求数排队,四分之一的桌面端页面超过 122 次(移动端约 116 次),最重的 10% 超过 185 次(移动端约 179 次)——这两个门槛在统计里常写作 p75、p90。
抓取资源时浏览器有几条规矩:图片等非阻塞资源,请求后继续解析;CSS 不阻塞 HTML 解析,但阻塞渲染和 JavaScript 执行;不带 async/defer 的 <script> 阻塞解析。**预加载扫描器(preload scanner)**则在主线程解析之余,提前找出 CSS、JS、字体等高优先级资源并行下载。每个唯一主机名单独查一次 DNS,同一主机名一次加载通常只查一次;首个数据块通常约 14KB——TCP 慢启动的节奏。
七、连接复用:几十次请求不必几十次握手
一个页面几十上百个请求,难道每次都重做 DNS + TCP + TLS 全套?不是——这正是 HTTP 版本演进的主线,一条时间线看全:
| 版本 | 定档 | 关键变化 |
|---|---|---|
| HTTP/0.9 | 1991 年前后 | 只有一行 GET,没有头部 |
| HTTP/1.0 | 1996 | 引入版本号、状态码、头部;但每个请求都要新建 TCP 连接,握手成本反复支付 |
| HTTP/1.1 | 1997(2022 年重构为 RFC 9110/9112) | **连接复用(keep-alive)**省去反复建连,Host 头让一个 IP 托管多个域名;但有队头阻塞(head-of-line blocking),浏览器只好对每个网站最多同时开 6 条 TCP 连接来规避 |
| HTTP/2 | 2015(RFC 7540) | 二进制协议、单连接多路复用(multiplexing)、压缩头部,解决了 HTTP 层的排队;可 TCP 一旦丢包重传,所有流仍要一起等 |
| HTTP/3 | 2022(RFC 9114) | 改用基于 UDP 的 QUIC,丢包检测与重传按流独立,单个流丢包只阻塞该流 |
八、把全程串起来:一次真实访问的时间线
用 curl 的计时参数给流水线装上秒表(依次对应 DNS、TCP、TLS、首字节、总耗时):
$ curl -sL -o /dev/null -w 'DNS解析: %{time_namelookup}s | TCP连接: %{time_connect}s | TLS握手: %{time_appconnect}s | 首字节: %{time_starttransfer}s | 总耗时: %{time_total}s\n' https://developer.mozilla.org/en-US/
DNS解析: 0.0029s | TCP连接: 0.0032s | TLS握手: 0.2516s | 首字节: 0.3589s | 总耗时: 0.4089s先说读法:这些字段是从发起请求起累计到各时刻的耗时,不是各阶段的独立时长——相邻字段相减才是该阶段的净耗时,例如 TLS 阶段 ≈ 0.2516 − 0.0032 ≈ 0.25 秒,直接相加会超过总耗时。这组读数(2026-10-04 归档实验记录下载 MDN 首页)很有意思:DNS 与 TCP 快到几乎看不见——命中了本地缓存或就近节点;TLS 握手却占总耗时约六成。HTTPS 时代,"建连接"常常比"找地址"贵。再用 curl -sv 看 example.com 的完整旅程(归档实验输出的节选,行序有简化,你复现时看到的行会更多更细;IP 是第二节说过的本机代理假地址):
* Trying 198.18.1.193:443...
* Connected to example.com port 443
(OUT) TLS handshake, Client hello
(IN) Server hello / Certificate / CERT verify / Finished
* SSL connection using TLSv1.3
* ALPN: server accepted h2
> GET / HTTP/2
> Host: example.com
> User-Agent: curl/8.7.1
> Accept: */*
< HTTP/2 200连上 443 端口(TCP 完成)→ Client Hello / Server Hello(TLS 握手,顺带商定 HTTP/2)→ GET / 请求 → HTTP/2 200 响应——本章每一站都在这几行里对上了号。
九、动手观察工具箱
curl -v 网址:看请求/响应的报文细节;curl -w '...' 网址:给各阶段计时(见上一节);dig 域名:查 DNS 记录、TTL 与耗时;- 浏览器开发者工具 Network 面板:数一数页面发出多少请求;
chrome://net-internals/#dns:窥探浏览器的 DNS 缓存。
十、下一站预告
本章每一站都值得单独展开:DNS 一章细讲递归与迭代、缓存与解析安全;TCP 一章深入序列号、确认与重传;HTTPS/TLS 一章拆解证书链与两套加密的分工;HTTP 报文与状态码一章梳理方法、头部与状态码;性能优化一章回到"为什么慢",谈连接复用、资源优先级与加载瀑布。
最后更新于