第 2 章 网络请求基础:HTTP/HTTPS 协议原理、请求与响应结构、状态码详解

大家好,我是你们的老朋友。今天咱们聊聊网络请求最底层的东西——HTTP 和 HTTPS。很多人觉得这东西太基础,不就是个 URL 嘛,发出去等返回就行了。但我在 SDK 优化里踩过的坑,基本都是因为这些基础没吃透。你想想,一次网络请求从发起到结束,中间有多少环节?DNS 解析、连接建立、数据传输、连接关闭,每个环节都可能成为瓶颈。

我自己有个习惯:碰到线上问题,先抓包看原始请求和响应。很多时候问题就出在请求结构不对,或者状态码理解偏了。今天把这部分彻底理清楚,后面讲流量优化才有底。

HTTP 协议的本质与演化

HTTP 说白了就是客户端和服务器之间约定的对话格式。你发什么内容,对方按什么结构回复,全靠这个协议来约束。

目前主流版本就三个:HTTP/1.1、HTTP/2、HTTP/3。它们的主要区别我列个表:

版本 连接模型 多路复用 头部压缩 传输层
HTTP/1.1 串行队列 + 持久连接 不支持 无 TCP
HTTP/2 二进制分帧 支持 HPACK TCP
HTTP/3 无队头阻塞 支持 QPACK QUIC (UDP)

我记得前两年做一个 SDK,用户量上来后 HTTP/1.1 的队头阻塞把请求延迟推到 2 秒以上。后来切到 HTTP/2,用同一个连接发送多个请求,延迟直接降了 40%。这个教训让我意识到:协议版本选对了,就是免费的优化。

注意: 虽然 HTTP/2 支持多路复用,但很多 CDN 和反向代理在实现上仍然会对同一个域名的连接数做限制。我曾经遇到过一个案例,客户端开了 6 个 HTTP/2 连接,结果服务器端直接限流了。最好的做法是用单个长连接,配合优先级控制。

HTTPS 的加密握手与证书校验

HTTPS 本质上是 HTTP over TLS。它解决了三个问题:加密传输、数据完整性、身份认证。我见过不少开发者只关心 HTTPS 开启没开启,却忽略了握手阶段的开销。

TLS 握手流程大致如下:

  1. 客户端发起 ClientHello,附带支持的加密套件、TLS 版本、随机数。
  2. 服务器回复 ServerHello,选定加密套件,附带证书链和服务器随机数。
  3. 客户端验证证书,生成预主密钥,用服务器公钥加密后发送。
  4. 服务器解密得到预主密钥,双方各自计算出会话密钥。
  5. 发送 ChangeCipherSpec,之后所有数据加密传输。

这里有个点常被忽略:证书链的长度。我之前在某大厂 SDK 里发现,一个 API 的证书链包含 4 个中间证书,每次握手光证书验证就要多花 200ms。后来让服务端做 OCSP Stapling,把证书状态信息提前缓存了,问题才解决。

建议: 在 SDK 里尽量复用 TLS 会话。开启 Session ID 或 Session Ticket 机制,可以让重连时跳过完整的握手,省掉一次 RTT。我一般会在客户端缓存 2~3 个工单状态的会话票据。

请求与响应的结构

HTTP 请求和响应的格式其实非常清晰,我给大家拆开看:

请求结构

GET /api/v1/user HTTP/1.1
Host: example.com
User-Agent: SDKClient/1.0
Accept: application/json
Authorization: Bearer xyz

(请求体,GET 通常为空)

请求行包含 方法 + 路径 + 协议版本。头部字段每行一个,用冒号分隔。空行之后是请求体。我见过很多 SDK 的坑是:头部字段的顺序在不同 HTTP 库里不一致,导致某些后端解析失败。尤其是 Host 字段,HTTP/1.1 必须带上,否则服务器会返回 400。

响应结构

HTTP/1.1 200 OK
Date: Mon, 10 Mar 2025 12:00:00 GMT
Content-Type: application/json
Content-Length: 128

{"status":"ok","data":{...}}

响应行:协议版本 + 状态码 + 原因短语。后面跟头部和空行,最后是响应体。注意:Content-Length 和 Transfer-Encoding: chunked 是互斥的,不能同时出现。我踩过这个坑——后端同时返回了这两个头,SDK 的解析库直接崩溃。

状态码详解

状态码是服务器对请求结果的「一句话总结」。我习惯把它们按类别记忆:

  • 1xx 信息性:比如 101 Switching Protocols,用在 WebSocket 升级时。
  • 2xx 成功:200 OK 常见,204 No Content 表示请求成功但没有响应体,206 Partial Content 用于断点续传。
  • 3xx 重定向:301 Moved Permanently 和 302 Found 是最常见的。我建议默认不自动跟随,因为有些 SDK 内部重定向逻辑会泄露 authorization 头。
  • 4xx 客户端错误:400 Bad Request(参数问题)、401 Unauthorized、403 Forbidden、404 Not Found、429 Too Many Requests(限流)。
  • 5xx 服务端错误:500 Internal Server Error、502 Bad Gateway、503 Service Unavailable、504 Gateway Timeout。

做 SDK 时,状态码的异常处理要特别细致。我见过一个 SDK 遇到 429 不做退避,结果被网关封 IP。好的做法是:4xx 多数不重试,5xx 可以带指数退避重试,但要限制次数。

避坑指南: 我曾经因为没处理 302 重定向,导致用户数据被发送到第三方服务器。后来我在 SDK 里强制检查 Location 字段的 host 是否在白名单内,不在就报错。

核心流程图:HTTP/HTTPS 请求生命周期

下面这张图我把完整流程画出来,重点标注了 HTTPS 和 HTTP/2 的差异点。你可以看到,一次请求背后有多少握手和头部交互。

HTTP/HTTPS 请求生命周期 客户端 DNS 解析 TCP 三次握手 TLS 握手 HTTP 请求 服务器处理 HTTP 响应 关闭连接 HTTPS 场景下必须经过 TLS 握手;HTTP/2 可在一个 TCP 连接上并发多个请求 DNS 解析、TCP 握手、TLS 握手各消耗 1 个 RTT,共 3 RTT 后才能发 HTTP 请求

这张图里我特意标出 HTTPS 要比 HTTP 多一次 TLS 握手。你想想,多一个 RTT 在一些弱网环境里可能就是几百毫秒。所以业界搞出了 TLS 1.3、0-RTT 技术,就是想要省掉这一轮。

OK,这一章的核心知识点就这些。记住:请求结构对了,状态码懂了,握手流程熟了,你做 SDK 的时候就能一眼看出问题出在哪个环节。后面我们还会反复用到这些基础。