【网络基础】HTTP/HTTPS 原理图解
用图解的方式深入理解 HTTP 协议的核心概念、请求响应流程、状态码含义,以及 HTTPS 如何保证安全通信。
【网络基础】HTTP/HTTPS 原理图解
「为什么我的网站要加 HTTPS?」 「HTTP 1.1 和 HTTP 2 有什么区别?」 「304 状态码是什么意思?」
作为前端开发者,我们每天都在和 HTTP 打交道,但很多人只知道 fetch 和 axios 怎么用,却不知道底层发生了什么。
今天小明用大量图解,带你彻底搞懂 HTTP 和 HTTPS。
HTTP 是什么?
一句话定义
HTTP(HyperText Transfer Protocol)是超文本传输协议,用于在客户端和服务器之间传输数据。
你在浏览器输入网址,按下回车,浏览器就会通过 HTTP 协议向服务器「要东西」。服务器收到请求后,把「东西」(HTML、图片、JSON 等)发回来。
┌─────────────┐ ┌─────────────┐
│ 浏览器 │ ───── HTTP 请求 ────▶│ 服务器 │
│ (客户端) │ │ │
│ │◀──── HTTP 响应 ─────│ │
└─────────────┘ └─────────────┘
HTTP 的特点
- 基于 TCP:HTTP 建立在 TCP 连接之上,保证数据可靠传输
- 无状态:每个请求都是独立的,服务器不记得「你是谁」
- 请求-响应模式:客户端发请求,服务器返响应
- 灵活:可以传输任何类型的数据(HTML、JSON、图片、视频...)
HTTP 请求详解
请求的组成
一个 HTTP 请求由三部分组成:
┌────────────────────────────────────────┐
│ 请求行 (Request Line) │
│ GET /api/users HTTP/1.1 │
├────────────────────────────────────────┤
│ 请求头 (Headers) │
│ Host: api.example.com │
│ Content-Type: application/json │
│ Authorization: Bearer xxx │
├────────────────────────────────────────┤
│ 请求体 (Body) │
│ {"name": "小明", "age": 18} │
└────────────────────────────────────────┘
请求方法
| 方法 | 用途 | 幂等性 | 请求体 |
|---|---|---|---|
| GET | 获取资源 | ✅ 是 | 通常无 |
| POST | 创建资源 | ❌ 否 | 有 |
| PUT | 更新资源(全量) | ✅ 是 | 有 |
| PATCH | 更新资源(部分) | ❌ 否 | 有 |
| DELETE | 删除资源 | ✅ 是 | 通常无 |
| HEAD | 获取响应头(不要 body) | ✅ 是 | 无 |
| OPTIONS | 获取支持的方法(预检请求) | ✅ 是 | 无 |
幂等性:多次请求的效果和一次请求相同。GET 获取 10 次数据,数据不会变;POST 提交 10 次,可能创建 10 条记录。
常见请求头
// 发起一个请求
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
// 告诉服务器请求体的格式
'Content-Type': 'application/json',
// 告诉服务器期望的响应格式
'Accept': 'application/json',
// 身份认证
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
// 自定义请求头
'X-Request-ID': '123456',
},
body: JSON.stringify({ name: '小明' }),
});
| 请求头 | 作用 |
|---|---|
Host | 目标主机名 |
Content-Type | 请求体的格式 |
Accept | 期望的响应格式 |
Authorization | 身份认证信息 |
Cookie | 携带的 Cookie |
User-Agent | 客户端信息 |
Referer | 请求来源页面 |
Cache-Control | 缓存控制 |
HTTP 响应详解
响应的组成
┌────────────────────────────────────────┐
│ 状态行 (Status Line) │
│ HTTP/1.1 200 OK │
├────────────────────────────────────────┤
│ 响应头 (Headers) │
│ Content-Type: application/json │
│ Content-Length: 1234 │
│ Set-Cookie: session=abc123 │
├────────────────────────────────────────┤
│ 响应体 (Body) │
│ {"id": 1, "name": "小明"} │
└────────────────────────────────────────┘
状态码:服务器在「说话」
状态码是服务器告诉客户端「这次请求怎么样了」。
状态码分类:
1xx - 信息性状态码(请求还在处理中)
2xx - 成功状态码(请求成功)
3xx - 重定向状态码(需要进一步操作)
4xx - 客户端错误(你的问题)
5xx - 服务器错误(我的问题)
常见状态码
2xx 成功:
| 状态码 | 含义 | 场景 |
|---|---|---|
| 200 | OK,成功 | 最常见的成功响应 |
| 201 | Created,已创建 | POST 创建资源成功 |
| 204 | No Content,无内容 | DELETE 成功,不返回内容 |
3xx 重定向:
| 状态码 | 含义 | 场景 |
|---|---|---|
| 301 | 永久重定向 | 网址永久更换 |
| 302 | 临时重定向 | 临时跳转 |
| 304 | Not Modified | 缓存有效,直接用缓存 |
4xx 客户端错误:
| 状态码 | 含义 | 场景 |
|---|---|---|
| 400 | Bad Request | 请求参数有问题 |
| 401 | Unauthorized | 未登录/Token 过期 |
| 403 | Forbidden | 没有权限 |
| 404 | Not Found | 资源不存在 |
| 405 | Method Not Allowed | 请求方法不支持 |
| 429 | Too Many Requests | 请求太频繁 |
5xx 服务器错误:
| 状态码 | 含义 | 场景 |
|---|---|---|
| 500 | Internal Server Error | 服务器内部错误 |
| 502 | Bad Gateway | 网关/代理错误 |
| 503 | Service Unavailable | 服务暂不可用 |
| 504 | Gateway Timeout | 网关超时 |
小明冷笑话时间:
404 和 500 有什么区别? 404 是「你要的东西我没有」 500 是「你要的东西我有,但我找不到了」
HTTP 版本演进
HTTP/1.0:一次一个
客户端 服务器
│ │
│──── TCP 连接 ────────────────▶│
│──── HTTP 请求 ───────────────▶│
│◀─── HTTP 响应 ────────────────│
│◀─── 关闭连接 ─────────────────│
│ │
│──── TCP 连接(再来一次)───────▶│
│──── HTTP 请求 ───────────────▶│
│◀─── HTTP 响应 ────────────────│
│◀─── 关闭连接 ─────────────────│
问题:每个请求都要新建 TCP 连接,开销很大。
HTTP/1.1:连接复用
客户端 服务器
│ │
│──── TCP 连接 ────────────────▶│
│──── HTTP 请求 1 ─────────────▶│
│◀─── HTTP 响应 1 ──────────────│
│──── HTTP 请求 2 ─────────────▶│ 同一个连接
│◀─── HTTP 响应 2 ──────────────│ 可以发多个请求
│──── HTTP 请求 3 ─────────────▶│
│◀─── HTTP 响应 3 ──────────────│
│ │
改进:
- Keep-Alive 连接复用
- 支持管道化(pipelining)
问题:队头阻塞(Head-of-Line Blocking)——请求必须按顺序响应。
HTTP/2:多路复用
客户端 服务器
│ │
│──── TCP 连接 ────────────────▶│
│ │
│ Stream 1: 请求 HTML │
│ Stream 2: 请求 CSS ────▶│ 同时发送
│ Stream 3: 请求 JS │ 不用排队
│ │
│◀──── Stream 2: 响应 CSS │
│◀──── Stream 3: 响应 JS │ 哪个好了
│◀──── Stream 1: 响应 HTML │ 先返回哪个
改进:
- 多路复用:一个连接上并行发多个请求/响应
- 头部压缩:减少重复传输的头部信息
- 服务器推送:服务器可以主动推送资源
- 二进制传输:更高效
HTTP/3:基于 QUIC
HTTP/3 不再使用 TCP,而是使用基于 UDP 的 QUIC 协议。
HTTP/1.1 & HTTP/2 HTTP/3
│ │
TCP QUIC
│ │
IP UDP
│
IP
改进:
- 解决 TCP 的队头阻塞问题
- 连接建立更快(0-RTT)
- 连接迁移(WiFi 切 4G 不断开)
HTTPS:加个锁
HTTP 的问题
HTTP 是明文传输的,这意味着:
你:密码是 123456
│
▼
┌───────┐
│ 中间人 │ 哈哈,我看到了
└───────┘
│
▼
服务器:收到密码 123456
任何在网络链路上的人(路由器、WiFi 热点、运营商)都能看到你传输的内容。
HTTPS = HTTP + TLS
HTTPS 在 HTTP 和 TCP 之间加了一层 TLS(Transport Layer Security) 加密。
HTTP HTTPS
│ │
│ TLS (加密)
│ │
TCP TCP
│ │
IP IP
TLS 握手过程(简化版)
客户端 服务器
│ │
│────── 1. Client Hello ───────────────▶│
│ (支持的加密算法列表) │
│ │
│◀───── 2. Server Hello ────────────────│
│ (选择的加密算法 + 证书) │
│ │
│────── 3. 验证证书 ────────────────────│
│ 生成对称密钥 │
│ 用服务器公钥加密发送 │
│ │
│◀───── 4. 服务器用私钥解密 ─────────────│
│ 得到对称密钥 │
│ │
│======= 使用对称密钥加密通信 ============│
核心点:
- 用非对称加密(公钥/私钥)安全地交换对称密钥
- 后续通信用对称加密(更快)
证书是什么?
证书是由**权威机构(CA)**颁发的「身份证」。
┌─────────────────────────────────────────┐
│ SSL/TLS 证书 │
├─────────────────────────────────────────┤
│ 域名:www.example.com │
│ 颁发机构:DigiCert │
│ 有效期:2024-01-01 ~ 2025-01-01 │
│ 公钥:MIIBIjANBgkqhkiG9w0BAQEFAA... │
│ 签名:由 CA 的私钥签名 │
└─────────────────────────────────────────┘
浏览器内置了受信任的 CA 列表。当收到证书时,浏览器会:
- 检查证书是否由受信任的 CA 签发
- 检查证书是否过期
- 检查证书的域名是否匹配
如果验证失败,浏览器会显示警告⚠️。
为什么要用 HTTPS?
| HTTP | HTTPS |
|---|---|
| 明文传输,可被窃听 | 加密传输,无法窃听 |
| 可被篡改 | 完整性校验,无法篡改 |
| 无法验证身份 | 证书验证身份 |
| 不安全 | 安全 |
现在 HTTPS 已经是标配了。Chrome 会把 HTTP 网站标记为「不安全」,Google 搜索也会优先收录 HTTPS 网站。
缓存机制
为什么需要缓存?
没有缓存:
用户 ──请求──▶ 服务器 ──响应──▶ 用户 (每次都要等)
有缓存:
用户 ──请求──▶ 本地缓存 ──命中──▶ 用户 (瞬间返回)
缓存可以:
- 减少服务器压力
- 减少网络传输
- 提高加载速度
强缓存 vs 协商缓存
强缓存:直接用缓存,不问服务器
浏览器 服务器
│ │
│ 检查本地缓存 │
│ Cache-Control: max-age=3600 │
│ (未过期) │
│ │
│ 直接使用缓存,不发请求 │
│ 状态码:200 (from cache) │
相关响应头:
Cache-Control: max-age=3600(缓存 3600 秒)Expires: Wed, 21 Oct 2024 07:28:00 GMT(过期时间)
协商缓存:问服务器「缓存还能用吗?」
浏览器 服务器
│ │
│ 本地缓存过期了 │
│──── If-Modified-Since: xxx ──────▶│
│ 或 If-None-Match: xxx │
│ │
│◀─── 304 Not Modified ─────────────│
│ (缓存还能用) │
│ │
│ 使用本地缓存 │
相关头部:
Last-Modified/If-Modified-Since:基于修改时间ETag/If-None-Match:基于内容哈希
缓存策略建议
// 静态资源(JS/CSS/图片):强缓存 + 文件名哈希
// Cache-Control: max-age=31536000 (一年)
// 文件名:bundle.abc123.js
// HTML 文件:协商缓存或不缓存
// Cache-Control: no-cache
// 每次都问服务器
// API 响应:通常不缓存或短时间缓存
// Cache-Control: no-store 或 max-age=60
CORS:跨域的那些事
什么是跨域?
浏览器的同源策略规定:只有协议、域名、端口都相同才算同源。
http://example.com/page.html 发起请求到:
✅ http://example.com/api (同源)
❌ https://example.com/api (协议不同)
❌ http://api.example.com/api (域名不同)
❌ http://example.com:8080/api (端口不同)
不同源的请求会被浏览器拦截。
CORS 解决方案
服务器通过响应头告诉浏览器「允许跨域」:
// 服务器响应头
Access-Control-Allow-Origin: https://your-site.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
简单请求 vs 预检请求
简单请求:直接发送
满足条件:
- 方法是 GET/HEAD/POST
- 没有自定义请求头
- Content-Type 是特定值
预检请求:先发 OPTIONS 请求
浏览器 服务器
│ │
│──── OPTIONS /api/users ──────────▶│
│ (预检请求) │
│ │
│◀─── 200 OK ───────────────────────│
│ Access-Control-Allow-XXX │
│ │
│──── POST /api/users ─────────────▶│
│ (实际请求) │
│ │
实战:分析一次完整的请求
打开浏览器开发者工具,访问一个网页,来看看实际发生了什么。
1. DNS 解析
浏览器先把域名转换成 IP 地址。
www.example.com → 93.184.216.34
2. TCP 连接
与服务器建立 TCP 连接(三次握手)。
3. TLS 握手(如果是 HTTPS)
建立安全连接,交换密钥。
4. 发送 HTTP 请求
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 ...
Accept: text/html
Accept-Encoding: gzip, deflate, br
5. 服务器处理
服务器收到请求,处理后返回响应。
6. 接收响应
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Cache-Control: max-age=3600
<!DOCTYPE html>
<html>...
7. 渲染页面
浏览器解析 HTML,遇到 CSS/JS/图片再发起新请求。
总结
HTTP 核心知识点:
- 请求方法:GET 获取、POST 创建、PUT 更新、DELETE 删除
- 状态码:2xx 成功、3xx 重定向、4xx 客户端错误、5xx 服务器错误
- 版本演进:HTTP/1.1 连接复用 → HTTP/2 多路复用 → HTTP/3 基于 QUIC
- HTTPS:HTTP + TLS 加密,保证安全
- 缓存:强缓存直接用,协商缓存问一下
- CORS:跨域资源共享,需要服务器配合
记住:
HTTP 是前端和后端沟通的语言。学好 HTTP,调接口不再抓瞎。
小明冷笑话收尾:
问:HTTP 和 HTTPS 有什么区别? 答:差一个 S。 这个 S 代表 Secure(安全),也代表 Slow(稍微慢一点,因为要加密)。 但为了安全,这点性能损失完全值得。
问:为什么要有 OPTIONS 预检请求? 答:因为浏览器想问服务器:「我要发个复杂请求,你愿意接吗?」 这是浏览器的礼貌,也是安全的保障。
「理解 HTTP,就理解了 Web 的一半。」—— 小明