主题
HTTP / HTTPS
HTTP 协议基础
HTTP(HyperText Transfer Protocol)是无状态的应用层协议,基于请求-响应模型:
客户端(浏览器) 服务器
| |
| ── HTTP 请求 ──────────────────────→ |
| GET /api/users HTTP/1.1 |
| Host: example.com |
| Accept: application/json |
| |
| ←────────────────── HTTP 响应 ── |
| HTTP/1.1 200 OK |
| Content-Type: application/json |
| { "users": [...] } |请求方法语义
| 方法 | 语义 | 幂等 | 安全 | 请求体 | 典型用途 |
|---|---|---|---|---|---|
| GET | 获取资源 | ✅ | ✅ | ❌ | 查询数据 |
| POST | 创建资源/提交数据 | ❌ | ❌ | ✅ | 表单提交、创建记录 |
| PUT | 全量替换资源 | ✅ | ❌ | ✅ | 更新完整记录 |
| PATCH | 部分更新资源 | ❌ | ❌ | ✅ | 更新部分字段 |
| DELETE | 删除资源 | ✅ | ❌ | ⚠️ | 删除记录 |
| HEAD | 获取响应头(无 body) | ✅ | ✅ | ❌ | 检查资源是否存在 |
| OPTIONS | 获取支持的方法 | ✅ | ✅ | ❌ | CORS 预检请求 |
幂等(Idempotent):多次执行和一次执行效果相同
安全(Safe):不修改服务器状态HTTP 状态码
分类
1xx 信息性 请求已接收,继续处理
2xx 成功 请求已成功处理
3xx 重定向 需要进一步操作才能完成
4xx 客户端错误 请求有问题
5xx 服务器错误 服务器处理出错高频状态码详解
200 OK 请求成功,返回数据
201 Created 资源创建成功(POST 响应)
204 No Content 成功但无响应体(DELETE 响应)
301 Moved Permanently 永久重定向(搜索引擎更新 URL)
302 Found 临时重定向(保持原始方法不变——但实际浏览器会改为 GET)
303 See Other 临时重定向(明确要求用 GET)
304 Not Modified 资源未修改,使用缓存
307 Temporary Redirect 临时重定向(严格保持原始方法)
308 Permanent Redirect 永久重定向(严格保持原始方法)
400 Bad Request 请求格式错误
401 Unauthorized 未认证(需要登录)
403 Forbidden 已认证但无权限
404 Not Found 资源不存在
405 Method Not Allowed 请求方法不被允许
409 Conflict 资源冲突(如并发更新)
422 Unprocessable Entity 请求格式正确但语义错误
429 Too Many Requests 请求频率超限
500 Internal Server Error 服务器内部错误
502 Bad Gateway 网关/代理收到无效响应
503 Service Unavailable 服务不可用(维护/过载)
504 Gateway Timeout 网关/代理等待上游超时301 vs 302 vs 307 vs 308
永久重定向 临时重定向
─────────── ──────────
方法可能改变 301 302
(POST → GET)
方法严格不变 308 307
实践建议:
- 域名迁移 → 301
- HTTPS 强制跳转 → 301
- 临时维护跳转 → 307
- 表单提交后跳转 → 303(明确用 GET)HTTP 缓存
HTTP 缓存分为两层:强缓存和协商缓存。
浏览器发起请求
↓
检查强缓存(Cache-Control / Expires)
├─ 命中 → 直接使用缓存(200 from cache),不发请求
└─ 未命中 ↓
发起请求,携带协商字段
↓
服务器检查资源是否变化
├─ 未变化 → 304 Not Modified(使用缓存)
└─ 已变化 → 200 OK(返回新资源)强缓存
浏览器不与服务器通信,直接使用本地缓存:
Cache-Control 头(HTTP/1.1,优先级高):
────────────────────────────────────────
Cache-Control: max-age=31536000 一年内直接用缓存
Cache-Control: no-cache 每次都要协商验证(不是不缓存!)
Cache-Control: no-store 完全不缓存(敏感数据用这个)
Cache-Control: private 只有浏览器可以缓存(不允许 CDN)
Cache-Control: public 允许 CDN 等中间缓存
Cache-Control: s-maxage=600 CDN 缓存时间(覆盖 max-age)
Cache-Control: immutable 资源永不变(即使用户刷新也不验证)
Cache-Control: stale-while-revalidate=60
缓存过期后 60s 内先返回旧缓存,后台重新验证
Expires 头(HTTP/1.0,优先级低):
────────────────────────────────
Expires: Thu, 01 Jan 2026 00:00:00 GMT
缺点:依赖客户端时间,可能不准确协商缓存
强缓存过期后,浏览器携带条件请求头询问服务器资源是否变化:
方案一:Last-Modified / If-Modified-Since(基于修改时间)
首次请求:
响应头:Last-Modified: Tue, 15 Oct 2024 10:30:00 GMT
再次请求:
请求头:If-Modified-Since: Tue, 15 Oct 2024 10:30:00 GMT
服务器比较修改时间:
未变化 → 304 Not Modified
已变化 → 200 OK + 新资源
缺点:
- 精度只到秒(1 秒内多次修改检测不到)
- 文件内容没变但修改时间变了(如重新部署)也会失效
方案二:ETag / If-None-Match(基于内容哈希,优先级更高)
首次请求:
响应头:ETag: "abc123def456"
再次请求:
请求头:If-None-Match: "abc123def456"
服务器比较 ETag:
匹配 → 304 Not Modified
不匹配 → 200 OK + 新资源 + 新 ETag
优点:精确到内容级别
缺点:服务器需要计算哈希,有一定开销实际缓存策略
不同资源的缓存策略:
HTML 文件(入口文件):
Cache-Control: no-cache
ETag: "..."
→ 每次都协商验证,确保获取最新版本
带 hash 的静态资源(JS/CSS/图片):
Cache-Control: max-age=31536000, immutable
→ 文件名包含内容哈希(如 app.a1b2c3.js)
→ 内容变化 = 文件名变化 = 新 URL
→ 可以长期缓存
API 响应:
Cache-Control: no-store
→ 动态数据不缓存
或
Cache-Control: max-age=60, stale-while-revalidate=300
→ 对实时性要求不高的 API 可短期缓存缓存更新流程:
index.html ← no-cache(每次验证)
引用
├─ app.a1b2c3.js ← immutable(长期缓存)
├─ style.d4e5f6.css ← immutable(长期缓存)
└─ logo.g7h8i9.svg ← immutable(长期缓存)
代码更新后:
index.html ← 服务器返回新版本
引用
├─ app.x1y2z3.js ← 新 hash = 新 URL = 重新下载
├─ style.d4e5f6.css ← hash 没变 = 继续用缓存
└─ logo.g7h8i9.svg ← hash 没变 = 继续用缓存CORS(跨源资源共享)
同源策略
浏览器的**同源策略(Same-Origin Policy)**限制不同源的文档或脚本之间的交互:
同源的定义:协议 + 域名 + 端口 完全相同
https://example.com/page1
https://example.com/page2 ✅ 同源
http://example.com/page1 ❌ 协议不同
https://api.example.com/data ❌ 域名不同(子域名也不同源)
https://example.com:3000/api ❌ 端口不同
同源策略限制的行为:
✅ 允许:<script>、<img>、<link>、<iframe> 加载跨域资源
❌ 禁止:JS 读取跨域请求的响应
❌ 禁止:JS 读取跨域 iframe 的 DOM
❌ 禁止:JS 读取跨域 Cookie / StorageCORS 机制
CORS 是浏览器和服务器之间的协议——服务器通过响应头告诉浏览器"允许哪些源访问":
简单请求(Simple Request):
条件(同时满足):
- 方法为 GET / HEAD / POST
- Header 只有 Accept / Accept-Language / Content-Language /
Content-Type(仅 text/plain / multipart/form-data / application/x-www-form-urlencoded)
- 没有自定义 Header
流程:
浏览器 ─→ 服务器
GET /api/data
Origin: https://app.example.com
服务器 ─→ 浏览器
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
(浏览器检查 Origin 是否在允许列表中)预检请求(Preflight Request):
条件(不满足简单请求条件时触发):
- 使用 PUT / DELETE / PATCH 等方法
- 发送 Content-Type: application/json
- 携带自定义 Header(如 Authorization)
流程:
步骤 1:浏览器先发 OPTIONS 预检
OPTIONS /api/users
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
步骤 2:服务器响应允许的操作
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400 ← 预检结果缓存 24 小时
步骤 3:预检通过后,发送实际请求
POST /api/users
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer xxxCORS 响应头详解
Access-Control-Allow-Origin: https://app.example.com
允许的源(不能同时设为 * 和使用 credentials)
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
允许的 HTTP 方法
Access-Control-Allow-Headers: Content-Type, Authorization
允许的请求头
Access-Control-Expose-Headers: X-Total-Count, X-Request-Id
允许 JS 读取的响应头(默认只能读取简单响应头)
Access-Control-Allow-Credentials: true
允许携带 Cookie(前端 fetch 也要设 credentials: 'include')
Access-Control-Max-Age: 86400
预检请求缓存时间(秒),减少 OPTIONS 请求次数携带 Cookie 的跨域请求
js
// 前端
fetch('https://api.example.com/data', {
credentials: 'include', // 必须
});
// 服务器响应头
Access-Control-Allow-Origin: https://app.example.com // 不能是 *
Access-Control-Allow-Credentials: true // 必须
// 注意:Chrome 80+ 默认 SameSite=Lax
// 跨站请求不会自动携带 Cookie
// 需要 Cookie 设置 SameSite=None; SecureCookie
Cookie 属性
Set-Cookie: session_id=abc123;
Domain=.example.com; 可以被哪些域名访问
Path=/; 可以被哪些路径访问
Expires=Thu, 01 Jan 2026; 过期时间(绝对时间)
Max-Age=86400; 过期时间(相对秒数,优先级高于 Expires)
HttpOnly; JS 不可读取(防 XSS 窃取)
Secure; 仅 HTTPS 传输
SameSite=Lax; 跨站请求限制SameSite 属性
SameSite=Strict
完全禁止跨站携带 Cookie
从 B 站跳转到 A 站 → 不带 A 站的 Cookie(用户需重新登录)
SameSite=Lax(默认值,Chrome 80+)
允许顶级导航(链接点击、表单 GET)携带 Cookie
禁止第三方请求(fetch、POST 表单、iframe)携带
→ 安全和体验的平衡
SameSite=None
允许所有跨站请求携带(必须同时设置 Secure)
→ 第三方登录、支付回调等场景必须
对比:
场景 Strict Lax(默认) None
─────────────────── ────── ───────── ────
顶级导航链接跳转 ❌ ✅ ✅
<img src="跨站"> ❌ ❌ ✅
fetch/XHR 跨站请求 ❌ ❌ ✅
<form method="GET"> ❌ ✅ ✅
<form method="POST"> ❌ ❌ ✅
<iframe> ❌ ❌ ✅HTTPS
HTTP 的安全问题
HTTP 明文传输的三大风险:
1. 窃听(Eavesdropping)
中间人可以看到所有传输内容(密码、Token)
2. 篡改(Tampering)
中间人可以修改传输内容(注入广告、劫持页面)
3. 冒充(Impersonation)
无法验证服务器身份(钓鱼网站)TLS 握手流程
HTTPS = HTTP + TLS(Transport Layer Security)
TCP 三次握手完成后:
TLS 1.2 握手(2-RTT):
────────────────────
客户端 服务器
|── ClientHello ──────────────────→ | (1) 支持的加密套件列表 + 随机数A
| |
|←── ServerHello ───────────────── | (2) 选定的加密套件 + 随机数B
|←── Certificate ───────────────── | (3) 服务器证书(含公钥)
|←── ServerHelloDone ──────────── |
| |
|── ClientKeyExchange ───────────→ | (4) 用公钥加密的 Pre-Master Secret
|── ChangeCipherSpec ────────────→ | (5) 后续使用协商的密钥加密
|── Finished ────────────────────→ |
| |
|←── ChangeCipherSpec ─────────── | (6) 双方使用 随机数A + 随机数B +
|←── Finished ─────────────────── | Pre-Master Secret 生成对称密钥
| |
|════ 对称加密通信 ═══════════════ |
TLS 1.3 握手(1-RTT,更快更安全):
────────────────────────────────
客户端 服务器
|── ClientHello + KeyShare ──────→ | (1) 一次性发送:支持的加密套件 +
| | DH 公钥 + 随机数
|←── ServerHello + KeyShare ───── | (2) 选定的加密套件 + DH 公钥
|←── Certificate ──────────────── | (3) 证书
|←── Finished ─────────────────── |
| |
|── Finished ────────────────────→ | (4) 完成,开始加密通信证书验证
证书信任链:
Root CA(根证书,预装在操作系统/浏览器中)
└─ 签发 → Intermediate CA(中间证书)
└─ 签发 → example.com 的证书(叶子证书)
验证流程:
1. 浏览器收到 example.com 的证书
2. 检查证书是否在有效期内
3. 检查证书域名是否匹配
4. 用 Intermediate CA 的公钥验证叶子证书的签名
5. 用 Root CA 的公钥验证 Intermediate CA 的签名
6. Root CA 在本地信任列表中 → 验证通过HTTP 头部详解
请求头
常用请求头:
Host: example.com 目标主机(HTTP/1.1 必须)
Origin: https://app.example.com 请求源(CORS/CSRF 防护)
Referer: https://google.com/search 来源页面 URL
User-Agent: Mozilla/5.0 ... 客户端标识
Accept: application/json 期望的响应格式
Accept-Language: zh-CN,en;q=0.9 期望的语言
Accept-Encoding: gzip, br 支持的压缩算法
Authorization: Bearer <token> 认证凭证
Cookie: session_id=abc123 Cookie
If-Modified-Since: Tue, 15 Oct... 协商缓存(时间)
If-None-Match: "etag-value" 协商缓存(ETag)
Content-Type: application/json 请求体格式
Content-Length: 256 请求体长度响应头
常用响应头:
Content-Type: application/json; charset=utf-8 响应体格式
Content-Length: 1024 响应体长度
Content-Encoding: gzip 压缩算法
Cache-Control: max-age=31536000 缓存控制
ETag: "abc123" 资源指纹
Last-Modified: Tue, 15 Oct 2024 10:30:00 GMT 修改时间
Set-Cookie: id=abc; HttpOnly; Secure 设置 Cookie
Access-Control-Allow-Origin: * CORS 允许源
Access-Control-Allow-Methods: GET,POST CORS 允许方法
X-Content-Type-Options: nosniff 禁止 MIME 嗅探
X-Frame-Options: DENY 禁止被 iframe 嵌入
Strict-Transport-Security: max-age=31536000 强制 HTTPS(HSTS)
Content-Security-Policy: default-src 'self' CSP 安全策略内容协商
浏览器和服务器通过 Header 协商最合适的资源格式:
请求:
Accept: text/html, application/json;q=0.9, */*;q=0.8
Accept-Language: zh-CN, en;q=0.9
Accept-Encoding: gzip, br
响应:
Content-Type: text/html; charset=utf-8
Content-Language: zh-CN
Content-Encoding: br
q 值(Quality Value):0-1 的权重,默认 1,越高越优先压缩
常见压缩算法对比:
算法 压缩率 速度 浏览器支持
───── ────── ────── ──────────
gzip 好 快 所有浏览器
br(Brotli) 更好 稍慢 所有现代浏览器
zstd 最好 快 Chrome 123+
配置示例(Nginx):
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1024;
brotli on;
brotli_types text/plain text/css application/json application/javascript;TCP 连接管理
HTTP/1.1 的连接策略
Keep-Alive(HTTP/1.1 默认开启):
HTTP/1.0:每个请求一个 TCP 连接(开销大)
请求1: TCP 握手 → 请求 → 响应 → 关闭
请求2: TCP 握手 → 请求 → 响应 → 关闭
HTTP/1.1 Keep-Alive:复用 TCP 连接
TCP 握手 → 请求1 → 响应1 → 请求2 → 响应2 → ... → 关闭
但仍有队头阻塞(Head-of-Line Blocking):
同一连接上的请求必须排队,前一个响应完才能发下一个
浏览器的应对策略:
对同一域名开 6 个并行 TCP 连接(Chrome)
所以经典优化手段是"域名分片"(domain sharding)
→ 将资源分散到 cdn1.example.com、cdn2.example.com
→ HTTP/2 时代已不需要面试高频题
1. 强缓存和协商缓存的区别?缓存的完整判断流程是什么?
强缓存不与服务器通信,直接使用本地缓存(状态码 200 from cache);协商缓存需要向服务器发请求验证资源是否变化(304 Not Modified 或 200 OK)。完整流程:①浏览器先检查 Cache-Control / Expires,如果未过期则命中强缓存直接使用;②如果过期,携带 If-None-Match(ETag)和/或 If-Modified-Since(Last-Modified)发请求到服务器;③服务器优先比较 ETag(精确到内容),其次比较 Last-Modified(精确到秒);④资源未变返回 304,已变返回 200 + 新资源。实际项目中,HTML 用 no-cache(每次协商),带 hash 的静态资源用 max-age=31536000, immutable(长期强缓存)。
2. CORS 是什么?简单请求和预检请求的区别?
CORS 是浏览器实现的跨域资源共享机制——允许服务器通过响应头声明"哪些源可以访问"。简单请求(GET/HEAD/POST + 简单 Header + 简单 Content-Type)浏览器直接发送并在响应中检查 Access-Control-Allow-Origin。非简单请求(如 PUT/DELETE、Content-Type: application/json、自定义 Header)浏览器会先发一个 OPTIONS 预检请求,询问服务器是否允许该跨域操作,通过后才发实际请求。预检结果可通过 Access-Control-Max-Age 缓存。携带 Cookie 时必须设 credentials: 'include'、Allow-Origin 不能为 *、且需要 Allow-Credentials: true。
3. Cookie 的 SameSite 属性有哪些值?各自的行为是什么?
三个值:①Strict:完全禁止跨站携带 Cookie,最安全但影响体验(从搜索引擎跳转过来需要重新登录);②Lax(Chrome 80+ 默认):只允许顶级导航(链接点击、GET 表单提交)携带 Cookie,禁止第三方请求(fetch/POST 表单/iframe)携带——在安全性和可用性间取平衡;③None:允许所有跨站请求携带,但必须同时设置 Secure(仅 HTTPS),用于第三方登录、支付回调等场景。Lax 是默认值,这直接影响了跨域 API 请求的 Cookie 携带行为。
4. HTTPS 相比 HTTP 做了什么?TLS 握手的大致流程?
HTTPS 在 HTTP 基础上增加了 TLS 层,解决三个问题:①加密——防止窃听(对称加密保护数据);②完整性——防止篡改(MAC 消息认证码);③身份认证——防止冒充(证书 + 数字签名)。TLS 1.2 握手流程:ClientHello(支持的加密套件 + 随机数A)→ ServerHello(选定套件 + 随机数B + 证书)→ 客户端验证证书后用公钥加密 Pre-Master Secret → 双方用三个随机数生成对称密钥 → 后续用对称密钥加密通信。TLS 1.3 优化为 1-RTT(Client 在 Hello 中直接发送 DH 公钥),减少握手延迟。
5. 实际项目中如何设计 HTTP 缓存策略?
核心原则是"入口文件协商缓存 + 静态资源长期强缓存"。具体:①HTML 文件设置 Cache-Control: no-cache,每次访问都向服务器验证是否有更新(配合 ETag);②JS/CSS/图片等静态资源,文件名包含内容哈希(如 app.a1b2c3.js),设置 Cache-Control: max-age=31536000, immutable;③当代码更新时,HTML 引用的资源 URL 自然变化,浏览器会重新下载新文件,旧文件缓存自动失效;④API 响应根据实时性要求选择:no-store(不缓存)或 max-age=60, stale-while-revalidate=300(短期缓存 + 后台重新验证)。这套方案的优势是无缓存失效问题(hash 变了就是新 URL)。
追问思考
Cache-Control: no-cache和no-store的区别是什么?为什么 HTML 文件用no-cache而不是no-store?- CDN 在缓存体系中扮演什么角色?
s-maxage和max-age分别控制谁的缓存? - 为什么 CORS 的预检请求使用 OPTIONS 方法?如果服务器不处理 OPTIONS 会怎样?
Referer头部在隐私方面有什么问题?Referrer-Policy有哪些值?- HTTP/1.1 的队头阻塞(Head-of-Line Blocking)具体是怎么发生的?浏览器开 6 个连接是怎么解决这个问题的?