第 10 章 请求的上行:HTTP 与 HTTPS
副题:报文、连接、加密——浏览器和服务器之间,到底隔着什么
第 9 章我们让页面能渲染、能交互了。现在,用户在浏览器里点下"发布内容"——请求离开浏览器,走上网络。它要去哪?怎么去?路上会发生什么?本章跟着一次真实的请求走完网络段:先看慢请求怎么排查,再看请求的"语言"(HTTP 报文)、请求的"交通"(TCP 连接)、请求的"保险箱"(HTTPS 加密)。
本章要建立的认知
- HTTP 的核心实体是报文:请求报文和响应报文——第 7 章讲的接口约定(方法、状态码),都在报文里有固定的位置;
- HTTP 是无状态的协议——这个设计让它简单、可缓存、可扩展,代价是"记不住用户"(第 13 章的会话问题从这里来);
- HTTPS 不是另一个协议,是 HTTP + 一层加密(TLS)——它成为默认,是因为明文在互联网上等于裸奔。
10.1 慢请求之谜
小明在浏览器里点"发布内容",页面转圈了 8 秒,然后才成功。这已经不是第一次了——"接口有时候慢"。
这不是服务器挂了(页面能打开、其他接口正常),是"慢"——慢是一种比"挂"更常见的线上问题:系统还活着,但体验在流失。第 8 章的白屏是"没画出来",本章的慢是"画出来了但要等"——两类问题,两类排查,但开发者工具是同一个(Network 面板)。
第 8 章说过,"转圈"是"打不开"三种形态里的网络/服务端问题。现在页面能打开了(白屏解决),但接口慢是另一类问题:请求发出去了,响应迟迟不来。慢在哪?
打开开发者工具的 Network 面板,点开那个慢请求,浏览器把一次请求的时间线分成了四段:
- 排队(Queuing):请求在浏览器里排队等出发——可能是浏览器限制并发连接数(同一域名同时最多 6 个连接),也可能是前面的请求占着连接;
- 连接(Stalled/Connection):建立 TCP 连接的时间——三次握手(10.3 会讲),每次新连接都要付这笔"过路费";(域名解析 DNS 也发生在这附近——第 11 章展开)
- TTFB(Time To First Byte):请求发出后,到收到响应的第一个字节——这段是"服务器在处理",服务端慢(第 12 章)、数据库慢(第 14 章)都体现在这里;
- 下载(Content Download):响应体下载的时间——响应大的时候(图片、大 JSON)这一段长。
小明这个 8 秒的请求,点开时间线一看:连接(Stalled/Connection)占了 5 秒——不是服务器慢,是每次请求都在重新建立连接。为什么连接这么贵?因为浏览器和服务器之间的"交通"不是一条现成的路,每次都要先"修路"——这就是 TCP 的三次握手。
慢请求的排查口诀:先看时间线分段,再看慢在哪段——排队慢是浏览器侧(并发限制、连接复用),连接慢是网络侧(握手、距离),TTFB 慢是服务端侧(第 12/14 章的战场),下载慢是内容侧(响应太大)。"接口有时候慢"的"有时候",往往就藏在"每次请求都重新握手"里——握手是有固定成本的,请求越多,成本越明显。
慢请求的度量也对接一下第 4 章的验收标准:"首页 3 秒首屏"(第 4 章需求清单)在浏览器时间线里就是"排队 + 连接 + TTFB + 下载"四段之和——每一段都是第 4 章验收标准的一部分(网络段占多少、服务端占多少,一量便知)。第 16 章性能优化的"先度量再优化",在网络段就是从这四段开始的。
浏览器限制同域名并发连接数的原因先说一下(它不是一个随意的小数):保护服务器——如果浏览器放开限制,一个页面可以同时开 50 个连接到同一服务器,小服务器直接被打趴(第 23 章的限流解决的是同类问题)。这个限制是 HTTP/1.1 时代的"交通规则",HTTP/2 的多路复用(一个连接并行多个请求)把它绕开了——规则会过时,但"限制存在的理由"不会过时:每个设计决策都有它当时的约束。
"有时候慢"还有一种间歇性的形态,排查思路不同:不是每次都慢意味着"常态路径是快的"——那慢的时候发生了什么?三个最常见的候选:连接重建(连接池里的连接被服务器关了,这次请求重新握手)、网络抖动(丢包触发 TCP 重传,传输层在悄悄补救)、服务端偶发(某个请求赶上 GC、慢查询——第 14 章)。间歇性慢的排查比持续性慢难,因为难复现——但 Network 面板的时间线分段依然是最快的定位工具:分段会告诉你慢在传输还是慢在服务端,剩下的事就交给对应章节了。
还有一类"转圈"的网络原因要预告给第 11 章:域名解析慢——浏览器要先问 DNS"www.example.com 是哪台服务器"(第 11 章),DNS 没响应/解析慢,请求根本发不出去(时间线上表现为连接前的一段空白)。网络段的"慢",按顺序查:DNS → 连接 → 发送 → 等待(TTFB)→ 下载——每一段都有它的主人(DNS 是第 11 章,连接是本章,等待是第 12/14 章,下载是第 16 章)。
小明这个 8 秒请求的完整修法:先确认时间线(连接段 5 秒)→ 检查是不是每次请求都新建连接(Network 面板看 Connection ID——每个请求的 ID 相同说明在复用,不同说明在重建)→ 发现前端代码每发一个请求就 new 一个连接(把 fetch 封装里的连接池用起来)→ 5 秒变 0.3 秒。修的不是服务器,是连接策略——网络段的慢,解法常常在网络段自己身上。
要理解连接为什么贵,得先看请求的"语言":HTTP 报文。
10.2 报文:HTTP 的语言
浏览器和服务器之间的对话,用的是一种叫 HTTP(HyperText Transfer Protocol)的语言。它的每个句子——一次请求、一次响应——都是一个报文(message)。报文是 HTTP 的核心实体,第 7 章讲的接口约定(方法、URL、状态码),全都要写进报文里。
报文的骨架是固定的三部分:起始行(请求行或状态行)+ 首部(Headers,键值对)+ 主体(Body,内容)。先记住这个骨架,下面三个真实的报文都是它的实例。方法语义在报文里的差异也一眼看清:GET 没有主体(请求行 + 首部就完了——它只是"读",没什么可带的);POST 有主体(要提交的内容放在里面)——第 7 章说"GET 幂等、POST 不幂等",报文形态就是它的物理体现:GET 连"内容"都没有,自然不会产生副作用;POST 带着内容来,"创建了什么"由服务器决定。
一次请求的报文长这样(小明发布内容,第 7 章的 POST /v1/contents):
POST /v1/contents HTTP/1.1 ← 请求行:方法 + 路径 + 协议版本
Host: api.example.com ┐
Content-Type: application/json │
Authorization: Bearer xxxxx │ 首部(Headers):请求的"元信息"
Content-Length: 86 │
├─ 空行:分隔首部和主体
{ ┐
"title": "一台二手相机", │ 主体(Body):请求的"内容"
"body": "九成新,快门数两万……" │
} ┘
响应报文的结构是同一套骨架,只是第一行换成了状态行:
HTTP/1.1 201 Created ← 状态行:状态码 + 原因短语
Content-Type: application/json ┐
Content-Length: 512 │ 首部
├─ 空行
{ ┐
"id": 1024, "title": "一台二手相机", ... │ 主体:创建成功的内容
} ┘
再看一个错误响应的报文——库存不足时(第 7 章的 OUT_OF_STOCK):
HTTP/1.1 409 Conflict ← 状态行:状态码 + 原因短语
Content-Type: application/json ┐
request-id: a1b2c3 │ 首部
├─ 空行
{ ┐
"code": "OUT_OF_STOCK", │ 主体:业务错误码(第 7 章)
"message": "商品库存不足" │
} ┘
第 7 章的完整约定在这里都能看到:409 是状态码(机器知道"冲突")、OUT_OF_STOCK 是业务码(程序知道"为什么冲突")、message 是人读的(前端展示给用户)、request-id 是排障的(第 25 章日志追踪)——第 7 章设计的三层错误结构,在报文里一字不差。第 7 章是'约定',本章是'约定写进协议的样子':两章的边界就在这——第 7 章讲接口设计(业务语义),本章讲协议机制(报文怎么承载语义)。
把第 7 章的整张接口清单(12 个接口)放回报文里看一遍,对应关系是完整的:方法和路径在请求行、查询参数在 URL、请求体在主体、凭证在 Authorization 首部、分页信息在响应主体——第 7 章设计的每一个约定,报文里都有它的位置。这就是"接口是承诺"的协议层实锤:契约写的每一个字,最后都会变成一个报文里的字节。
报文的"开销"先给一个直觉:首部是固定开销,主体是实际内容。一个请求的报文,首部(Host、Content-Type、Authorization……)通常几百字节——如果请求很多、每次都是同样的首部,这部分就成了"重复的过路费"(HTTP/2 的头部压缩就是冲这个去的,10.5 提过)。报文大小的常识:首部算开销,能省则省;主体算内容,该大就大——接口设计时"少传多余字段"(第 7 章的 DTO 就是干这个的),在报文层面就是"让主体更小"。
大响应还有一个"流式"形态先说:分块传输(Transfer-Encoding: chunked)——响应不一次性给完,而是一块一块地给(服务器边生成边发,浏览器边收边显示)。它对"大响应"的意义:不用等全部生成完才开始下载——TTFB 变短了,"首字节"更快到(第 8 章的 FCP 因此受益)。接口的流式响应(第 16 章性能、第 25 章日志)都用它。
报文里还有一个"看不见的编码"要交代:中文是怎么放进报文的。报文是文本协议,但文本≠ASCII——小明的标题"一台二手相机"要放进报文,得先编码成字节(HTTP 报文默认按 UTF-8 传输;Content-Type: application/json; charset=utf-8 里的 charset 就是在说"主体是 UTF-8 编码")。编码不一致是经典乱码事故的根源:服务器用 UTF-8 存,前端按 GBK 解析,标题就变成"锟斤拷"——报文的两端必须约定同一个编码,这又是"约定"(第 7 章精神)在协议层的体现。
首部是报文的"元信息",常见类型混个脸熟(每个一行):Host(去哪台服务器——虚拟主机靠它区分)、Content-Type(主体是什么格式:JSON/表单/图片)、Content-Length(主体多大)、Authorization(我是谁——第 13 章)、User-Agent(什么浏览器发的)、Accept(我想要什么格式的响应)、Cache-Control(能不能缓存——第 16 章)、Set-Cookie(服务器让浏览器存一个小纸条——下一段就讲它)。
报文设计里有三个"为什么":
**第一,为什么首部是文本(键值对)而不是二进制?**因为 HTTP 诞生于 1990 年代初,设计目标之一是"人能读懂"——用文本协议,调试工具直接看报文就能知道发生了什么(第 8 章的开发者工具就是干这个的)。代价是文本比二进制占空间,HTTP/2、HTTP/3 引入二进制帧(10.5 一句带过)就是冲着这个去的。
**第二,为什么要有空行?**首部和主体的分隔线——没有它,接收方不知道"元信息结束、内容开始"。格式的每个细节都有它的职责,哪怕是一个空行。
**第三,为什么状态码带"原因短语"(Created)?**状态码是给程序看的(201),原因短语是给人看的(Created)——和业务错误码的 code/message 设计(第 7 章)同一个思路:机器读码,人读短语。
还有一个首部藏着第 13 章的地基:Cookie。服务器通过 Set-Cookie 首部让浏览器存一小段数据(比如"登录凭证"),浏览器之后的每个请求都自动带上(Cookie 首部)——HTTP 无状态,Cookie 是'给无状态协议补状态'的第一块砖:服务器不用记"这个用户是谁",浏览器每次自己报上名来(凭证在浏览器手里)。第 13 章的 Session/JWT 之争,核心就是"这块砖怎么用"——这里先记住:Cookie 让无状态的 HTTP 有了'记住'的能力。
调试报文也很简单:开发者工具 Network 面板点开任意请求,Headers 标签页就是完整的请求/响应报文(起始行、每个首部都在)——第 8 章用它排查白屏,本章用它读报文。报文不是抽象概念,是你每次 F12 都能看到的实物。
10.3 无状态与连接
报文讲完了,回答 10.1 的问题:连接为什么这么贵?
HTTP 是无状态的——这是 HTTP 最核心的设计决策,也是理解整个 Web 的钥匙。无状态的意思是:每个请求都是独立的,服务器不记得上一个请求是谁发的。浏览器发"请求 1:看首页",服务器响应;发"请求 2:看评论",服务器当作"另一个陌生人"处理——它不记得"刚才那个用户刚看过首页"。(无状态还有一个排查时的推论:HTTP 没有'连接'的概念——连接是 TCP 的,HTTP 只认报文。"连接断了"是 TCP 的事,"请求没响应"是 HTTP 的事——排查时先分清是哪一层。)
无状态的好处是巨大的:简单(服务器不用维护"谁在干什么"的状态)、可缓存(同一个 GET 请求,任何时刻结果都一样,缓存可以放心复用——第 11 章 CDN 和第 16 章缓存都建立在它上面)、可扩展(任意一台服务器都能处理任意请求,不用"会话粘性"——第 23 章负载均衡会看到它的价值)。
"可扩展"多展开一层:因为服务器不记状态,加一台服务器就能分担流量——任何请求到任何一台机器,结果都一样(无状态 = 无绑定)。这个性质是 Web 能水平扩展(加机器而不是换大机器)的根本原因,也是第 23 章负载均衡、第 26 章服务拆分的基石:一旦某个请求"必须到某台机器"(有状态),扩展就卡住了——这是第 13 章 Session 方案的阿喀琉斯之踵(Session 存在一台服务器上,其他服务器不认识——所以 Session 要共享存储),提前记住这个伏笔。
第 26 章还有一个呼应:无状态是服务拆分的前提。第 26 章拆服务时,"服务之间怎么通信"是核心问题——无状态的接口(每个请求自带完整信息)让服务可以随便拆、随便挪、随便加实例;有状态的接口(服务器记着客户端)一拆就乱。所以"保持接口无状态"不只是 HTTP 的规矩,是整个系统可扩展性的地基——这句话,第 26 章会以"服务设计原则"的身份再出现一次。
无状态的代价也直接:服务器记不住用户。用户登录了,下一个请求服务器又问"你是谁"——这就是第 13 章会话问题的来源(Session/JWT 都是"给无状态协议补状态"的方案)。记住这个因果:无状态是 HTTP 的根基,会话机制是给这个根基打的补丁。
无状态和缓存的关系点透一层:无状态让缓存成为可能——同一个 GET 请求任何时刻结果都一样,缓存(第 11 章 CDN、第 16 章浏览器/服务器缓存)才能放心复用响应。缓存的控制靠响应首部 Cache-Control(public, max-age=3600 表示"可以缓存一小时")——这是 HTTP 给"重复请求"的官方解药:既然无状态让请求可以重复,缓存就让重复不白费。无状态 + 缓存,是 HTTP 性能的根基组合——第 16 章会看到它们怎么被用到极致。
但"连接贵"和"无状态"是两回事:无状态说的是"应用层不记状态",连接说的是"传输层的通路"。浏览器和服务器之间的通路叫 TCP(Transmission Control Protocol)——HTTP 跑在 TCP 上面。TCP 连接的建立要三次握手:
浏览器 服务器
│ │
│── SYN ────────────────→ │ 1. 浏览器:我要连你(同步请求)
│←──── SYN + ACK ────────│ 2. 服务器:好,我也要连你(同步+确认)
│── ACK ────────────────→ │ 3. 浏览器:收到,连接建立
│═════════════════════════│ 连接建立,开始发 HTTP 报文
为什么是三次而不是两次?因为双方都要确认"对方能收到我的消息":第一次浏览器确认"服务器在";第二次服务器确认"浏览器在";第三次浏览器确认"服务器收到我的确认"——三次之后,双方都确认了"这条路是通的"。两次不够(服务器不知道浏览器收到它的 SYN+ACK 没有),四次多余(第三次已经是确认的确认)。
三次握手的代价:每次新连接都要 1 个 RTT(round-trip time,一来一回的时间,国内跨省网络一般 20-50ms)。小明那个 5 秒的连接时间,就是"每次请求都新建连接"的累积——每个请求先付握手费,再发报文。解法是连接复用:Keep-Alive(HTTP/1.1 默认:一个 TCP 连接处理多个请求,用完了不立即断开)、连接池(浏览器侧复用同一域名的连接)。第 8 章说的"请求排队"(Queuing),很多就是"等连接池里的连接空出来"。
三次握手不是随便定的数字,它是 1970 年代协议设计者的经典结论:两次不足以确认双向,四次是浪费——这个"恰到好处"的设计经受住了五十年的考验,是第 2 章"技术为什么不断变化"的反例:有些设计一旦对了,就稳定地对了(握手至今没变过,变的只是它上面的层)。
Keep-Alive 的细节也交代一下:连接不是"永远开着"——服务器会设超时(比如 60 秒无请求就关闭),连接池里的连接会被回收。所以"连接复用"不是零成本:连接是会被关的,关了就要重新握手——这就是"有时候慢"的另一个来源:连接池里的连接刚被服务器关了,下一个请求重新握手(10.1 说的"连接重建"候选)。握手失败的场景也认识一下:超时(服务器没响应——防火墙拦了/服务器挂了)和拒绝(服务器主动拒——连接数满,第 24 章高可用会看到)——Network 面板上表现为连接段卡住或直接报错。
连接的真实成本比握手还多两层:TLS 握手叠加(HTTPS 要在 TCP 握手后再来一轮 TLS 握手,10.4 会讲——新连接的 HTTPS 是两次握手)、慢启动(TCP 的拥塞控制:新连接前几个包发得慢,逐渐提速——连接刚建立时是"慢速起步")。所以"新建连接贵"不只是 1 个 RTT:它是握手 + 加密握手 + 慢启动的三重过路费。这也是为什么连接复用的收益比看上去更大——省下的不是一次握手,是一整套"修路"成本。
TCP 为什么叫"可靠传输"也要说一句:它靠 ACK 确认 + 超时重传 + 顺序重组 保证"发的每个字节都到达、且按序到达"——报文在网络上可能丢包、乱序,TCP 负责把"可能出错的网络"包装成"看起来可靠的管道"(HTTP 不用操心丢包,TCP 都处理了)。相对的 UDP 不保证可靠(快但会丢),HTTP/3 的 QUIC 就是"UDP 上自己实现可靠"——传输层的取舍,也是第 5 章决策模型的案例。
TCP 还有两个"看不见的机制"在默默工作:流量控制(接收方告诉发送方"我还能收多少"——防止发送太快把对方撑爆)和拥塞控制(发送方探测网络容量,慢了就收敛——防止把网络堵死)。这两个机制和 10.1 的慢请求有关:网络抖动时,TCP 会自动降速重传——你在时间线上看到的"连接段变长",很多是 TCP 在背后"悄悄补救"(丢包 → 重传 → 看起来慢,其实协议在努力)。理解这一点,排查"网络慢"时就不会只怪服务器:慢可能是 TCP 在替网络的不靠谱买单。
回到 10.1 的慢请求:小明 8 秒的请求,5 秒在连接——修法是让连接复用起来(Keep-Alive/连接池)。慢请求的答案,往往不在"服务器快不快",在"连接省不省"。
把网络段放回第 8 章的关键路径做一次量化:一次 HTTPS 请求的关键路径 = DNS(第 11 章)+ TCP 握手(1 RTT)+ TLS 握手(1-2 RTT)+ 发送请求 + TTFB + 下载——网络段占了关键路径的大半。第 8 章说"白屏时间 = 关键路径长度",本章给出了关键路径的网络段怎么算——两章合起来,"首屏时间"才有一个完整的账本。
连接池还有一个浏览器侧的限制要知道:同一域名最多约 6 个并行连接(HTTP/1.1 的浏览器限制)——页面同时发 20 个请求时,14 个在排队。这个限制是 HTTP/2 要解决的核心问题之一(多路复用:一个连接并行跑多个请求,10.5 会提)——"连接是稀缺资源"这个事实,贯穿 HTTP 的整个演进史。
10.4 HTTPS:为什么加密成为默认
报文有了、连接建了——但还有一个致命问题:HTTP 报文是明文。明文意味着,报文经过的每一台设备都能看到内容:你公司的路由器、小区的光猫、运营商的路由器、跨国的海底光缆……任何中间设备都能读到小明的标题和正文。
明文不只是"隐私"问题,更是信任问题:中间设备不仅能看,还能改。经典场景:运营商在网页里插入广告(HTTP 时代真实发生过);更严重的是中间人攻击——攻击者伪装成"服务器",浏览器以为自己在和 api.example.com 说话,其实在和一个骗子说话(小明以为在发布内容,其实把内容发给了攻击者;更糟的是,攻击者可以替小明"发布"他想发的内容——篡改方向、金额、收货地址,都是中间人的攻击面)。
所以 HTTPS 不是"高级选项",而是 Web 的默认形态。HTTPS = HTTP + TLS:TLS(Transport Layer Security)是加在 HTTP 外面的一层加密。HTTPS 要解决两件事:加密(内容只有浏览器和服务器能读)和身份验证(浏览器确认对面真的是 api.example.com,不是骗子)。
TLS 的机制是"非对称 + 对称"的组合,一次握手的简化流程:
浏览器 服务器
│ │
│── 你好,我要安全连接 ─────────────────→ │
│←── 这是我的证书(含公钥)─────────────│ 1. 服务器出示证书(身份证明)
│ (浏览器验证证书:CA 签名、域名、有效期) │
│── 用公钥加密一个"会话密钥" ──────────→ │ 2. 浏览器生成会话密钥,公钥加密传过去
│←── 收到,开始用会话密钥加密通信 ──────→│ 3. 之后的通信都用会话密钥(对称加密)
│════════ 加密通道建立 ═════════════════│
这套机制的两个关键设计:
**第一,为什么不用非对称加密直接传数据?**因为非对称加密(公钥/私钥)慢——它用数学难题(大数分解、离散对数)做加解密,比对称加密慢几个量级。所以设计是:非对称只用来安全地传一个"会话密钥",传完就换对称加密传数据——非对称解决"密钥怎么安全送达"(这是最难的问题),对称解决"数据怎么高速加密"。一次握手,两种加密,各司其职。
对称加密的直觉也补一句:它就像一把临时钥匙——浏览器和服务器各拿一把相同的钥匙(会话密钥),用 AES 这类算法加解密(快),但钥匙本身不能直接通过网络传(传了就被中间人拿到了)——所以需要非对称加密来"护送钥匙"。非对称是护送员,对称是保险箱:护送员只跑一次(握手),保险箱用全程(通信)。(顺带说清一个常见误解:"HTTPS 的锁"不代表"网站可信"——它只代表"通道加密了";网站是不是骗子,是证书域名和 CA 的事,锁图标管不了这个。)
第二,证书是干什么的?公钥本身不能证明"我是 api.example.com"——任何人都能生成一对公钥/私钥说"我是 api"。所以需要证书:由CA(Certificate Authority,证书颁发机构)签名的"身份证明"。证书里写着"域名 + 公钥 + CA 的签名",浏览器验证三步:签名合法吗(CA 的签名链)、域名对吗(证书的域名 == 访问的域名)、过期了吗(证书有有效期)。验证通过,才信"对面真的是 api.example.com"。证书体系就是 Web 的"身份证系统"——CA 是发证机关,浏览器是查证机关。
证书链的完整形态是三层:根 CA → 中间 CA → 网站证书。根 CA 的证书内置于浏览器(浏览器厂商信任的"根证书库"),中间 CA 由根 CA 签发,网站证书由中间 CA 签发——验证时浏览器沿着链往上查,每一层的签名都合法、直到内置的根,才信任网站证书。这条链存在的意义:根 CA 的私钥不能直接用来签发每张证书(一旦泄露全盘皆输),中间层是隔离风险用的。(证书管理还有一个省力技巧:通配符证书(*.example.com 覆盖所有子域名)和 SAN 证书(一张证书列多个域名)——省的是"每个子域名一张证书"的续期负担。)
TLS 自身也在演进,一句带过:TLS 1.2(2008,今天的基线)→ TLS 1.3(2018,握手从 2 个 RTT 降到 1 个——更快的 HTTPS;同时删掉了一批过时的加密套件——更安全的 HTTPS)。TLS 1.3 的"握手更快"和第 10.1 的慢请求直接相关:每一次握手优化,都是网络段性能的实打实收益——协议演进不是学术兴趣,是"连接更省"(10.3)的延续。
这套信任模型还有一层哲学:Web 的信任建立在"信根"上——浏览器内置的根证书库(全球大约一百多个根 CA)是信任链的起点,所有人默认信任它们。这个模型不是完美的(根 CA 被攻破是灾难性事件,历史上发生过),但它解决了"如何在互不认识的双方之间建立信任"——互联网上没有"见面",只有"背书"(CA 的签名)。第 19 章安全会看到,信任模型的每一个假设(证书、签名、密钥)都是攻击者的目标。
HTTPS 的部署现实也交代一句:证书按验证等级分 DV(验证域名所有权,免费、几分钟签发——Let's Encrypt 就是它,自动续期)、OV(验证组织身份)、EV(最高等级,地址栏显示组织名)——对大多数业务,DV 足够;证书到期忘了续是每年都在发生的事故("连接不安全"报错),所以证书续期要自动化(Let's Encrypt 的自动续期是标配)。还有一个加强头先认识:HSTS(HTTP Strict Transport Security,响应头)——告诉浏览器"这个域名只准用 HTTPS 访问",防"先走 HTTP 再被劫持跳转"的降级攻击。
验证 HTTPS 的实操也很简单:点浏览器地址栏的锁图标 → "连接安全" → "证书有效"——能看到证书的颁发者、域名、有效期,这就是 CA 链验证的"人肉版"。遇到"连接不安全"的报错,第一反应应该是看证书:过期了?域名不匹配?还是证书链断了——三种情况三种修法(续期/换证书/查部署)。
HTTPS 的代价要诚实说:握手延迟——TLS 握手要在 TCP 握手之后再来一轮(1-2 个 RTT),新连接的 HTTPS 比 HTTP 慢一拍;证书管理——证书要买(或 Let's Encrypt 免费)、要续期、要部署,到期忘了续就是"连接不安全"的报错(每年都有公司栽在证书过期上)。但对比明文的代价(被看、被改、被冒充),这些成本是明确划算的——这就是为什么 HTTPS 成为默认:不是因为它便宜,是因为明文已经不可接受了。
加密和认证要区分开——这是 HTTPS 里最容易混的两个概念:HTTPS 加密的是'传输通道'(路上的内容别人看不到),但**'你是谁'的问题 HTTPS 不回答**——小明登录没登录、有没有权限发帖,是第 13 章认证的事。打个比方:HTTPS 是"封好的信封"(路上没人能拆),第 13 章是"门禁"(进门要出示证件)——信封保证路上安全,门禁保证门口安全,两件事分开管。混在一起想("都 HTTPS 了怎么还要登录"),是初学者最常见的困惑。
把 HTTPS 放回第 8 章的关键路径:TLS 握手发生在关键路径上(第一个 HTML 到达之前,要完成 TCP + TLS 两次握手)——所以"白屏"的另一种可能:不是 CSS 没到,是握手还没完成(网络差时,两次握手就是 2-3 个 RTT,200ms+)。第 8 章的白屏排查清单里加上网络段:HTML 为什么没到?——排队/连接/握手,都在关键路径的前半段。这也呼应了第 16 章的网络优化:连接复用和 TLS 会话复用(session resumption),都是"让关键路径的网络段变短"的手段。
10.5 HTTP 的代价与边界
HTTP 讲完了,做一次诚实的总结。
HTTP 的三笔收益:简单(文本报文,人能读懂)、无状态(可缓存、可扩展、可水平扩展)、开放(任何人都能实现,生态最大)。三笔代价:无状态记不住用户(第 13 章打补丁)、明文不安全(HTTPS 打补丁)、文本协议占空间(HTTP/2/3 打补丁)。注意这个模式:HTTP 的每一次"升级",都是给它的某个原始设计打补丁——第 2 章的演进循环,在协议身上一样成立:无状态是 1990 年代的设计,会话、加密、二进制帧都是它长出来的问题逼出来的答案。
HTTP 版本演进一句带过(第 9 章提过 HTTP/2 只动传输不动语义):HTTP/1.1(1997,Keep-Alive、缓存控制——今天还在用)、HTTP/2(2015,二进制帧、多路复用——一个连接并行多个请求)、HTTP/3(2022,改用 UDP 的 QUIC,解决 TCP 的队头阻塞)。三个版本解决的是同一件事:连接和传输的效率——接口语义(方法、状态码、URL)完全没变。这也是为什么第 7 章说"接口设计可以放心依赖 HTTP 语义":语义稳定,传输在演进。
HTTP/1.1 的两个已知限制先认识(它们就是 HTTP/2/3 要解决的):队头阻塞——一个连接上的请求按顺序处理,前面的响应慢了,后面的全部排队(HTTP/2 的多路复用解决);半双工——同一时刻一个连接只能单向传(请求或响应,不能同时),大响应会堵住连接(HTTP/2 的帧交错解决)。"协议升级"的本质,就是"旧设计的限制被业务逼出来"(第 2 章演进循环的又一次实例)。
还有一个第 1 章的回扣:HTTP 是最容易"名词地图化"的协议领域——报文、握手、ACK、RTT、TLS、证书、CA、HSTS、QUIC……一堆名词,每个都能背定义,但连不成地图。本章的"机制先行"(跟着一次请求走)就是防名词地图:先立住"一次请求的网络旅程"这条主线,所有名词(报文、握手、加密、证书)都是这条路上的站点——你记住的不是名词,是名词之间的路。
HTTP 还是 REST 能成立的地基(第 7 章回扣):REST 的所有约定——方法、状态码、缓存头——都是 HTTP 的现成语义。REST 不是发明了一套新语言,是把 HTTP 的语言用得更规范。这也是为什么 REST 能成为 Web 的通用语:它建立在所有人都会的 HTTP 之上(第 7 章说过"HTTP 是 Web 上唯一所有人都认得的协议"——本章就是这句的机制层)。
网络调试还有一个命令行工具先认识:curl。curl -i https://api.example.com/v1/contents 会在终端里打出完整的响应报文(状态行、首部、主体)——服务器端排查接口问题时,curl 是第 8 章开发者工具的"无浏览器版":一样的报文,不一样的环境(第 12 章服务端会反复用它)。
压测工具也混个脸熟(第 16 章会用到):ab(ApacheBench)和 wrk 能模拟大量并发请求压一个接口——"这个接口每秒能扛多少请求"(QPS)是第 16 章性能优化的基准数字。测压时注意:压测要测的是"接口的能力",不是"网络的锅"——压测工具本机跑,网络延迟是零,量出来的就是服务端的真实能力。
要不要 HTTPS,其实不是决策——2020 年之后的新系统默认 HTTPS,浏览器对 HTTP 网站标"不安全",行业标准是"默认加密"。真正需要走九步的,是"哪些内容需要额外的性能成本":全站 HTTPS 是底线;图片/静态资源用 CDN + HTTPS(第 11 章);内网服务之间是否加密(内部网络可信度)是唯一有讨论空间的地方。这个决策的九步很轻:业务目标(数据安全)→ 约束(性能预算)→ 候选(全站/部分/内网豁免)→ 维度(安全 vs 延迟)→ 选择(全站,内网按需)→ 收益(防看防改防冒充)→ 代价(握手延迟/证书管理)→ 演进(性能瓶颈再优化)。大多数项目的答案都是同一个:默认全加密,优化留给瓶颈。案例从第一天就全站 HTTPS——这是第 4 章需求清单里"数据不能丢"(第 4 章非功能需求)在协议层的落点:需求清单的每一行,都要在协议层找到它的答案。
还有一个给第 23 章的伏笔:HTTP 的幂等语义(第 7 章讲过 GET 幂等)在网络层有一个对应物——超时重试。请求发出后没收到响应,是"服务器没收到"还是"响应丢了"?TCP 的确认机制(ACK)保证"报文不会丢",但不保证"响应一定能回来"(服务器可能处理到一半崩了)。所以网络层有重传(TCP 层面),应用层要自己决定要不要重试(第 17 章的回调重试、幂等判断,都是"网络不可靠"的应对)。网络不可靠是 Web 的基本事实——这句话会在第 17 章反复出现。
方法与安全也有一层关系(第 19 章展开):GET 不产生副作用是 Web 安全的地基之一——CSRF 攻击(第 19 章)能得手,往往因为某些系统用 GET 干了"改数据"的活("GET 一下就把订单取消了",第 7 章警告过);严格区分方法语义(GET 只读、POST/PUT/DELETE 才改),安全审计时少一半麻烦。
网络段的优化还有一笔账记给第 16 章:连接复用(本章)、HTTP/2 多路复用、QUIC(HTTP/3)、资源合并与 CDN(第 11 章)——网络段的每一点优化,都在缩短"关键路径"(第 8 章)的网络部分。第 16 章性能优化的全链路视角里,网络段和渲染段是两大战场——本章是网络战场的入场券。
10.6 本章对应表
| 业务诉求 | 技术选择 | 为什么 | 代价/取舍 |
|---|---|---|---|
| 前后端要对话 | HTTP 报文(起始行+首部+主体) | 统一的协议语言,简单、可读、可扩展 | 文本协议占空间(HTTP/2 二进制帧补丁) |
| 服务器要记住用户 | (无状态是 HTTP 的设计) | 简单、可缓存、可扩展 | 记不住用户——会话机制打补丁(第 13 章) |
| 连接要省 | Keep-Alive / 连接池 | 三次握手每次付 1 个 RTT,复用省过路费 | 连接管理有复杂度,排队等连接 |
| 数据不能明文 | HTTPS(HTTP + TLS) | 明文可被看、被改、被冒充 | 握手延迟 1-2 个 RTT、证书要买要续 |
| 对面是不是真的服务器 | 证书链(CA 签名) | 公钥不能自证身份,需要发证机关 | 证书体系有信任成本,CA 被攻破是灾难 |
| 接口"有时候慢" | 网络时间线排查(排队/连接/TTFB/下载) | 慢在哪一段看哪一段 | 每段的原因不同,要分别排查 |
每一行都在本章正文里有完整的论证:报文在 10.2,无状态在 10.3,连接在 10.3,HTTPS 在 10.4,证书在 10.4,排查在 10.1。和前面章节的对应表一样,先看"业务诉求"列——这一章的每一行,都是从"8 秒的转圈"里长出来的。
把本章放回旅程图(图 8-1):浏览器站(第 8、9 章)已过,本章是网络段——请求离开浏览器后的第一段路。网络段还有下一站(第 11 章 DNS 与 CDN:域名怎么变成 IP、CDN 为什么存在),然后才是服务端(第 12 章起)。第三部分的地图进度:浏览器 ✅ → 网络(上)✅ → 网络(下)→ 服务端 → 数据层 → 第 15 章全链路。对两种读者,本章的收益也呼应前两章:后端读者把"接口"从语义层落到了协议层(报文、握手、加密——平时天天打交道的东西终于看清了全貌);前端读者把 Network 面板从"看响应"升级到"看时间线"(排队/连接/TTFB/下载——排查慢请求的必备技能)。全栈的"全",在网络这里又补了一块拼图。
本章小结
HTTP 速查(第三部分回指本章时翻回这里):
- 报文三部分:起始行(方法/状态码)+ 首部(元信息)+ 主体(内容)——第 7 章的约定全在报文里有位置;
- 无状态是 HTTP 的根基:简单、可缓存、可扩展——代价是记不住用户(第 13 章补丁);
- TCP 三次握手:双方确认"路是通的"——每次握手 1 个 RTT,Keep-Alive/连接池省过路费;
- HTTPS = HTTP + TLS:证书验身份(CA 链)+ 公钥传密钥 + 会话密钥加密——非对称传密钥、对称传数据;
- 网络时间线四段:排队/连接/TTFB/下载——慢在哪段看哪段,连接慢是握手,TTFB 慢是服务端。
(五条速查对应本章的认知结构:1 是协议的语言,2-3 是协议的性格(无状态)与交通(连接),4 是协议的保险箱,5 是排查的方法——报文、无状态、连接、加密,是 HTTP 的四根柱子;本章的报文示例和排查时间线,第 25 章可观测性会以"日志里的请求"身份再见到。)
- HTTP 的核心实体是报文:请求报文和响应报文——第 7 章讲的接口约定,都在报文里有固定的位置;
- HTTP 是无状态的协议——简单、可缓存、可扩展,代价是"记不住用户"(第 13 章会话问题从这里来);
- HTTPS 不是另一个协议,是 HTTP + 一层加密(TLS)——它成为默认,是因为明文在互联网上等于裸奔;
- HTTP 的每次升级都是给原始设计打补丁(会话/加密/二进制帧)——第 2 章的演进循环在协议身上同样成立;
- 网络不可靠是 Web 的基本事实——超时重试、幂等判断(第 17 章)都是它的应对。
下一章
请求的报文写好了,连接建好了,加密也加上了——但还有一个问题:**浏览器怎么知道要去哪台服务器?**小明输入的是 www.example.com,可网络世界里没有"名字",只有 IP 地址。谁把域名翻译成 IP?翻译要经过哪些关卡?还有,为什么有的页面加载飞快、有的慢吞吞——除了服务器远近,还有什么在起作用?
下一章,请求的上行(下):DNS 与 CDN——域名怎么变成 IP,以及 CDN 为什么存在。