ai-notebook

从输入 URL 到页面渲染完全指南

当你在浏览器地址栏输入一个 URL 并按下回车,背后会经历 URL 解析、DNS 查询、TCP 连接、TLS 握手、HTTP 请求与响应、服务器处理以及浏览器渲染等一系列步骤,最终将页面呈现在你眼前。

1. 概述

1.1 全局流程

从输入 URL 到页面渲染完成,整个过程可以用下面的流程图概括:

flowchart LR
    A[URL 解析] --> B[DNS 查询]
    B --> C[TCP 连接]
    C --> D[TLS 握手]
    D --> E[HTTP 请求]
    E --> F[服务器处理]
    F --> G[HTTP 响应]
    G --> H[浏览器渲染]
阶段 核心任务 关键协议/技术
URL 解析 拆解协议、主机名、路径等信息 URL 规范
DNS 查询 将域名解析为 IP 地址 DNS
TCP 连接 建立可靠的传输通道 TCP 三次握手
TLS 握手 建立加密安全通道 TLS 1.2 / 1.3
HTTP 请求 发送请求报文 HTTP/1.1、HTTP/2
服务器处理 路由、业务逻辑、数据库查询等 后端框架
HTTP 响应 返回状态码、响应头和响应体 HTTP
浏览器渲染 解析 HTML/CSS/JS,构建渲染树绘制 浏览器引擎

1.2 用 curl -v 观测全过程

curl -v 是观测网络请求全过程的利器。一条命令就能看到 DNS 解析、TCP 连接、TLS 握手、HTTP 请求与响应的完整细节:

curl -v https://httpbin.org/get

提示-v(verbose)标志会输出连接过程中每个阶段的详细信息,是理解网络请求流程最直观的方式。

完整输出如下,后续章节会逐段拆解分析:

* Host httpbin.org:443 was resolved.
* IPv6: (none)
* IPv4: 198.18.0.40
*   Trying 198.18.0.40:443...
* Connected to httpbin.org (198.18.0.40) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=httpbin.org
*  start date: Jul 20 00:00:00 2025 GMT
*  expire date: Aug 17 23:59:59 2026 GMT
*  subjectAltName: host "httpbin.org" matched cert's "httpbin.org"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
*  SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://httpbin.org/get
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: httpbin.org]
* [HTTP/2] [1] [:path: /get]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
> GET /get HTTP/2
> Host: httpbin.org
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
< date: Sun, 01 Mar 2026 14:54:46 GMT
< content-type: application/json
< content-length: 256
< server: gunicorn/19.9.0
< access-control-allow-origin: *
< access-control-allow-credentials: true
<
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "curl/8.7.1",
    "X-Amzn-Trace-Id": "Root=1-69a45336-01c9a5360d78dba746e05f2d"
  },
  "origin": "140.235.140.187",
  "url": "https://httpbin.org/get"
}
* Connection #0 to host httpbin.org left intact

在这段输出中,可以清晰地看到各阶段的对应关系:

curl 输出标志 对应阶段
Host ... was resolved DNS 查询
Trying ... Connected to TCP 连接
TLS handshake 相关行 TLS 握手
> 开头的行 HTTP 请求
< 开头的行 HTTP 响应
JSON 响应体 服务器处理结果

2. URL 解析

2.1 URL 的组成部分

https://httpbin.org/get 为例,一个完整的 URL 可以拆解为以下几个组成部分:

组成部分 说明
协议 https 指定使用的应用层协议,https 表示通过 TLS 加密的 HTTP 通信
域名 httpbin.org 目标服务器的主机名,后续需要通过 DNS 解析为 IP 地址
端口 443(隐含) HTTPS 默认端口为 443,HTTP 默认为 80,省略时使用协议对应的默认端口
路径 /get 服务器上的资源路径,告诉服务器客户端请求的是哪个资源

面试提示:完整的 URL 还可以包含查询参数(?key=value)、片段标识符(#section)等部分。面试时能完整说出 URL 的各组成部分,说明你对 HTTP 协议有扎实的理解。

2.2 浏览器的判断逻辑

当用户在地址栏输入内容并按下回车时,浏览器首先需要判断输入的是一个 URL 还是搜索关键词。判断逻辑大致如下:

  1. 包含协议前缀:如果输入以 http://https:// 开头,直接当作 URL 处理
  2. 包含域名特征:如果输入包含 .(如 httpbin.org),浏览器倾向于将其视为域名
  3. 特殊字符判断:包含 :///: 等 URL 特征字符时,优先作为 URL 处理
  4. 其他情况:将输入内容发送给默认搜索引擎进行搜索

面试提示:这个判断逻辑看似简单,但它解释了为什么输入 localhost 会访问本地服务器,而输入一个普通单词会触发搜索。

2.3 HSTS 检查

在确定输入是 URL 之后,如果用户输入的是 http:// 协议,浏览器会检查该域名是否在 HSTS(HTTP Strict Transport Security) 列表中。如果是,浏览器会在发出请求之前将 http:// 强制替换为 https://

HSTS 有两种生效方式:

面试提示:HSTS 的核心价值在于防止 SSL 剥离攻击(SSL Stripping)——攻击者在中间将 HTTPS 降级为 HTTP,从而窃取明文数据。Preload List 则解决了”首次访问”的安全盲区。

3. DNS 解析

3.1 curl 中的 DNS

回顾 curl -v 输出的第一段,可以看到 DNS 解析的结果:

* Host httpbin.org:443 was resolved.
* IPv6: (none)
* IPv4: 198.18.0.40

这三行告诉我们:浏览器(或 curl)将域名 httpbin.org 解析为了 IP 地址 198.18.0.40,没有获取到 IPv6 地址。这个看似简单的结果,背后经历了一套完整的 DNS 查询流程。

3.2 DNS 查询流程

DNS 查询采用分层缓存和递归查询机制,完整流程如下:

缓存层级(按查询顺序):

  1. 浏览器 DNS 缓存:浏览器自身维护的缓存,TTL 通常较短(Chrome 默认 60 秒)
  2. 操作系统 DNS 缓存:OS 级别的缓存,如 macOS 的 mDNSResponder、Linux 的 systemd-resolved
  3. 路由器 DNS 缓存:家用路由器通常会缓存 DNS 记录
  4. ISP DNS 服务器:互联网服务提供商的 DNS 服务器
  5. 递归查询:如果以上缓存全部未命中,DNS 递归解析器会从根域开始逐级查询

当所有缓存未命中时,递归解析器会执行以下查询过程:

sequenceDiagram
    participant B as 浏览器
    participant L as 本地DNS缓存
    participant R as 递归解析器
    participant Root as 根DNS服务器
    participant Org as .org DNS服务器
    participant Auth as 权威DNS服务器

    B->>L: 查询 httpbin.org
    L-->>B: 未命中
    B->>R: 查询 httpbin.org
    R->>Root: 查询 httpbin.org
    Root-->>R: 返回 .org NS 记录
    R->>Org: 查询 httpbin.org
    Org-->>R: 返回 httpbin.org NS 记录
    R->>Auth: 查询 httpbin.org
    Auth-->>R: 返回 198.18.0.40
    R-->>B: 返回 198.18.0.40

面试提示:面试官常问”DNS 用的是 TCP 还是 UDP?”答案是 默认使用 UDP(端口 53),因为 DNS 查询报文通常很小,UDP 的无连接特性更高效。但当响应数据超过 512 字节(如区域传输)时会切换到 TCP。

3.3 DNS 优化

在实际开发中,有多种 DNS 优化手段可以提升页面加载速度:

<link rel="dns-prefetch" href="//cdn.example.com" />
<link rel="dns-prefetch" href="//api.example.com" />
<link rel="preconnect" href="https://cdn.example.com" />

面试提示dns-prefetchpreconnect 是前端性能优化的常见考点。关键区别是 dns-prefetch 只做 DNS 解析,而 preconnect 会建立完整连接。对于关键资源用 preconnect,对于可能用到的资源用 dns-prefetch

4. TCP 连接

4.1 curl 中的 TCP

DNS 解析拿到 IP 地址后,下一步就是建立 TCP 连接。curl -v 输出中对应的部分:

*   Trying 198.18.0.40:443...
* Connected to httpbin.org (198.18.0.40) port 443

Trying 表示正在向目标 IP 的 443 端口发起 TCP 连接,Connected 表示三次握手已经成功完成,连接建立。

4.2 三次握手

TCP 使用三次握手(Three-Way Handshake)来建立可靠连接。这个过程确保通信双方都具备发送和接收数据的能力:

sequenceDiagram
    participant C as 客户端
    participant S as 服务器

    C->>S: SYN (seq=x)
    Note right of S: 客户端请求建立连接
    S->>C: SYN-ACK (seq=y, ack=x+1)
    Note left of C: 服务器确认并请求建立连接
    C->>S: ACK (ack=y+1)
    Note right of S: 客户端确认,连接建立

三次握手各步骤详解:

  1. SYN(同步):客户端发送 SYN 报文,携带一个随机初始序列号 x,表示请求建立连接。客户端进入 SYN_SENT 状态
  2. SYN-ACK(同步确认):服务器收到 SYN 后,回复 SYN-ACK 报文,携带自己的随机初始序列号 y,并将确认号设为 x+1。服务器进入 SYN_RCVD 状态
  3. ACK(确认):客户端收到 SYN-ACK 后,发送 ACK 报文,确认号为 y+1。双方进入 ESTABLISHED 状态,连接建立完成

4.3 为什么是三次握手

面试中经常会问:”为什么 TCP 需要三次握手,两次不行吗?”

核心原因是防止历史连接请求导致资源浪费。考虑以下场景:

  1. 客户端发送了一个 SYN 请求(第一次),但由于网络延迟,这个包在网络中滞留了
  2. 客户端超时后重新发送了新的 SYN 并成功建立连接、完成通信、关闭连接
  3. 此时那个滞留的旧 SYN 到达了服务器

如果只有两次握手,服务器收到旧 SYN 后会直接建立连接并分配资源,但客户端不会响应这个连接,导致服务器资源被白白占用。

有了三次握手,服务器回复 SYN-ACK 后需要等待客户端的 ACK 确认。客户端收到这个 SYN-ACK 后,发现不是自己发起的连接请求,会回复 RST(重置)报文,服务器随即释放资源。

面试提示:三次握手的本质是确认双方的发送和接收能力。第一次握手确认客户端能发送,第二次确认服务器能接收和发送,第三次确认客户端能接收。这是建立可靠双向通信的最小次数。

5. TLS 握手

5.1 curl 中的 TLS 握手

TCP 连接建立后,由于我们访问的是 https:// 协议,客户端和服务器需要通过 TLS 握手建立加密通道。curl -v 输出中 TLS 握手的完整过程如下:

* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=httpbin.org
*  start date: Jul 20 00:00:00 2025 GMT
*  expire date: Aug 17 23:59:59 2026 GMT
*  subjectAltName: host "httpbin.org" matched cert's "httpbin.org"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
*  SSL certificate verify ok.

这段输出信息量很大,包含了 TLS 握手的每一个步骤。其中 (OUT) 表示客户端发出的消息,(IN) 表示从服务器收到的消息,括号中的数字是 TLS 握手消息类型编号。接下来逐步拆解。

5.2 TLS 握手流程详解

TLS 1.2 的握手过程需要 2 个 RTT(往返时间)才能完成,比 TCP 的 1 个 RTT 多了一倍。这也是为什么 HTTPS 比 HTTP 建立连接更慢的原因之一。

第一步:Client Hello(客户端问候)

* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none

客户端向服务器发送 Client Hello 消息,包含以下关键信息:

CAfile 指向本地的证书信任库 /etc/ssl/cert.pem,客户端稍后会用它来验证服务器证书。

第二步:Server Hello(服务器问候)

* (304) (IN), TLS handshake, Server hello (2):

服务器从客户端提供的选项中做出选择,回复 Server Hello 消息:

第三步:Certificate(服务器证书)

* TLSv1.2 (IN), TLS handshake, Certificate (11):

服务器将自己的数字证书发送给客户端。证书中包含服务器的公钥和身份信息(如 CN=httpbin.org),由受信任的 CA(证书颁发机构)签名。客户端会验证证书的合法性,这一步是防止中间人攻击的关键。

第四步:Server Key Exchange(服务器密钥交换)

* TLSv1.2 (IN), TLS handshake, Server key exchange (12):

由于使用的是 ECDHE 密钥交换算法,服务器需要发送额外的参数——ECDHE 的公钥参数(椭圆曲线参数和服务器的临时公钥)。服务器用自己的 RSA 私钥对这些参数进行签名,确保参数在传输过程中不被篡改。

为什么需要 ECDHE? 因为 ECDHE 提供了前向安全性(Forward Secrecy):每次握手都会生成新的临时密钥对,即使服务器的长期私钥将来被泄露,之前的通信内容也无法被解密。

第五步:Server Hello Done

* TLSv1.2 (IN), TLS handshake, Server finished (14):

服务器发送 Server Hello Done 消息,告知客户端:我这边的握手消息发送完毕了。curl 将此消息显示为 Server finished (14),其中 14 是 TLS 握手消息类型 ServerHelloDone 的编号。

第六步:Client Key Exchange + Change Cipher Spec + Finished

* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):

这三条消息是客户端连续发出的:

第七步:服务器确认

* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):

服务器也发送 Change Cipher Spec 和 Finished 消息,确认双方进入加密通信状态。至此,TLS 握手完成,安全通道建立成功。

完整的 TLS 1.2 握手流程如下:

sequenceDiagram
    participant C as 客户端
    participant S as 服务器

    Note over C,S: 第 1 个 RTT
    C->>S: Client Hello(TLS版本、加密套件列表、客户端随机数、SNI)
    S->>C: Server Hello(选定加密套件、服务器随机数)
    S->>C: Certificate(服务器证书 + 公钥)
    S->>C: Server Key Exchange(ECDHE 参数)
    S->>C: Server Hello Done

    Note over C,S: 第 2 个 RTT
    C->>C: 验证服务器证书
    C->>S: Client Key Exchange(客户端 ECDHE 公钥)
    C->>S: Change Cipher Spec
    C->>S: Finished(加密的握手摘要)
    S->>C: Change Cipher Spec
    S->>C: Finished(加密的握手摘要)

    Note over C,S: 加密通道建立完成

面试提示:TLS 1.3 将握手从 2-RTT 优化到了 1-RTT,甚至支持 0-RTT 恢复。核心改进是在 Client Hello 中就附带密钥交换参数,减少了一次往返。面试时提到 TLS 1.2 和 1.3 的区别是加分项。

5.3 证书验证

TLS 握手完成后,curl 输出了服务器证书的详细信息:

* Server certificate:
*  subject: CN=httpbin.org
*  start date: Jul 20 00:00:00 2025 GMT
*  expire date: Aug 17 23:59:59 2026 GMT
*  subjectAltName: host "httpbin.org" matched cert's "httpbin.org"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
*  SSL certificate verify ok.

客户端(浏览器或 curl)验证证书时会检查以下几项:

  1. 证书链验证httpbin.org 的证书由 Amazon RSA 2048 M03 签发,而 Amazon 的中间 CA 证书又由 Amazon 的根 CA 签发。客户端沿着证书链向上追溯,直到找到本地信任库(/etc/ssl/cert.pem)中预装的根证书,形成完整的信任链
  2. 域名匹配:检查证书的 subjectCN=httpbin.org)和 subjectAltName 是否与实际访问的域名一致。输出中的 host "httpbin.org" matched cert's "httpbin.org" 确认了匹配成功
  3. 有效期检查:证书的 start dateexpire date 范围内才有效。当前日期必须在 2025-07-202026-08-17 之间
  4. 吊销状态检查:通过 OCSP(在线证书状态协议)或 CRL(证书吊销列表)检查证书是否已被吊销

最终 SSL certificate verify ok. 表示所有验证均通过。

面试提示:面试中常问”HTTPS 如何防止中间人攻击?”核心就是证书验证机制。中间人无法伪造受信任 CA 签名的证书,因此即使截获通信也无法冒充服务器。如果证书验证失败,浏览器会显示安全警告,阻止用户访问。

5.4 ALPN 协商

在 TLS 握手过程中,还嵌套了一个 ALPN(Application-Layer Protocol Negotiation,应用层协议协商) 过程:

* ALPN: curl offers h2,http/1.1
* ALPN: server accepted h2

第一行表示客户端在 Client Hello 中告知服务器:”我支持 HTTP/2(h2)和 HTTP/1.1,优先使用 HTTP/2。”第二行表示服务器在 Server Hello 中回复:”我选择 HTTP/2。”

ALPN 的作用是在 TLS 握手阶段就确定应用层协议,避免建立连接后再协商导致的额外往返。这也是为什么 HTTP/2 在实践中几乎总是与 HTTPS 一起使用——ALPN 需要 TLS 扩展来承载。

面试提示:HTTP/2 规范本身并不强制要求 TLS,但所有主流浏览器都只在 HTTPS 连接上支持 HTTP/2。原因之一就是 ALPN 提供了一种优雅的协议升级方式,不需要像 HTTP/1.1 的 Upgrade 头那样多一次往返。

5.5 加密套件解读

握手完成后,curl 输出了最终选定的加密套件:

* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / [blank] / UNDEF

ECDHE-RSA-AES128-GCM-SHA256 这一长串名称实际上由四个组件组成,每个组件在加密通信中承担不同角色:

组件 作用
密钥交换 ECDHE 椭圆曲线 Diffie-Hellman 临时密钥交换,让双方在不安全的信道上协商出共享密钥,”临时”意味着每次连接使用新密钥对,提供前向安全性
认证 RSA 使用 RSA 算法验证服务器身份,即用服务器的 RSA 私钥签名握手参数,客户端用证书中的公钥验证签名
加密 AES128-GCM 使用 128 位密钥的 AES 对称加密算法,GCM 模式同时提供加密和数据完整性校验(AEAD),无需额外的 MAC 计算
摘要 SHA256 用于 PRF(伪随机函数)生成密钥材料和握手过程中的消息验证

这四个组件协同工作:ECDHE 负责安全地交换密钥,RSA 确保通信对象的身份可信,AES128-GCM 负责对实际数据进行高速加密和完整性保护,SHA256 保障握手过程和密钥派生的安全性。

面试提示:面试官可能会问”对称加密和非对称加密在 HTTPS 中分别用在哪里?”答案是:非对称加密(RSA/ECDHE)用于握手阶段的身份验证和密钥交换对称加密(AES)用于后续的数据传输。原因是非对称加密计算成本高,不适合大量数据传输;对称加密速度快但需要双方共享密钥,而密钥交换正是靠非对称加密来安全完成的。

6. HTTP 请求与响应

6.1 curl 中的 HTTP/2 请求

TLS 握手完成并通过 ALPN 协商确定使用 HTTP/2 后,客户端开始发送 HTTP 请求。curl -v 输出中对应的部分:

* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://httpbin.org/get
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: httpbin.org]
* [HTTP/2] [1] [:path: /get]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
> GET /get HTTP/2
> Host: httpbin.org
> User-Agent: curl/8.7.1
> Accept: */*

这段输出包含两层信息。以 * 开头的行展示了 HTTP/2 的底层细节:stream 编号为 [1],以及 HTTP/2 特有的伪头部(pseudo-headers),它们以冒号 : 开头。以 > 开头的行是 curl 对请求的可读化展示。

HTTP/2 的四个伪头部各有其作用:

6.2 HTTP/2 vs HTTP/1.1

HTTP/2 相较于 HTTP/1.1 做了多项重大改进,这些改进直接影响页面加载性能:

特性 HTTP/1.1 HTTP/2
传输格式 纯文本协议,请求和响应都是可读文本 二进制分帧,将数据拆分为更小的帧进行传输
多路复用 每个请求需要独占一个 TCP 连接,或使用管线化(实际很少使用) 多个请求/响应可在同一个 TCP 连接上并行传输,互不阻塞
头部处理 每次请求都完整发送所有头部,存在大量重复 使用 HPACK 算法压缩头部,维护动态表记录已发送的头部,只传输变化的部分
服务器推送 不支持,客户端必须主动请求每个资源 服务器可以在客户端请求之前主动推送相关资源(如 CSS、JS)
请求头格式 请求行 + Host 头(GET /get HTTP/1.1 伪头部(:method:scheme:authority:path

为什么多路复用如此重要? 在 HTTP/1.1 时代,浏览器为了并行加载资源,需要对同一域名开启 6-8 个 TCP 连接。每个连接都需要独立的三次握手和 TLS 握手,开销很大。HTTP/2 在单个连接上使用 stream(流)来并行传输多个请求和响应,每个 stream 有独立的编号(如 [1]),大幅减少了连接建立的开销。

面试提示:面试中常问”HTTP/2 解决了 HTTP/1.1 的哪些问题?”重点回答:队头阻塞(通过多路复用解决连接级别的队头阻塞,但 TCP 层仍存在)、头部冗余(HPACK 压缩)、文本效率低(二进制分帧)。进一步提到 HTTP/3 使用 QUIC 协议彻底解决 TCP 层队头阻塞,是加分项。

6.3 响应分析

服务器处理请求后返回的响应如下:

< HTTP/2 200
< date: Sun, 01 Mar 2026 14:54:46 GMT
< content-type: application/json
< content-length: 256
< server: gunicorn/19.9.0
< access-control-allow-origin: *
< access-control-allow-credentials: true

< 开头的行表示从服务器接收到的响应,逐行分析各响应头:

响应体是一个 JSON 对象:

{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "curl/8.7.1",
    "X-Amzn-Trace-Id": "Root=1-69a45336-01c9a5360d78dba746e05f2d"
  },
  "origin": "140.235.140.187",
  "url": "https://httpbin.org/get"
}

httpbin.org 的 /get 端点会将收到的请求信息”回显”给客户端,方便调试。可以看到服务器收到了我们发送的 AcceptHostUser-Agent 头,origin 是客户端的公网 IP 地址,X-Amzn-Trace-Id 是 AWS 负载均衡器自动添加的请求追踪标识。

下面的时序图展示了 HTTP/2 请求与响应在 stream 中的完整交互:

sequenceDiagram
    participant C as 客户端
    participant S as 服务器

    Note over C,S: TLS 加密通道已建立(HTTP/2)

    C->>S: HEADERS 帧(stream 1)<br/>:method: GET<br/>:path: /get<br/>:authority: httpbin.org
    Note right of S: 服务器处理请求

    S->>C: HEADERS 帧(stream 1)<br/>:status: 200<br/>content-type: application/json
    S->>C: DATA 帧(stream 1)<br/>JSON 响应体
    Note left of C: 接收完成,解析响应

面试提示:面试官问到 HTTP 状态码时,不要只背 200、404、500。重点理解分类:2xx 成功3xx 重定向(301 永久 / 302 临时 / 304 缓存未变)、4xx 客户端错误(401 未认证 / 403 无权限 / 404 不存在)、5xx 服务器错误。能结合实际场景解释何时出现这些状态码,远比死记硬背更有说服力。

7. 服务器处理

7.1 curl 中的服务器信息

在响应头中,我们已经看到了服务器的身份标识:

< server: gunicorn/19.9.0

这告诉我们 httpbin.org 使用的是 Gunicorn——一个 Python WSGI HTTP 服务器,版本 19.9.0。但在生产环境中,一个请求从到达服务器到返回响应,通常不会只经过一个软件组件。

7.2 请求处理链路

一个典型的 Web 服务器架构采用多层处理模型:

客户端请求 → 反向代理(Nginx) → WSGI 服务器(Gunicorn) → Web 应用(Flask/Django)

每一层各司其职:

为什么要分层? 核心原因是关注点分离并发处理能力不同。Nginx 使用事件驱动模型,能高效处理数万并发连接和静态资源;Gunicorn 通过多进程模型隔离请求,保证一个请求崩溃不会影响其他请求;应用层只需专注于业务逻辑,无需关心连接管理和进程调度。这种分层架构让每个组件都发挥自己的优势,整体性能和稳定性远优于单一组件处理一切。

面试提示:面试中提到”从 URL 到页面渲染”时,服务器处理部分不需要深入展开,但能说出反向代理 → 应用服务器 → Web 框架的分层结构,说明你对后端架构有基本了解,这是全栈思维的体现。

8. 浏览器解析与渲染

从这一章开始,我们进入了 curl 无法观测的领域——浏览器接收到 HTTP 响应后,如何将 HTML、CSS、JavaScript 转化为用户看到的页面。这是前端面试的核心考点。

8.1 HTML 解析与 DOM 树

浏览器收到服务器返回的 HTML 字节流后,会经历以下解析过程将其转换为 DOM 树:

字节(Bytes) → 字符(Characters) → 词法单元(Tokens) → 节点(Nodes) → DOM 树

各阶段详解:

  1. 字节 → 字符:根据响应头中的 content-type: text/html; charset=UTF-8 或 HTML 中的 <meta charset="UTF-8">,将原始字节流按指定编码解码为字符串
  2. 字符 → Tokens:词法分析器(Tokenizer)逐字符扫描,将字符串拆解为一个个有意义的词法单元,如开始标签(<div>)、结束标签(</div>)、属性(class="container")、文本内容等
  3. Tokens → Nodes:根据词法单元创建对应的 DOM 节点对象,每个节点包含标签名、属性、子节点等信息
  4. Nodes → DOM 树:按照标签的嵌套关系,将节点组织为树形结构。<html> 是根节点,<head><body> 是其子节点,层层嵌套

面试提示:DOM(Document Object Model)不仅是 HTML 的内存表示,也是 JavaScript 操作页面的接口。document.getElementById() 等 API 本质上就是在这棵树上进行查找和操作。

8.2 CSS 解析与 CSSOM 树

当 HTML 解析器遇到 <link rel="stylesheet"><style> 标签时,浏览器会启动 CSS 解析,构建 CSSOM(CSS Object Model)树。解析过程与 DOM 类似:

字节(Bytes) → 字符(Characters) → 词法单元(Tokens) → 节点(Nodes) → CSSOM 树

CSSOM 树的每个节点包含样式规则,并且具有层叠特性——浏览器会按照优先级合并来自不同来源的样式(浏览器默认样式 → 外部样式表 → 内联样式 → !important)。

CSSOM 树的一个关键特性是层叠计算需要完整的 CSS 信息。浏览器必须等到所有 CSS 下载并解析完毕后,才能确定每个元素的最终样式。这就是为什么 CSS 被称为渲染阻塞资源——在 CSSOM 构建完成之前,浏览器不会渲染任何内容。

面试提示:这也是为什么推荐将 CSS 放在 <head> 中的原因——让浏览器尽早发现并下载 CSS,减少渲染阻塞时间。

8.3 渲染树构建

DOM 树和 CSSOM 树都构建完成后,浏览器将它们合并为渲染树(Render Tree)

DOM 树 + CSSOM 树 → 渲染树(Render Tree)

渲染树的构建规则:

注意 visibility: hiddendisplay: none 的区别——前者的元素仍在渲染树中(占据空间但不可见),后者的元素完全不在渲染树中(不占据空间)。

面试提示:这是面试高频考点。”display:none 和 visibility:hidden 有什么区别?”从渲染树的角度回答:display:none 的元素不在渲染树中,不参与布局;visibility:hidden 的元素在渲染树中,参与布局但不绘制像素。

8.4 布局与绘制

渲染树构建完成后,浏览器需要经历三个阶段才能将页面呈现到屏幕上:

Layout(布局 / 回流)

浏览器遍历渲染树,计算每个节点的几何信息——精确的位置(x, y 坐标)和大小(宽度、高度)。这个过程从根节点开始,递归计算每个元素的盒模型(content → padding → border → margin)。

触发回流的常见操作:

Paint(绘制 / 重绘)

根据布局计算的几何信息和渲染树中的样式信息,将每个节点绘制为实际的像素。绘制涉及填充颜色、绘制边框、渲染阴影、显示文本和图片等视觉效果。

触发重绘但不触发回流的操作:修改 colorbackground-colorvisibilitybox-shadow 等不影响几何信息的样式属性。

Composite(合成)

浏览器将页面分成多个图层(Layer),每个图层独立绘制后,由 GPU 按正确的顺序合成最终画面。某些 CSS 属性(如 transformopacitywill-change)会让元素提升为独立图层,修改这些属性时只需重新合成,无需回流和重绘,性能最优。

面试提示:性能优化的核心原则——回流的开销 > 重绘的开销 > 合成的开销。尽量使用 transform 代替 top/left 实现动画,使用 opacity 代替 visibility 实现淡入淡出,因为前者只触发合成,后者会触发重绘甚至回流。

8.5 JavaScript 对渲染的影响

JavaScript 是影响渲染性能最关键的因素之一。当 HTML 解析器遇到 <script> 标签时,默认行为是暂停 HTML 解析,等待脚本下载并执行完毕后才继续。

为什么要暂停? 因为 JavaScript 可以通过 document.write() 修改 DOM 结构,如果解析器不暂停,可能会导致解析结果与脚本执行后的 DOM 不一致。

为了减少脚本对解析的阻塞,HTML5 引入了 asyncdefer 属性:

属性 HTML 解析 下载时机 执行时机 执行顺序
阻塞 立即开始下载 下载完成后立即执行,阻塞解析 按文档顺序
async 不阻塞 并行下载 下载完成后立即执行,短暂阻塞解析 不保证顺序
defer 不阻塞 并行下载 HTML 解析完成后、DOMContentLoaded 事件前执行 按文档顺序

使用建议:

CSS 与渲染阻塞

CSS 不会阻塞 DOM 解析(HTML 解析器可以继续构建 DOM 树),但会阻塞渲染(浏览器不会在 CSSOM 构建完成前绘制页面)。此外,如果 <script> 标签在 <link> 标签之后,浏览器会等待 CSS 下载完成后才执行脚本,因为脚本可能需要查询元素的样式信息。

面试提示async vs defer 是前端面试必考题。记住核心区别:async 下载完就执行(不保证顺序),defer 等 DOM 解析完再按顺序执行。实际项目中 defer 用得更多,因为大多数脚本需要操作 DOM 且存在依赖关系。

8.6 渲染管线总览

将以上所有步骤串联起来,浏览器的完整渲染管线如下:

flowchart LR
    A[HTML] --> B[DOM 树]
    C[CSS] --> D[CSSOM 树]
    B --> E[渲染树]
    D --> E
    E --> F[Layout 布局]
    F --> G[Paint 绘制]
    G --> H[Composite 合成]
    H --> I[显示]

整个管线的关键要点:

面试提示:面试中被问到”从输入 URL 到页面渲染经历了什么”时,能从网络层讲到渲染层,最后以渲染管线收尾,展现出对浏览器工作原理的完整理解。重点是理解每个阶段的输入和输出,以及它们之间的依赖关系。

9. 总结

9.1 全流程速查表

阶段 核心任务 关键协议/技术
URL 解析 解析协议、域名、端口、路径 HSTS
DNS 解析 域名 → IP 地址 DNS, DoH
TCP 连接 建立可靠连接 TCP 三次握手
TLS 握手 建立加密通道 TLS 1.2, ECDHE
HTTP 请求 发送/接收数据 HTTP/2
服务器处理 生成响应 Nginx, gunicorn
浏览器渲染 解析并绘制页面 DOM, CSSOM, GPU 合成

9.2 30 秒面试回答模板

浏览器首先对 URL 进行解析,提取协议、域名和路径等信息,随后通过 DNS 将域名解析为 IP 地址。接着与服务器建立 TCP 连接(三次握手),如果是 HTTPS 还需要进行 TLS 握手以建立加密通道。连接建立后,浏览器发送 HTTP 请求,服务器处理请求并返回 HTML 响应。最后,浏览器解析 HTML 构建 DOM 树,解析 CSS 构建 CSSOM 树,两者合并为渲染树,经过布局计算和绘制,最终通过 GPU 合成将页面显示在屏幕上。

9.3 常见追问

Q1:为什么 HTTPS 比 HTTP 慢?如何优化?

HTTPS 多了 TLS 握手环节(1-2 个 RTT),增加了连接建立时间。优化手段包括:开启 TLS 1.3(1-RTT 甚至 0-RTT)、使用 Session Resumption 复用会话、部署 OCSP Stapling 减少证书验证延迟、启用 HTTP/2 多路复用减少连接数。

Q2:什么是回流和重绘?如何减少?

回流(Reflow)是指元素的几何属性(尺寸、位置)发生变化,浏览器需要重新计算布局;重绘(Repaint)是指元素外观改变但不影响布局。回流必然触发重绘,代价更高。减少方法:批量修改 DOM、使用 transform 代替 top/left 动画、避免频繁读取布局属性、使用 will-change 提升合成层。

Q3:HTTP/2 相比 HTTP/1.1 有哪些改进?

核心改进包括:多路复用(一个连接并行传输多个请求)、头部压缩(HPACK 算法)、服务器推送(Server Push)、二进制分帧(替代文本协议)、流优先级控制。这些特性解决了 HTTP/1.1 的队头阻塞问题,大幅提升了页面加载性能。

Q4:DNS 解析慢怎么办?

优化策略包括:使用 dns-prefetch 预解析关键域名、减少页面中的域名数量、选择快速的公共 DNS 服务器(如 1.1.1.1 或 8.8.8.8)、启用 DNS over HTTPS(DoH)提升安全性和稳定性、利用本地 DNS 缓存和 CDN 就近解析。

Q5:首屏优化有哪些手段?

首屏优化是一个综合工程:网络层面使用 CDN、开启 Gzip/Brotli 压缩、利用 HTTP 缓存;资源层面进行代码分割(Code Splitting)、懒加载非关键资源、内联关键 CSS;渲染层面采用 SSR/SSG 减少客户端渲染时间、使用骨架屏提升感知速度、预加载关键字体和图片。

10. 参考资源