Skip to content

HTTP 与 HTTPS ​

S2-014建议学进阶约 90 分钟JDK 21计算机基础 · 计算机网络

1. 本节定位 ​

《TCP 与 UDP》讲了传输层;本节讲应用层最重要的协议:

text
HTTP:  明文文本协议,简单但"谁都能看" ✗
HTTPS:HTTP + TLS,加密 + 完整性 + 身份验证 ✓✓

★ 本节用 JDK 自带的 HttpServer 起真实服务 ✓ ——并用自签名证书起一个真实的 HTTPS 服务 ✓✓

2. 学习目标 ​

  • 能说出请求/响应的结构与必需的头 ★★★;
  • 知道状态码分组的语义,并据此设计重试策略 ★★★;
  • 知道 HTTP 请求头在真实项目里做什么 ★★;
  • 能说清 HTTPS 做了哪三件事 ★★★;
  • 知道"证书信任链"的现实意义 ★★★;
  • 知道为什么真实网站不用你手动信任证书 ★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看一个"只记 200 和 404"的代价 ✗:

java
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
if (response.statusCode() == 200) {
    return parse(response.body());
}
// 其他情况一律重试 —— 包括 400(参数错)、401(未认证)、403(无权限)
retry();                                  // ✗ 白重试三次,还放大故障

★ 本节的实测会告诉你 4xx 与 5xx 的区别有多重要 ✓:

text
GET /api/orders/ORD-1    → 200
POST /api/orders         → 201
GET /api/orders/ORD-999  → 404(RESOURCE_NOT_FOUND)
DELETE /api/orders/ORD-1 → 405(METHOD_NOT_ALLOWED)

★ 关键结论 ✓✓:

4xx 是"客户端错",重试没用(参数错、没权限,重试一百次还是错) ✗

5xx 是"服务端错",可以重试(可能只是瞬时过载) ✓

混淆这两者,会让故障被重试放大 ✗✗

5. 核心概念 ​

5.1 请求与响应的结构 ★★★ ​

请求 ✓:

text
GET /api/orders/ORD-1 HTTP/1.1        ← 请求行:方法 + 路径 + 版本
Host: 127.0.0.1:1138                  ← 头:HTTP/1.1 必需
User-Agent: demo
Accept: application/json
                                      ← 空行:头结束
{ "body": "..." }                     ← 体(GET 通常没有)

响应 ✓:

text
HTTP/1.1 200 OK                       ← 状态行:版本 + 状态码 + 原因
Content-Type: application/json; charset=UTF-8
Content-Length: 31                    ← ★ 体有多少字节(必需)
                                      ← 空行
{ "orderNo": "ORD-1" }                ← 体

★ 四个必需或高频的头 ✓✓:

头作用
Host ★★★HTTP/1.1 必需 ——一个 IP 上跑多个站点靠它区分 ✓
Content-Type ★★★这段字节是什么(json / 表单 / 图片)✓
Content-Length ★★★体有多少字节 ——否则对方不知道读到哪算完 ✓
Connection ★keep-alive(复用连接)/ close(用完就断)✓

★ 另外四个"业务相关"的头 ✓:

头用途
Authorization ★★认证凭据(明文 HTTP 下会被直接看到 ✗)
Cookie / Set-Cookie ★★会话保持 ✓
Cache-Control ★缓存策略(max-age / no-store)✓
X-Forwarded-For ★★经过代理时携带真实客户端 IP(但可伪造 ⚠️)

5.2 状态码分组与重试策略 ★★★ ​

分组含义能重试吗
2xx ★成功(200 / 201 创建 / 204 无内容)✓—
3xx ★重定向(301 永久 / 302 临时 / 304 未修改)看情况(客户端自动跟随)
4xx ★★★客户端错(400 参数 / 401 未认证 / 403 无权限 / 404 不存在 / 409 冲突 / 429 限流)不能重试 ✗✗
5xx ★★★服务端错(500 内部错 / 502 网关错 / 503 不可用 / 504 超时)可以重试(带退避)✓

★ 两个容易被忽略的例外 ✓✓:

状态码为什么可以重试
429 Too Many Requests ★★它是"限流",等一会儿就能过 ——但必须按 Retry-After 头等待 ✓
408 Request Timeout客户端超时,可以重试 ✓

★ 重试必须遵守三条 ✓✓:

text
① 只重试"幂等"的操作(GET / PUT / DELETE;POST 通常不幂等)✗
② 必须带"退避"(1s → 2s → 4s),不能立即重试(否则形成重试风暴)✗✗
③ 必须有次数上限 + 熔断(避免一直重试一个已经挂掉的服务)✓

★ 第三条的后果 ✓:重试风暴会把一个"有点慢"的服务直接压死 ✗✗(《Sentinel 限流与熔断》 会展开熔断)

5.3 HTTPS = HTTP + TLS ★★★ ​

本节的实测(自签名证书的真实握手):

text
GET https://127.0.0.1:1141/api/secure
    → 200 {"ssl":true,"msg":"这响应用 TLS 加密过"}
    HTTPS 请求耗时(含 TLS 握手):434 ms

★ HTTPS 只做三件事 ✓✓:

#做的事防的是
① 加密 ★★★中间人看不到内容 ✓窃听 ✓
② 完整性 ★★★内容被改过能发现 ✓篡改 ✓
③ 身份验证 ★★★确认对端真的是它声称的站点(靠证书)✓中间人冒充 ✓✓

★★ 第三条最容易被忽略 ✓✓:

没有身份验证,加密毫无意义 ✗

因为你可以和"中间人"建立一条完美的加密连接,而中间人再和真实站点建立另一条 ✓

你看到的锁头图标是真的,但对面不是你以为的那个人 ✗✗

★ TLS 握手做了什么(简化版) ✓:

text
① 客户端:我要连 example.com(并列出我支持的加密套件)
② 服务端:这是我的证书(含公钥,由 CA 签名)
③ 客户端:验签证书链 → 确认"这个公钥属于 example.com" ✓
④ 双方:用非对称算法协商出"本次会话的对称密钥"
⑤ 之后:全部数据用对称加密(AES)传输 ✓✓

★ 为什么"先非对称、后对称" ✓✓:

非对称加密很慢,但能安全地交换密钥;对称加密很快,适合传数据 ✓

所以用前者"协商",用后者"传输" ——这叫混合加密 ✓✓(《网络安全基础》 有实测)

5.4 证书信任链 ★★★ ​

实测里的关键细节 ✓:

本节的 HTTPS 服务用的是"自签名证书",所以客户端必须显式信任它 ✓

而真实网站不需要你手动信任 ——因为它的证书由受信任的 CA 签发,而 JDK 内置了这些 CA 的根证书 ✓✓

★ 信任链的形态 ✓:

text
根 CA(JDK/浏览器内置)→ 中间 CA → 站点证书(example.com)

★ 三个现实问题 ✓:

问题说明
证书过期 ★★最常见的线上事故之一 ✗ ——到期当天全站不可用 ✗✗
证书域名不匹配 ★★用 IP 访问、或用错的域名 → 校验失败 ✓
自签证书在内网 ⚠️必须把自签 CA 加进客户端的 truststore(否则要"信任所有",那是危险写法)✗

★★ 一条重要的安全提醒 ✓✓:

TrustManager 写成"信任所有证书",等于关掉身份验证 ✗✗

本节的代码里明确标注了"仅演示用,生产绝不要这么写" ✓

因为那会让中间人攻击变得毫无门槛 ✗

★ 证书排障的两个命令 ✓:

bash
# 看证书链与有效期
openssl s_client -connect example.com:443 -showcerts

# 用 Java 自带工具看
keytool -printcert -sslserver example.com:443

5.5 HTTP 版本演进的一句话总结 ★ ​

版本关键改进遗留问题
HTTP/1.0每次请求一个连接 ✗建连开销大
HTTP/1.1 ★★keep-alive 复用连接 ✓队头阻塞(《TCP 与 UDP》)✗
HTTP/2 ★★多路复用(一个连接跑多个请求) ✓、头部压缩TCP 层队头阻塞仍在 ⚠️
HTTP/3 ★★基于 QUIC(UDP) ——缓解队头阻塞、建连更快 ✓✓生态仍在演进 ⚠️

★ 服务端的现实 ✓:HTTP/1.1 仍是主流,HTTP/2 在网关层普遍开启,HTTP/3 多在 CDN 上 ✓

6. 递进示例 ​

6.1 最小示例:正确处理状态码 ​

java
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

switch (response.statusCode() / 100) {
    case 2 -> handleSuccess(response);
    case 4 -> throw new ClientException(response.statusCode(), response.body());  // ✗ 不重试
    case 5 -> { if (attempt < maxAttempts) retryWithBackoff(); }                  // ✓ 可重试
    default -> throw new IllegalStateException("未预期状态码:" + response.statusCode());
}

6.2 业务示例:看头做决策 ​

java
// 按 Retry-After 决定等多久
String retryAfter = response.headers().firstValue("Retry-After").orElse(null);

// 按 Content-Type 决定怎么解析
String contentType = response.headers().firstValue("Content-Type").orElse("");
if (contentType.contains("application/json")) { parseJson(response.body()); }

6.3 反例:只判断 == 200 ​

见第 4 节 ✓

7. 完整可运行代码与实测 ​

代码:labs/cs-basics/src/main/java/cn/kb/learn/cs/net/HttpLesson.java

运行:

powershell
cd labs
.\mvnw.cmd -pl cs-basics compile
java "-Dstdout.encoding=UTF-8" -cp cs-basics/target/classes cn.kb.learn.cs.net.HttpLesson

实测输出(节选):

text
===== 1. HTTP 基础:方法、状态码、头 =====
  GET /api/orders/ORD-1  → 200 {"orderNo":"ORD-1","product":"机械键盘"}
  POST /api/orders       → 201 {"created":true,...}
  GET /api/orders/ORD-999 → 404 {"code":"RESOURCE_NOT_FOUND"}
  DELETE /api/orders/ORD-1 → 405 {"code":"METHOD_NOT_ALLOWED"}

===== 3. HTTPS:自签名证书的真实握手 =====
  GET https://127.0.0.1:1141/api/secure
      → 200 {"ssl":true,"msg":"这响应用 TLS 加密过"}
      HTTPS 请求耗时(含 TLS 握手):434 ms

★ 三条可落地的结论 ✓:

#结论依据
①按 4xx/5xx 分流重试策略4xx 重试无用,5xx 才值得 ✓
②重试必须带退避与上限否则放大故障 ✗
③凭据必须走 HTTPS;不要信任所有证书TLS 的第三件事是身份验证 ✓

8. Debug 观察点 ​

  1. 用 curl -v 观察完整报文:对比自己代码发出的头 ★;
  2. 用 curl -w 拆分 HTTPS 各阶段耗时:观察 TLS 握手占多少 ★★;
  3. 用 keytool -printcert -sslserver 看真实证书链:观察有效期与签发者 ✓;
  4. 把自签名证书的有效期改短(-validity 1)并等到过期:观察客户端报错 ⚠️;
  5. 关掉"信任所有"的 TrustManager:观察客户端拒绝自签名证书 ✓(这正是真实行为)。

9. 常见错误 ​

#错误说明
1只判断 == 200 ✗✗忽略 201/204 与错误分流(5.2)✗
2对 4xx 重试 ✗✗白重试并放大故障 ✓
3重试不带退避 ✗✗重试风暴 ✓
4对非幂等的 POST 盲目重试 ✗✗可能重复下单 ✓
5TrustManager 信任所有证书 ✗✗等于关掉身份验证 ✓✓
6忽略证书过期 ✗✗到期当天全站不可用 ✓
7把 X-Forwarded-For 当真 ✗可伪造,必须只信任已知代理设置的值 ✓

10. 动手实验 ​

目标:把"状态码分流"与"TLS 的三件事"变成实操。

步骤:

  1. 跑 HttpLesson,记录 200/201/404/405 与 HTTPS 的耗时 ✓;
  2. 给服务端加一个 500 的端点,在客户端实现"带退避的重试"(1s、2s、4s)★★;
  3. 再加一个 400 的端点,验证客户端"不重试" ✓;
  4. 用 keytool 生成一个新证书(不同 CN),观察客户端校验失败 ★;
  5. 用 curl -w 对比 HTTP 与 HTTPS 的阶段耗时 ✓。

验收方式:能说出"哪些状态码该重试、退避策略是什么"。

扩展任务:做一个"降级开关":

  • 当某个下游连续 5 次 5xx 时,直接返回兜底数据(不再调用)✓;
  • 60 秒后放一个探测请求,成功了就恢复 ✓;
  • 说明:这就是"熔断器"的最小实现 ★★

11. 练习题 ​

  1. 判断:所有失败都可以通过重试解决。(对 / 错)
  2. 填空:4xx 是 ______ 错,5xx 是 ______ 错;HTTPS 做的三件事是 、、______。
  3. 简答:为什么"加密"必须配合"身份验证"才有意义?
  4. (代码阅读)
java
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{trustAllManager}, new SecureRandom());

这段代码有什么风险? 5. (编程)实现一个带退避的重试器:只对 5xx 重试,最多 3 次,间隔 1s/2s/4s。

参考答案(要点)
  1. 错。4xx 重试无用(参数错、无权限)✗ ——只有 5xx 与 429 值得重试 ✓★
  2. 客户端;服务端;加密、完整性、身份验证 ✓
  3. 因为没有身份验证时,你可能在和中间人建立加密连接 ✗ ——你以为的"安全通道"通向攻击者 ✓✓
  4. 它信任所有证书 → 完全失去防中间人能力 ✗✗ ——生产环境绝不能用 ✓
  5. 要点:判断状态码 → 只对 5xx → 指数退避 → 次数上限 ✓ 另外要只对幂等操作重试,并考虑加"熔断" ✓

12. 掌握标准 ​

  • [ ] 能写出请求/响应结构,并说出四个必需的头 ★★★;
  • [ ] 能按 4xx/5xx 分流重试,且知道 429/408 的例外 ★★★;
  • [ ] 知道重试必须带退避、上限、熔断 ★★★;
  • [ ] 能说清 HTTPS 的三件事,尤其"身份验证" ★★★;
  • [ ] 知道证书信任链与"自签名证书要手动信任"的原因 ★★★;
  • [ ] 知道"信任所有证书"的危险 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

《DNS、IP、端口与 ARP》:请求发出之前,还要先"找到对方"。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引