"HTTP 是互联网的空气------你平时感觉不到它的存在,但没有它你什么都做不了。"
作为一个后端工程师,你可以不懂量子力学,但不能不懂 HTTP。面试里,从"GET 和 POST 的区别"到"HTTP/2 的多路复用原理",从"三次握手为什么是三次"到"HTTPS 的 TLS 握手过程"------这些问题你答不上来,基本就凉了。本文从最底层开始,把 HTTP 和相关网络知识给你掰开了揉碎了讲一遍。
📑 目录
- [HTTP 是什么?](#HTTP 是什么?)
- [HTTP 报文详解](#HTTP 报文详解)
- [HTTP 方法:不只是 GET 和 POST](#HTTP 方法:不只是 GET 和 POST)
- [HTTP 状态码:服务器的心情表达](#HTTP 状态码:服务器的心情表达)
- [HTTP Header:请求的「身份证」](#HTTP Header:请求的「身份证」)
- [HTTP 版本演进:从 0.9 到 3.0](#HTTP 版本演进:从 0.9 到 3.0)
- [TCP 基础:HTTP 的地基](#TCP 基础:HTTP 的地基)
- [HTTPS 与 TLS:加密通信](#HTTPS 与 TLS:加密通信)
- DNS:互联网的电话簿
- [Cookie、Session 与 Token](#Cookie、Session 与 Token)
- [HTTP 缓存机制](#HTTP 缓存机制)
- 跨域问题(CORS)
- [HTTP 连接管理](#HTTP 连接管理)
- [从输入 URL 到页面渲染的全过程](#从输入 URL 到页面渲染的全过程)
- 面试高频问题速查
- 参考文献
1. HTTP 是什么?

图1:HTTP 请求-响应模型 ------ 一问一答的通信艺术
1.1 一句话定义
HTTP(HyperText Transfer Protocol,超文本传输协议)是客户端和服务端之间通信的规则。它定义了:客户端怎么发请求、服务端怎么返响应、数据的格式是什么样的。
1.2 核心特点
无状态(Stateless)。 这是 HTTP 最重要的特点,也是面试最爱问的。每次请求都是独立的------服务器不会记住你上一次请求了什么。就像你每次都去同一家餐厅,但服务员每次都问你"请问您几位?"------他不记得你昨天来过。
为什么这么设计?因为无状态让服务器可以水平扩展------任何一台服务器都能处理任何请求,不需要共享状态。但在实际应用中,我们经常需要状态(比如登录状态),所以有了 Cookie、Session、Token 等机制来"模拟"有状态。
基于请求-响应模型。 客户端发请求,服务器返响应。在 HTTP/1.x 中,通信是半双工的------同一时刻只能有一方在说话。HTTP/2 引入了服务器推送,但本质上还是请求-响应模型。
可扩展。 HTTP 的 Header 是可扩展的------你可以添加自定义的 Header 字段,服务器和客户端都可以理解。这就是为什么 HTTP 能活 30 多年------它的设计足够灵活,可以通过扩展来适应新需求。
1.3 HTTP 在网络协议栈中的位置
| 层次 | 协议 | 作用 | HTTP 的位置 |
|---|---|---|---|
| 应用层 | HTTP, FTP, SMTP, DNS | 应用程序通信 | ← HTTP 在这里 |
| 传输层 | TCP, UDP | 端到端可靠传输 | HTTP 依赖 TCP |
| 网络层 | IP | 路由和寻址 | IP 地址 |
| 数据链路层 | Ethernet, Wi-Fi | 帧传输 | MAC 地址 |
| 物理层 | 光纤、电缆 | 比特流传输 | 电信号 |
表1:HTTP 在网络协议栈中的位置
HTTP 是应用层协议------它不关心数据怎么从A传到B(那是TCP和IP的事),它只关心数据的格式和语义。就像你写信------信的内容是应用层,邮局负责传输是传输层和网络层。
2. HTTP 报文详解
2.1 请求报文结构
GET /api/users?page=1 HTTP/1.1 ← 请求行(方法 + URL + 版本)
Host: example.com ← 请求头
Accept: application/json
Authorization: Bearer eyJhbG...
Content-Type: application/json
Cookie: session_id=abc123
← 空行(分隔Header和Body)
{"name": "张三"} ← 请求体(GET没有Body)
2.2 响应报文结构
HTTP/1.1 200 OK ← 状态行(版本 + 状态码 + 原因短语)
Content-Type: application/json ← 响应头
Content-Length: 42
Cache-Control: max-age=3600
Set-Cookie: session_id=xyz789; HttpOnly
← 空行
{"id": 1, "name": "张三"} ← 响应体
2.3 报文的组成部分
| 部分 | 请求报文 | 响应报文 | 说明 |
|---|---|---|---|
| 起始行 | 请求行 | 状态行 | 描述请求/响应的基本信息 |
| Header | 请求头 | 响应头 | 元数据(Content-Type等) |
| 空行 | CRLF | CRLF | 分隔Header和Body |
| Body | 请求体 | 响应体 | 实际传输的数据 |
表2:HTTP报文结构
一个关键细节:Header和Body之间必须有一个空行(CRLF)。如果没有空行,服务器不知道Header在哪里结束、Body在哪里开始。这是HTTP协议的基本语法。
3. HTTP 方法:不只是 GET 和 POST
3.1 九种 HTTP 方法
| 方法 | 语义 | 幂等 | 安全 | 有Body | 典型用途 |
|---|---|---|---|---|---|
| GET | 获取资源 | ✅ | ✅ | ❌ | 获取网页、API查询 |
| POST | 提交数据 | ❌ | ❌ | ✅ | 创建资源、表单提交 |
| PUT | 替换资源 | ✅ | ❌ | ✅ | 更新整个资源 |
| PATCH | 部分更新 | ❌ | ❌ | ✅ | 更新资源的部分字段 |
| DELETE | 删除资源 | ✅ | ❌ | 可选 | 删除资源 |
| HEAD | 获取Header | ✅ | ✅ | ❌ | 检查资源是否存在 |
| OPTIONS | 查询支持的方法 | ✅ | ✅ | ❌ | CORS预检请求 |
| TRACE | 回显请求 | ✅ | ✅ | ❌ | 调试用(生产禁用) |
| CONNECT | 建立隧道 | ❌ | ❌ | ❌ | HTTPS代理 |
表3:HTTP九种方法
幂等:同一个请求执行多次,效果和执行一次相同。GET是幂等的------你刷新页面10次,页面内容一样(假设没有副作用)。POST不是幂等的------你提交订单10次,就创建了10个订单。
3.2 GET vs POST:面试必问
这是面试中最经典的HTTP问题。很多人只知道"GET参数在URL里,POST参数在Body里",但这只是表面区别。
| 维度 | GET | POST |
|---|---|---|
| 语义 | 获取资源 | 提交数据 |
| 幂等 | 是 | 否 |
| 安全 | 是(只读) | 否(可能有副作用) |
| 参数位置 | URL查询字符串 | 请求体 |
| 参数长度 | 浏览器限制约2048字符 | 理论上无限制 |
| 缓存 | 可以被缓存 | 默认不缓存 |
| 书签 | 可以收藏为书签 | 不行 |
| 编码 | URL编码 | 支持多种编码(JSON、FormData等) |
| TCP数据包 | 一个(Header+URL一起发) | 可能两个(先发Header,再发Body) |
表4:GET vs POST 详细对比
一个容易被忽略的区别:语义。GET的语义是"获取资源"------它不应该有副作用(不改变服务器状态)。POST的语义是"提交数据"------它可能创建新资源、触发处理流程等。RESTful API就是基于这个语义来设计的。
3.3 RESTful API 设计
| 操作 | HTTP方法 | URL | 示例 |
|---|---|---|---|
| 获取所有用户 | GET | /users | GET /users |
| 获取单个用户 | GET | /users/{id} | GET /users/42 |
| 创建用户 | POST | /users | POST /users |
| 更新用户 | PUT | /users/{id} | PUT /users/42 |
| 部分更新 | PATCH | /users/{id} | PATCH /users/42 |
| 删除用户 | DELETE | /users/{id} | DELETE /users/42 |
表5:RESTful API设计规范
4. HTTP 状态码:服务器的心情表达

图2:HTTP状态码五大类 ------ 1xx到5xx,服务器的五种心情
4.1 五大类状态码
状态码是三位数字,第一位表示类别:
| 类别 | 含义 | 常见例子 | 类比 |
|---|---|---|---|
| 1xx | 信息性 | 100 Continue, 101 Switching | "收到了,继续" |
| 2xx | 成功 | 200 OK, 201 Created | "搞定了!" |
| 3xx | 重定向 | 301, 302, 304 | "去找别人吧" |
| 4xx | 客户端错误 | 400, 401, 403, 404 | "你的问题" |
| 5xx | 服务端错误 | 500, 502, 503 | "我的问题" |
表6:HTTP状态码五大类
4.2 面试必背状态码详解
200 OK。 最常见的状态码。请求成功,返回了请求的数据。
201 Created。 资源创建成功。POST请求创建新资源后应该返回201,同时在Location头中返回新资源的URL。
204 No Content。 请求成功,但没有返回内容。DELETE请求成功后通常返回204------你删了东西,没什么好返回的。
301 Moved Permanently。 资源永久搬家了。浏览器会自动跳转到新地址,搜索引擎会更新链接。下次请求直接去新地址。
302 Found。 资源临时搬家。浏览器会自动跳转,但下次请求还是来老地方找。302是临时的,301是永久的。
304 Not Modified。 资源没变过,用缓存吧。这是缓存机制的核心------服务器告诉客户端"你手里的版本还是最新的,不用重新下载"。省带宽神器。
400 Bad Request。 请求写错了。比如JSON格式错误、缺少必填参数。服务器看不懂你的请求。
401 Unauthorized。 你没有认证(没有登录)。需要提供有效的身份凭证(如Token)。
403 Forbidden。 你认证了但没有权限。你登录了,但这个资源你不能访问。
404 Not Found。 资源不存在。最熟悉的陌生人------每个互联网用户都见过它。
405 Method Not Allowed。 方法不允许。比如你用DELETE去访问一个只支持GET的URL。
500 Internal Server Error。 服务器内部出bug了。最通用的服务端错误。
502 Bad Gateway。 网关(如Nginx)收到了后端服务器的无效响应。通常意味着后端服务挂了。
503 Service Unavailable。 服务器暂时不可用(过载或维护)。通常可以稍后重试。
504 Gateway Timeout。 网关等待后端服务器响应超时。
5. HTTP Header:请求的「身份证」
Header 是 HTTP 报文的元数据------描述了请求/响应的各种属性。
5.1 通用 Header
| Header | 说明 | 示例 |
|---|---|---|
| Content-Type | Body的数据类型 | application/json, text/html |
| Content-Length | Body的字节数 | 42 |
| Date | 报文的日期时间 | Thu, 01 Jan 2026 00:00:00 GMT |
| Connection | 连接管理 | keep-alive, close |
| Cache-Control | 缓存策略 | max-age=3600, no-cache |
表7:通用Header
5.2 请求 Header
| Header | 说明 | 示例 |
|---|---|---|
| Host | 目标主机名(HTTP/1.1必须) | example.com |
| Accept | 客户端能接受的响应类型 | text/html, application/json |
| Accept-Encoding | 能接受的压缩方式 | gzip, deflate, br |
| Authorization | 身份凭证 | Bearer eyJhbG... |
| Cookie | 客户端存储的Cookie | session_id=abc123 |
| User-Agent | 客户端信息 | Mozilla/5.0 ... |
| Referer | 来源页面URL | https://example.com/prev |
| If-None-Match | 协商缓存(ETag) | "abc123" |
| If-Modified-Since | 协商缓存(时间) | Thu, 01 Jan 2026 00:00:00 GMT |
表8:请求Header
5.3 响应 Header
| Header | 说明 | 示例 |
|---|---|---|
| Set-Cookie | 设置Cookie | session_id=xyz; HttpOnly |
| ETag | 资源的"指纹" | "abc123" |
| Last-Modified | 资源最后修改时间 | Thu, 01 Jan 2026 00:00:00 GMT |
| Location | 重定向目标URL | https://example.com/new |
| Access-Control-Allow-Origin | CORS允许的源 | * 或 https://trusted.com |
| Strict-Transport-Security | 强制HTTPS | max-age=31536000 |
表9:响应Header
5.4 Content-Type 常见值
| Content-Type | 说明 | 使用场景 |
|---|---|---|
| text/html | HTML文档 | 网页 |
| text/plain | 纯文本 | 文本文件 |
| application/json | JSON数据 | API |
| application/xml | XML数据 | 旧式API |
| application/x-www-form-urlencoded | 表单数据 | HTML表单提交 |
| multipart/form-data | 文件上传 | 上传文件 |
| application/octet-stream | 二进制流 | 下载文件 |
表10:Content-Type常见值
6. HTTP 版本演进:从 0.9 到 3.0

图3:HTTP版本演进 ------ 30年五代进化
6.1 HTTP/0.9(1991)
HTTP 的"太爷爷"。只有 GET 方法,没有 Header,只能传输 HTML。一行命令就搞定了:
GET /index.html
服务器返回:
<html>...</html>
就这么简单。没有状态码,没有Header,没有图片,没有CSS,没有JavaScript。
6.2 HTTP/1.0(1996)
引入了 Header、状态码、多种方法(GET/POST/HEAD)。但有一个致命问题:非持久连接------每个请求都要重新建立 TCP 连接。请求一个包含10张图片的网页,需要建立11个 TCP 连接(1个HTML + 10张图片)。三次握手的开销是巨大的。
6.3 HTTP/1.1(1997)
HTTP/1.1 是目前使用最广泛的版本(虽然已经 27 岁了)。关键改进:
持久连接(Keep-Alive)。 默认开启,一个 TCP 连接可以发送多个请求。不用每次都重新握手。
管线化(Pipelining)。 客户端可以连续发送多个请求,不用等前一个响应返回。但实际中很少使用------因为响应必须按请求顺序返回(队头阻塞),如果第一个响应很慢,后面的都要等。
分块传输(Chunked Transfer)。 服务器可以分块发送数据,不用等全部准备好。这对大文件和动态生成的内容很有用。
Host 头。 一个IP地址可以托管多个域名(虚拟主机)。Host头告诉服务器你要访问哪个域名。
6.4 HTTP/2(2015)
HTTP/2 是一次重大升级,核心创新是二进制分帧 和多路复用。
二进制分帧。 HTTP/1.x 是纯文本协议(人可读),HTTP/2 改为二进制格式。数据被拆分成更小的"帧"(Frame),每个帧有类型、长度、标志位等元数据。
多路复用(Multiplexing)。 这是 HTTP/2 最重要的特性。在同一个 TCP 连接上,多个请求/响应可以同时传输,互不干扰。彻底解决了 HTTP/1.1 的应用层队头阻塞。
头部压缩(HPACK)。 HTTP 头部有很多重复的字段(如 Cookie、User-Agent),HPACK 算法用索引表来压缩这些重复信息。
服务器推送(Server Push)。 服务器可以主动向客户端推送资源,不用等客户端请求。比如客户端请求 HTML,服务器可以主动推送 CSS 和 JS。
但 HTTP/2 有一个新问题:TCP 层的队头阻塞。 虽然应用层可以多路复用,但底层的 TCP 是字节流协议------如果一个 TCP 包丢了,所有流都要等它重传。这就是 HTTP/3 要解决的问题。
6.5 HTTP/3(2022)
HTTP/3 用 QUIC 协议替代了 TCP。QUIC 基于 UDP,但加入了可靠性、加密和多路复用。
0-RTT 建连。 首次连接只需要 1-RTT(比 TCP+TLS 的 3-RTT 快),后续连接可以 0-RTT------数据在握手的同时就开始传输。
真正的多路复用。 QUIC 在传输层就支持多路复用------一个连接中的多个流是独立的,一个流的丢包不会影响其他流。彻底解决了队头阻塞。
连接迁移。 TCP 连接由四元组(源IP、源端口、目的IP、目的端口)标识。当你从 Wi-Fi 切换到 4G 时,IP 地址变了,TCP 连接就断了。QUIC 用连接 ID 来标识连接,IP 变了连接还在。
6.6 版本对比总结
| 版本 | 年份 | 传输层 | 队头阻塞 | 建连延迟 | 多路复用 | 头部压缩 |
|---|---|---|---|---|---|---|
| 1.0 | 1996 | TCP | 有 | 1.5RTT+TLS | ❌ | ❌ |
| 1.1 | 1997 | TCP | 有(管线化) | 1.5RTT+TLS | ❌ | ❌ |
| 2 | 2015 | TCP | TCP层有 | 1.5RTT+TLS | ✅ | HPACK |
| 3 | 2022 | QUIC/UDP | 彻底解决 | 0-1RTT | ✅ | QPACK |
表11:HTTP版本对比
7. TCP 基础:HTTP 的地基

图4:TCP三次握手与四次挥手 ------ 网络连接的生与死
7.1 为什么 HTTP 需要 TCP?
HTTP 自己不管传输------它只管数据格式。数据的可靠传输由 TCP 来保证。TCP 提供:
- 可靠性:数据不丢失、不重复、按顺序到达
- 面向连接:通信前需要建立连接(三次握手)
- 流量控制:防止发送方发太快淹没接收方
- 拥塞控制:防止网络拥塞
7.2 三次握手(Three-way Handshake)
建立 TCP 连接需要三次握手:
第一次握手(SYN)。 客户端发一个 SYN 包(seq=x),表示"我要连接你"。此时客户端进入 SYN_SENT 状态。
第二次握手(SYN+ACK)。 服务器收到后,返回一个 SYN+ACK 包(seq=y, ack=x+1),表示"好的,我同意连接,你还在吗?"。此时服务器进入 SYN_RCVD 状态。
第三次握手(ACK)。 客户端收到后,返回一个 ACK 包(ack=y+1),表示"我在,连接建立!"。双方进入 ESTABLISHED 状态。
为什么是三次而不是两次? 面试必问。核心原因是:防止已失效的连接请求到达服务器。
想象一下:客户端发了一个 SYN,但网络延迟导致它很久才到达服务器。客户端以为丢了,又发了一个新的 SYN,这次正常建立了连接并完成了通信。然后那个迟到的旧 SYN 终于到了服务器------如果是两次握手,服务器会以为这是一个新连接并分配资源,但客户端根本不知道,资源就浪费了。三次握手让客户端在第三步确认"我确实要连接",避免了这种情况。
7.3 四次挥手(Four-way Handshake)
断开 TCP 连接需要四次挥手:
第一次挥手(FIN)。 客户端发 FIN,表示"我不发数据了"。
第二次挥手(ACK)。 服务器确认收到,但可能还有数据没发完。
第三次挥手(FIN)。 服务器也发完数据了,发 FIN 表示"我也不发了"。
第四次挥手(ACK)。 客户端确认,然后等待 2MSL(Maximum Segment Lifetime)后关闭。
为什么是四次而不是三次? 因为 TCP 是全双工的------两个方向的数据流是独立的。关闭连接时,每个方向都要单独关闭。客户端说"我不发了",但服务器可能还有数据没发完,所以要先确认,等发完了再发自己的 FIN。
7.4 TCP 与 UDP
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠(确认+重传) | 不可靠 |
| 顺序 | 保证顺序 | 不保证 |
| 速度 | 较慢 | 快 |
| 头部开销 | 20字节 | 8字节 |
| 用途 | HTTP、文件传输 | DNS、视频、游戏、QUIC |
表12:TCP vs UDP
HTTP/3 的 QUIC 协议就是在 UDP 之上实现可靠性------它用 UDP 的速度,加上自己的可靠传输和加密机制。这就像"在高速公路上建铁路"------利用UDP的高效传输,加上自己的可靠保障。
8. HTTPS 与 TLS:加密通信

图5:HTTPS = HTTP + TLS ------ 加密通信的三重保护
8.1 为什么需要 HTTPS?
HTTP 是明文传输的------你发的每一个字节,中间人(如路由器、ISP、公共Wi-Fi的管理员)都能看到。这意味着:
- 密码、Token、Cookie 全部裸奔
- 中间人可以篡改你收到的内容(注入广告、修改页面)
- 你不知道对面的服务器是不是真的(可能是钓鱼网站)
HTTPS 通过 TLS(Transport Layer Security)协议解决了这三个问题。
8.2 TLS 握手过程(TLS 1.3)
TLS 1.3 将握手从 2-RTT 优化到 1-RTT:
- ClientHello:客户端发送支持的加密套件列表 + 客户端随机数 + 支持的TLS版本
- ServerHello:服务器选定加密套件 + 服务器随机数 + 服务器证书 + 服务器公钥
- 客户端验证证书:用CA的公钥验证服务器证书是否合法
- 密钥交换:客户端生成预主密钥,用服务器公钥加密后发送
- 生成会话密钥:双方用三个随机数(客户端随机数+服务器随机数+预主密钥)生成对称加密的会话密钥
- 加密通信开始:后续所有数据都用对称加密传输
为什么要用三个随机数? 因为任何一个随机数被猜到都不会影响安全性。这叫做"前向安全性"(Forward Secrecy)。
8.3 对称加密 vs 非对称加密
| 类型 | 原理 | 速度 | 用途 |
|---|---|---|---|
| 对称加密 | 加密解密用同一个密钥 | 快 | 数据传输(AES) |
| 非对称加密 | 公钥加密,私钥解密 | 慢 | 密钥交换(RSA, ECDHE) |
表13:对称加密 vs 非对称加密
TLS 握手用非对称加密来安全地交换密钥,然后用对称加密来传输数据。就像你用保险箱(非对称加密)把钥匙寄给对方,然后双方用这把钥匙(对称加密)来通信。
8.4 数字证书与 CA
数字证书是服务器的"身份证"------它证明"这个公钥确实属于 example.com"。
证书的信任链:
- 根证书:内置在操作系统/浏览器中(如 DigiCert, Let's Encrypt)
- 中间证书:由根证书签发
- 服务器证书:由中间证书签发
浏览器验证证书时,会沿着信任链一直验证到根证书。如果任何一个环节有问题(过期、域名不匹配、未被信任的CA),浏览器会显示"不安全"警告。
9. DNS:互联网的电话簿
9.1 DNS 是什么?
DNS(Domain Name System)把域名(如 example.com)翻译成 IP 地址(如 93.184.216.34)。没有 DNS,你就得记住每个网站的 IP 地址------就像没有电话簿,你就得记住每个人的电话号码。
9.2 DNS 解析过程
你在浏览器输入 www.example.com,DNS 解析过程如下:
- 浏览器缓存:浏览器先查自己的 DNS 缓存
- 操作系统缓存:浏览器没有,查操作系统的 DNS 缓存(hosts文件)
- 本地 DNS 服务器:操作系统没有,问本地 DNS 服务器(通常是路由器或ISP提供的)
- 递归查询 :本地 DNS 服务器也没有,开始递归查询:
- 问根域名服务器("."): "com 在哪?"
- 问顶级域名服务器(".com"): "example.com 在哪?"
- 问权威域名服务器("example.com"): "www.example.com 的IP是?"
- 返回结果:权威服务器返回 IP 地址
- 缓存:各级缓存结果(根据 TTL 过期时间)
9.3 DNS 记录类型
| 记录类型 | 说明 | 示例 |
|---|---|---|
| A | 域名→IPv4地址 | example.com → 93.184.216.34 |
| AAAA | 域名→IPv6地址 | example.com → 2606:2800:... |
| CNAME | 域名别名 | www.example.com → example.com |
| MX | 邮件服务器 | example.com → mail.example.com |
| NS | 域名服务器 | example.com → ns1.example.com |
| TXT | 文本记录 | SPF、DKIM等 |
表14:DNS记录类型
10. Cookie、Session 与 Token

图6:身份验证三兄弟 ------ 解决HTTP无状态问题的三种方案
10.1 为什么需要它们?
HTTP 是无状态的------服务器不记得你是谁。但很多场景需要状态:登录、购物车、个性化设置。Cookie、Session、Token 就是为了在无状态的 HTTP 上"模拟"有状态。
10.2 Cookie
Cookie 是服务器通过 Set-Cookie 响应头设置在浏览器里的小数据。浏览器每次请求都会自动带上 Cookie。
# 服务器设置Cookie
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
# 浏览器每次请求自动带上
Cookie: session_id=abc123
关键属性:
HttpOnly:JavaScript 不能读取(防 XSS)Secure:只能通过 HTTPS 发送SameSite:防止 CSRF 攻击(Lax/Strict/None)Path:Cookie 的作用路径Domain:Cookie 的作用域名Expires/Max-Age:过期时间
10.3 Session
Session 是服务端存储的用户状态。浏览器只存一个 Session ID(通常放在 Cookie 里),用户数据存在服务器的内存或数据库中。
优点: 敏感数据不出服务器,更安全。
缺点: 分布式环境下需要共享Session(用Redis等共享存储),服务器重启可能丢失。
10.4 Token(JWT)
JWT(JSON Web Token)是一种无状态的身份凭证。用户信息编码在 Token 本身中,服务端不需要存储。
# JWT的结构(三部分用.分隔)
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0Mn0.signature
↑ Header ↑ Payload ↑ Signature
优点: 天然支持分布式、跨域、无状态。
缺点: Token 一旦签发无法撤销(除非用黑名单),Token 体积比 Session ID 大。
10.5 三者对比
| 维度 | Cookie | Session | Token (JWT) |
|---|---|---|---|
| 存储位置 | 浏览器 | 服务器 | 浏览器/客户端 |
| 状态 | 有状态 | 有状态 | 无状态 |
| 安全性 | 中(可被XSS/CSRF) | 高 | 高(但无法撤销) |
| 分布式 | 不适用 | 需要共享存储 | 天然支持 |
| 跨域 | 受限 | 受限 | 支持 |
| 适用场景 | 简单状态 | 传统Web | API、微服务、移动端 |
表15:Cookie vs Session vs Token
11. HTTP 缓存机制

图7:HTTP缓存决策流程 ------ 强缓存 + 协商缓存
11.1 为什么需要缓存?
- 减少延迟:直接用本地缓存,不用等网络传输
- 减少带宽:不需要重复下载相同的资源
- 减轻服务器压力:减少服务器的请求量
11.2 强缓存(Strong Caching)
强缓存是最"霸道"的缓存------在有效期内,浏览器直接用缓存,完全不问服务器。
Cache-Control: max-age=3600。 资源在3600秒内有效。浏览器会直接从磁盘/内存缓存中读取,连网络请求都不发。状态码是 200 (from cache)。
Expires: Thu, 01 Jan 2027 00:00:00 GMT。 资源在这个时间之前有效。用的是绝对时间,有 2038 问题(类似 Y2K)。现在推荐用 Cache-Control 的 max-age。
Cache-Control 的其他指令:
no-cache:不用直接缓存,但可以存(需要协商缓存验证)no-store:完全不缓存public:任何中间节点都可以缓存private:只有浏览器可以缓存(中间代理不行)must-revalidate:缓存过期后必须验证
11.3 协商缓存(Conditional Caching)
当强缓存过期后,浏览器会向服务器发请求验证------"我手里的版本还是最新的吗?"
ETag + If-None-Match。 服务器第一次返回资源时,会生成一个 ETag(资源的"指纹",通常是内容的哈希值)。浏览器下次请求时,在 If-None-Match 头中带上这个 ETag。服务器比对------如果没变,返回 304(不返回Body);如果变了,返回 200 和新资源。
Last-Modified + If-Modified-Since。 服务器返回资源的最后修改时间。浏览器下次请求时,在 If-Modified-Since 头中带上这个时间。服务器比对------如果没改过,返回 304。
ETag vs Last-Modified:
- ETag 更精确(内容级),Last-Modified 是秒级
- ETag 优先级更高(两者同时存在时用 ETag)
- ETag 计算有开销(哈希),Last-Modified 很便宜
11.4 缓存策略最佳实践
| 资源类型 | 缓存策略 | 原因 |
|---|---|---|
| HTML | no-cache(协商缓存) | HTML是入口,需要最新版本 |
| CSS/JS(带hash) | max-age=31536000(一年) | 文件名变了就是新文件 |
| 图片 | max-age=86400(一天) | 图片更新不频繁 |
| API数据 | no-store | 数据需要实时 |
表16:缓存策略最佳实践
面试经典问题:"如何更新用户的缓存?" 答案:给静态资源的文件名加 hash(如
app.a1b2c3.js)。文件内容变了,hash 就变了,URL 就变了,浏览器就会下载新版本。旧版本的 URL 还可以继续被缓存,不影响其他用户。
12. 跨域问题(CORS)
12.1 什么是跨域?
浏览器的同源策略(Same-Origin Policy)限制了不同源之间的资源访问。"同源"指的是协议+域名+端口完全相同。
| URL | 是否同源(相对 http://example.com:80) | 原因 |
|---|---|---|
| http://example.com:80/api | ✅ 同源 | 完全相同 |
| https://example.com:80/api | ❌ 跨域 | 协议不同 |
| http://example.com:8080/api | ❌ 跨域 | 端口不同 |
| http://api.example.com/api | ❌ 跨域 | 域名不同 |
表17:同源策略判断
12.2 CORS 的工作原理
CORS(Cross-Origin Resource Sharing)是解决跨域的标准方案。它通过 HTTP 头来告诉浏览器"这个跨域请求是允许的"。
简单请求 (GET/POST/HEAD + 简单Header):直接发,服务器在响应头中设置 Access-Control-Allow-Origin。
复杂请求 (PUT/DELETE/自定义Header等):浏览器先发一个 OPTIONS 预检请求(Preflight),问服务器"我能不能发这个跨域请求?"。服务器返回允许的方法和 Header,然后浏览器才发真正的请求。
# 预检请求
OPTIONS /api/users HTTP/1.1
Origin: http://frontend.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
# 预检响应
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://frontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
13. HTTP 连接管理
13.1 短连接 vs 长连接
| 类型 | 说明 | HTTP版本 | 效率 |
|---|---|---|---|
| 短连接 | 每个请求新建TCP连接,用完就关 | HTTP/1.0默认 | 低 |
| 长连接 | 一个TCP连接复用多个请求 | HTTP/1.1+默认 | 高 |
表18:短连接 vs 长连接
13.2 队头阻塞(Head-of-Line Blocking)
HTTP/1.1 的管线化问题: 虽然可以连续发多个请求,但响应必须按顺序返回。如果第一个请求的响应很慢,后面的都要排队等。
HTTP/2 的TCP层问题: 虽然应用层多路复用了,但TCP是字节流------一个TCP包丢了,所有流都要等重传。
HTTP/3 的解决方案: QUIC 在传输层支持真正的多路复用------每个流独立,一个流的丢包不影响其他流。
13.3 连接数限制
浏览器对同一域名的并发TCP连接数有限制(通常是6个)。这意味着如果你的页面需要请求100个资源,它们会分批进行------每批6个,等前6个完成后再发下一批。
优化手段:
- 域名分片(Domain Sharding):把资源分散到多个子域名
- HTTP/2 多路复用:一个连接搞定所有请求
- 资源合并:把多个小文件合并成一个大文件(雪碧图、打包)
14. 从输入 URL 到页面渲染的全过程
这是面试中最经典的综合性问题。以下是完整流程:
第一步:URL 解析。 浏览器解析 URL,提取协议、域名、端口、路径、查询参数。
第二步:DNS 解析。 将域名解析为 IP 地址(浏览器缓存→系统缓存→本地DNS→递归查询)。
第三步:建立 TCP 连接。 三次握手建立 TCP 连接。如果是 HTTPS,还需要 TLS 握手。
第四步:发送 HTTP 请求。 浏览器构造 HTTP 请求报文(请求行+请求头+请求体),发送给服务器。
第五步:服务器处理请求。 服务器接收请求,经过路由、中间件、业务逻辑处理,生成响应。
第六步:返回 HTTP 响应。 服务器返回响应报文(状态行+响应头+响应体)。
第七步:浏览器解析 HTML。 构建 DOM 树。
第八步:浏览器解析 CSS。 构建 CSSOM 树。
第九步:构建渲染树。 DOM + CSSOM = Render Tree。
第十步:布局(Layout/Reflow)。 计算每个节点的位置和大小。
第十一步:绘制(Paint)。 将节点绘制到屏幕上。
第十二步:合成(Composite)。 将多个图层合成最终画面。
在这个过程中,还会遇到:
- 遇到
<script>标签:阻塞 HTML 解析(除非 async/defer) - 遇到
<link>标签:不阻塞 HTML 解析,但阻塞渲染 - 遇到图片:异步加载,不阻塞
15. 面试高频问题速查
Q1: GET 和 POST 的区别?
答: 语义不同(GET获取资源,POST提交数据)、幂等性不同(GET幂等,POST不幂等)、参数位置不同(URL vs Body)、缓存不同(GET可缓存,POST默认不缓存)、安全性不同(GET参数在URL中可见)。
Q2: HTTP/1.1 和 HTTP/2 的区别?
答: HTTP/2 引入了二进制分帧(替代文本格式)、多路复用(一个连接并行多个请求)、头部压缩(HPACK)、服务器推送。核心解决了HTTP/1.1的队头阻塞问题。
Q3: HTTPS 的握手过程?
答: TLS 1.3:ClientHello→ServerHello+证书+公钥→客户端验证证书+密钥交换→生成会话密钥→加密通信。1-RTT完成。
Q4: 三次握手为什么是三次?
答: 防止已失效的连接请求到达服务器导致资源浪费。两次握手无法确认客户端的连接意愿。
Q5: Cookie、Session、Token 的区别?
答: Cookie是客户端存储机制,Session是服务端存储机制,Token是无状态的身份凭证。Cookie有大小限制和安全风险,Session需要服务端存储,Token天然支持分布式。
Q6: HTTP 缓存机制?
答: 强缓存(Cache-Control/Expires,不问服务器直接用)+ 协商缓存(ETag/Last-Modified,问服务器变没变)。强缓存优先。
Q7: 什么是跨域?怎么解决?
答: 浏览器同源策略限制不同源的资源访问。解决方案:CORS(服务端设置Access-Control-Allow-Origin)、代理服务器、JSONP(仅GET)。
Q8: HTTP/3 为什么用 UDP?
答: TCP的队头阻塞在传输层无法解决。QUIC基于UDP,在传输层实现多路复用,一个流的丢包不影响其他流。同时QUIC内置加密和0-RTT建连。
16. 参考文献
1 Fielding, R., et al. (1999). "Hypertext Transfer Protocol -- HTTP/1.1". RFC 2616. IETF.
2 Belshe, M., Peon, R., & Thomson, M. (2015). "Hypertext Transfer Protocol Version 2 (HTTP/2)". RFC 7540. IETF.
3 Bishop, M. (2022). "HTTP/3". RFC 9114. IETF.
4 Rescorla, E. (2018). "The Transport Layer Security (TLS) Protocol Version 1.3". RFC 8446. IETF.
5 Fielding, R. T. (2000). "Architectural Styles and the Design of Network-based Software Architectures". Doctoral dissertation, University of California, Irvine.
6 Berners-Lee, T., Fielding, R., & Frystyk, H. (1996). "Hypertext Transfer Protocol -- HTTP/1.0". RFC 1945. IETF.
7 Mozilla. "HTTP | MDN Web Docs". https://developer.mozilla.org/en-US/docs/Web/HTTP.
8 Google. "HTTP/2 FAQ". https://developers.google.com/web/fundamentals/performance/http2.
9 Cloudflare. "What is HTTP/3?". https://www.cloudflare.com/learning/performance/what-is-http3/.
10 OWASP. "Cross-Site Scripting (XSS)". https://owasp.org/www-community/attacks/xss/.