深色模式
04 · HTTP / HTTPS 与缓存
传输层保证"数据能到",应用层决定"数据长什么样、能不能被缓存、安不安全"。
一句话本质
HTTP 是一问一答的文本协议:客户端说"我要什么",服务器说"给你 / 没有 / 你去别处拿"。HTTPS 就是在它和 TCP 之间插了一层加密。
生活类比:点餐与取餐
| HTTP 概念 | 餐厅 |
|---|---|
| 请求方法 | GET = "给我看看菜单";POST = "我下单";PUT = "把这道菜换成新的";DELETE = "退掉" |
| 状态码 | 200 上菜了;301 换到隔壁店了;404 没这道菜;500 后厨炸了 |
| 请求头 | 忌口说明(我要微辣、不要香菜) |
| 响应头 | 出餐说明(这道菜 10 分钟内吃完、保质期到明天) |
| 强缓存 | "这道菜你自己留着,2 小时内别再点" |
| 协商缓存 | "你上次点的那份还在,还是老样子吗?" → "没变,用你那份" |
| HTTPS | 菜单和订单都装进密封袋,中间人看不到 |
核心机制
一、报文结构
请求:
GET /index.html?page=2 HTTP/1.1 < 请求行:方法 + 路径 + 版本
Host: example.com < 请求头
User-Agent: Chrome/120
Accept: text/html
Cookie: sid=abc123
< 空行(必须有)
(GET 通常没有 body;POST 有)
响应:
HTTP/1.1 200 OK < 状态行:版本 + 状态码 + 短语
Content-Type: text/html; charset=utf-8 < 响应头
Content-Length: 1024
Cache-Control: max-age=3600
< 空行
<html>...</html> < 响应体二、方法:幂等与安全(高频)
| 方法 | 语义 | 安全 | 幂等 | 有无 body |
|---|---|---|---|---|
| GET | 获取资源 | ✅ | ✅ | 无 |
| HEAD | 只要响应头 | ✅ | ✅ | 无 |
| POST | 提交数据 / 创建 | ❌ | ❌ | 有 |
| PUT | 全量替换 | ❌ | ✅ | 有 |
| PATCH | 局部更新 | ❌ | ❌ | 有 |
| DELETE | 删除 | ❌ | ✅ | 无 |
安全 = 不改变服务器状态(只读)。 幂等 = 执行 1 次和执行 N 次,服务器状态相同。
为什么重要:网络会重试。GET/PUT/DELETE 重试没问题,POST 重试会重复下单——这就是"支付接口必须用幂等键"的原因。
三、状态码:记住"段位"
1xx 信息 100 Continue(我先发个头,你确认再发 body)
2xx 成功 200 OK / 201 Created / 204 No Content
3xx 重定向 301 永久(浏览器会缓存,下次直接跳)
302 临时(每次都要问)
304 未修改(协商缓存命中,用本地副本)
4xx 客户端错 400 参数错 / 401 没认证 / 403 没权限 / 404 不存在 / 429 太频繁
5xx 服务端错 500 内部错误 / 502 网关收到上游的坏响应 / 503 服务不可用 / 504 网关超时两个易混点:
- 401 vs 403:401 是"你没登录"(不知道你是谁),403 是"知道你是谁,但你没权限"。
- 502 vs 504:502 是上游返回了非法响应(进程崩了、协议错),504 是上游没响应(超时)。502 常见于上游进程挂了,504 常见于上游处理太慢。
四、HTTP 版本演进(一条主线:不断减少往返)
HTTP/1.0 每个请求一个 TCP 连接,用完就关
+- 问题:每次都要三次握手,太慢
v
HTTP/1.1 ① 持久连接(keep-alive),复用 TCP 连接
② 管道化(pipelining):可以连着发多个请求,但**响应必须按序返回**
+- 问题:队头阻塞,一个慢请求卡住后面全部
③ Host 头(支持虚拟主机,一个 IP 多个域名)
④ 分块传输(chunked)、断点续传(Range)
v
HTTP/2 ① 二进制分帧(不再是文本)
② 多路复用:一个 TCP 连接上跑多个流,每个流有自己的 ID
+- 解决了 HTTP 层的队头阻塞
③ 头部压缩(HPACK,用静态表 + 动态表,避免重复传相同的头)
④ 服务器推送(Server Push,实际用得少,已被废弃)
+- 问题:底层还是 TCP,TCP 丢包会阻塞所有流
v
HTTP/3 基于 QUIC(跑在 UDP 上)
① 每个流独立排序,彻底解决队头阻塞
② 0-RTT / 1-RTT 建连(TLS 1.3 集成在 QUIC 里)
③ 连接迁移(换网络不用重连,靠 Connection ID 而不是四元组)面试必答的一句话:HTTP/2 解决的是应用层队头阻塞,没解决 TCP 层队头阻塞;HTTP/3 用 QUIC 才彻底解决。
五、缓存:两道关卡
浏览器要请求一个资源
v
① 强缓存:本地有,且没过期?
Cache-Control: max-age=3600 (相对时间,优先)
Expires: Wed, 21 Oct 2026 ... (绝对时间,会被本地时间影响,已过时)
+- 没过期 > 直接用本地,**不发请求**(状态码显示 200 from disk cache)
+- 过期了 v
② 协商缓存:带上标识问服务器"变了吗"
If-None-Match: "abc123" < 对应 ETag(内容哈希,精确)
If-Modified-Since: ... < 对应 Last-Modified(文件修改时间,精度只到秒)
+- 没变 > 304 Not Modified,**不传 body**,用本地副本
+- 变了 > 200,返回新内容为什么 ETag 优先于 Last-Modified:
- Last-Modified 只精确到秒,一秒内改两次识别不出来。
- 文件被 touch 但内容没变,Last-Modified 会变,导致不必要的重传。
- ETag 是内容哈希,精确。
Cache-Control 的关键值:
| 值 | 含义 |
|---|---|
max-age=3600 | 强缓存 1 小时 |
no-cache | 可以缓存,但每次都要协商(不是"不缓存"!) |
no-store | 完全不缓存(敏感数据) |
private | 只允许浏览器缓存,CDN 不许 |
public | 允许 CDN 等中间代理缓存 |
immutable | 内容永不改变,连协商都省了(配合带哈希的文件名) |
注意 no-cache 的坑:名字叫"不缓存",实际是"缓存但必须校验"。真正不缓存是 no-store。这是面试常见陷阱。
六、HTTPS:TLS 握手做了什么
HTTPS = HTTP + TLS。TLS 要解决三件事:机密性(加密)、完整性(防篡改)、身份认证(防冒充)。
TLS 1.2 握手(简化,2-RTT):
① 客户端 > 服务器:ClientHello(支持的加密套件、随机数 A)
② 服务器 > 客户端:ServerHello(选定的套件、随机数 B)+ 证书
③ 客户端验证证书(查 CA 签名、查域名、查有效期、查吊销状态)
④ 客户端用证书里的公钥加密"预主密钥"发给服务器
⑤ 双方用 随机数A + 随机数B + 预主密钥 算出相同的会话密钥
⑥ 之后用对称加密通信
TLS 1.3(1-RTT,甚至 0-RTT):
合并了握手轮次,删掉了不安全的算法(RSA 密钥交换、CBC 模式等)为什么用"非对称 + 对称"混合:
- 非对称加密(RSA/ECDHE):安全但慢(慢几百倍),只用来协商密钥。
- 对称加密(AES/ChaCha20):快,用来加密真正的数据。
- 一句话:用非对称的"安全"来传递对称的"钥匙"。
证书链:服务器证书 > 中间 CA > 根 CA(内置在浏览器/系统里)。浏览器逐级验证签名,直到信任的根。这就是为什么"自签名证书"会报警——没有可信的根为它背书。
为什么这么设计
反事实 1:如果 HTTP 不加密
- 同一 WiFi 下的人能看到你所有的请求内容(明文)。
- 中间人可以篡改响应(往网页里插广告、改支付账号)。
- 无法确认"你连的真的是银行网站"(DNS 劫持 + 假冒服务器)。
反事实 2:如果只用非对称加密
每个请求都要做一次 RSA 运算,比对称加密慢 2~3 个数量级。一个网页几十个请求,CPU 直接爆。
反事实 3:如果没有缓存
每次打开网页都要重新下载所有资源。一个 2MB 的页面,弱网下要十几秒。缓存把"重复下载"变成"问一句变没变"(304 响应只有几百字节)。
面试怎么问
Q:HTTP/1.1、HTTP/2、HTTP/3 的区别?
一条主线:都在解决"往返次数"和"队头阻塞"。 1.1 加持久连接(省握手)、加管道化(但响应必须按序,队头阻塞没解决)。 2.0 二进制分帧 + 多路复用(解决应用层队头阻塞)+ 头部压缩。 3.0 换 QUIC(UDP),解决 TCP 层队头阻塞 + 更快的建连 + 连接迁移。
Q:强缓存和协商缓存的区别?
强缓存不发请求,直接看本地是否过期(
Cache-Control: max-age)。 协商缓存要发请求,带If-None-Match/If-Modified-Since,服务器判断没变就回 304(不带 body)。 顺序:先强缓存,过期了才走协商。
Q:HTTPS 的握手过程?为什么要非对称 + 对称?
非对称只用来协商密钥(安全但慢),对称用来传数据(快)。证书解决"我怎么确认公钥是你的"这个问题。
Q:为什么 HTTPS 能防中间人?
因为证书由 CA 签名,浏览器内置了可信根。中间人伪造证书需要 CA 的私钥,做不到;用自签证书浏览器会报警。 但要补一句:如果用户手动信任了恶意根证书(或系统被装后门),HTTPS 也防不住。
追问链:
HTTP 和 HTTPS 区别?
> HTTPS 怎么加密?(TLS)
> 用对称还是非对称?(混合:非对称协商密钥,对称传数据)
> 为什么不全用非对称?(慢几百倍)
> 怎么确认公钥是对方的?(证书 + CA 签名)
> 证书链怎么验证?(逐级验签到内置根)
> 那中间人能解密吗?(不能,没有私钥)和其他知识的连接
- < 来自 TCP 连接管理:三次握手、四次挥手、TIME_WAIT:keep-alive 复用 TCP 连接,省掉反复握手
- < 来自 TCP 可靠传输与拥塞控制:TCP 队头阻塞是 HTTP/2 的瓶颈,催生了 HTTP/3
- > 影响 DNS 与 ARP:两个"翻译官":DNS 查询本身也走 HTTP(DoH)或 UDP
- > 影响 工程实践:CDN 缓存策略、静态资源指纹(
app.a1b2c3.js)、immutable、Service Worker 离线缓存 - > 影响 安全:CSRF 靠 Cookie 的
SameSite、XSS 靠 CSP、点击劫持靠X-Frame-Options
自测 3 问
Q1:Cache-Control: no-cache 和 no-store 有什么区别?
点开看答案
no-cache:可以缓存,但每次使用前必须向服务器确认(走协商缓存)。名字有误导性,它禁止的不是"缓存",而是"直接用缓存"。no-store:完全禁止缓存,不写磁盘、不写内存,每次都重新请求。
什么时候用:
- 内容会变但想省流量 >
no-cache(用 ETag 协商,没变就 304)。 - 敏感数据(银行页面、token) >
no-store(连本地副本都不留)。
这是最经典的 HTTP 缓存陷阱题。
Q2:为什么 HTTP/2 的多路复用不能解决 TCP 队头阻塞?
点开看答案
因为 HTTP/2 的多路复用是在 TCP 之上做的:多个流被切成帧,交错发在同一条 TCP 连接里。
而 TCP 是"按序交付的字节流",它不知道帧的边界。一旦某个 TCP 段丢了,后面的所有字节都要在内核缓冲区里等,包括其他流的帧。
所以:
- HTTP/1.1 的队头阻塞在应用层(一个请求慢,后面排队)。
- HTTP/2 解决了应用层的,但把问题下沉到了 TCP 层——而且更糟:所有流挤在一条连接上,一个丢包卡住全部。
HTTP/3 用 QUIC 在用户态实现可靠传输,每个流独立编号、独立重传,才真正做到"一个流丢包不影响其他流"。
Q3:为什么 502 和 504 要分开?
点开看答案
因为它们指向完全不同的故障,排查方向相反:
- 502 Bad Gateway:网关(Nginx)连上了上游,但上游返回了非法响应(连接被重置、响应格式错、进程崩了)。> 去查上游进程是否存活、日志有没有崩溃。
- 504 Gateway Timeout:网关连上了上游,也发了请求,但上游在超时时间内没回。> 去查上游为什么慢(慢 SQL、下游依赖、死锁)。
一个"坏了",一个"慢了",处理方式完全不同。这是线上排障的基本功。
类比失效处 / 易错点
- "一问一答"会让人以为 HTTP/1.1 不能并发。实际上浏览器会开 6 个左右的 TCP 连接并行请求。
- HTTP 是无状态的,但 Cookie 让它"看起来有状态"。状态存在客户端,服务器每次都要重新识别——所以才有 Session、Token。
- 301 会被浏览器永久缓存。发错了很难撤回(用户清了缓存才恢复),所以不确定时用 302/307。
- HTTPS 不加密域名(SNI 明文)。中间人能看到你访问哪个网站,只是看不到内容。ECH(Encrypted Client Hello)在解决这个问题,但还没普及。
max-age是相对时间,Expires是绝对时间。Expires依赖客户端时钟,时钟不准就失效,所以现代都用max-age。- HTTP/2 的 Server Push 已经被主流放弃(Chrome 已移除),别在面试里当亮点讲。
✅ 验收清单
- 能写出一个完整的 HTTP 请求报文和响应报文(含请求行/状态行、头、空行、body)
- 能说出 GET 和 POST 在"安全/幂等"上的区别,并解释为什么支付接口要幂等键
- 能区分 401/403、502/504,并说出各自的排查方向
- 能用一条主线讲清 HTTP/1.0 > 1.1 > 2 > 3 各自解决了什么问题
- 能说清强缓存和协商缓存的顺序、各自的头字段、以及 ETag 为什么优于 Last-Modified
- 能解释
no-cache和no-store的区别(这是陷阱题) - 能讲清 TLS 握手的核心步骤,并解释"为什么非对称 + 对称混用"
- 合上书,用自己的话讲 3 分钟,中途不卡壳