HTTP 协议深度指南
HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状
官方文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP
适用版本:HTTP/1.1(RFC 7231)/ HTTP/2(RFC 7540)/ HTTP/3(RFC 9114)(2026-05-07 核实)
HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。
相关文档:Web安全基础 FastAPI完全指南 Nginx完全指南
HTTP/1.1 核心
请求报文结构
起始行(Request Line)
方法 路径 协议版本
GET /api/users?page=1 HTTP/1.1
Headers(请求头,每行一个键值对,以空行结束)
Host: api.example.com
User-Agent: Mozilla/5.0
Accept: application/json
Body(请求体,GET/HEAD 通常无 Body)
{"username": "alice"}
常用请求头
| 请求头 | 说明 |
|---|---|
Host |
目标服务器域名和端口(HTTP/1.1 必须携带),用于虚拟主机路由 |
User-Agent |
客户端标识,包含浏览器/OS 版本信息 |
Accept |
客户端接受的响应内容类型,如 application/json, text/html |
Accept-Encoding |
客户端支持的压缩方式,如 gzip, deflate, br |
Accept-Language |
首选语言,如 zh-CN,zh;q=0.9 |
Content-Type |
请求体的媒体类型,如 application/json、multipart/form-data |
Content-Length |
请求体字节长度 |
Authorization |
认证凭据,如 Bearer <token> 或 Basic <base64> |
Cookie |
发送给服务器的 Cookie 键值对 |
Cache-Control |
请求端缓存指令,如 no-cache(强制向服务器验证) |
If-Modified-Since |
协商缓存,携带上次响应的 Last-Modified 值 |
If-None-Match |
协商缓存,携带上次响应的 ETag 值 |
Origin |
跨域请求的来源,格式 scheme://host:port |
Referer |
当前请求的来源页面 URL |
Connection |
连接控制,keep-alive 复用 TCP 连接,close 关闭 |
常用响应头
| 响应头 | 说明 |
|---|---|
Content-Type |
响应体的媒体类型,如 application/json; charset=utf-8 |
Content-Length |
响应体字节长度 |
Content-Encoding |
响应体的压缩方式,如 gzip |
Transfer-Encoding |
传输编码,chunked 表示分块传输(不含 Content-Length) |
Set-Cookie |
设置客户端 Cookie,可携带多个属性 |
Location |
重定向目标 URL,配合 3xx 状态码使用 |
ETag |
资源版本标识符(实体标签),用于协商缓存 |
Last-Modified |
资源最后修改时间,用于协商缓存 |
Cache-Control |
缓存策略指令,如 max-age=3600 |
Vary |
说明响应内容根据哪些请求头变化,影响缓存键,如 Vary: Accept-Encoding |
Access-Control-Allow-Origin |
CORS 响应,允许的请求来源 |
Strict-Transport-Security |
HSTS,强制使用 HTTPS |
X-Content-Type-Options |
防止 MIME 类型嗅探,值为 nosniff |
状态码分类
| 分类 | 范围 | 含义 | 常见状态码 |
|---|---|---|---|
| 1xx | 100-199 | 信息性响应,请求已接收,继续处理 | 100 Continue:客户端可继续发送请求体 |
| 2xx | 200-299 | 成功 | 200 OK:请求成功;201 Created:资源已创建;204 No Content:成功但无响应体;206 Partial Content:范围请求部分内容 |
| 3xx | 300-399 | 重定向 | 301 Moved Permanently:永久重定向(浏览器缓存);302 Found:临时重定向;304 Not Modified:协商缓存命中;307 Temporary Redirect:临时重定向,保持方法;308 Permanent Redirect:永久重定向,保持方法 |
| 4xx | 400-499 | 客户端错误 | 400 Bad Request:请求格式错误;401 Unauthorized:未认证;403 Forbidden:无权限;404 Not Found:资源不存在;405 Method Not Allowed:方法不允许;409 Conflict:资源冲突;422 Unprocessable Entity:语义错误(FastAPI 默认用于参数校验失败);429 Too Many Requests:请求频率超限 |
| 5xx | 500-599 | 服务端错误 | 500 Internal Server Error:服务器内部错误;502 Bad Gateway:网关错误(上游服务不可用);503 Service Unavailable:服务暂不可用;504 Gateway Timeout:网关超时 |
HTTP 方法
| 方法 | 语义 | 是否幂等 | 是否有请求体 | 典型用途 |
|---|---|---|---|---|
GET |
获取资源 | 是 | 否 | 查询数据,不改变服务器状态 |
POST |
创建资源/提交数据 | 否 | 是 | 创建新资源,提交表单,RPC 调用 |
PUT |
完整替换资源 | 是 | 是 | 以请求体完整替换目标资源 |
PATCH |
部分更新资源 | 否(通常) | 是 | 仅更新资源的部分字段 |
DELETE |
删除资源 | 是 | 否(可选) | 删除指定资源 |
HEAD |
获取响应头,不返回 Body | 是 | 否 | 检查资源是否存在、获取元数据 |
OPTIONS |
查询支持的方法 | 是 | 否 | CORS 预检请求,查询跨域权限 |
幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。
缓存机制
强缓存
浏览器直接从本地缓存读取,不向服务器发送请求。
响应头:
Cache-Control: max-age=86400 # 缓存有效期 86400 秒(优先级高)
Expires: Thu, 01 Jan 2026 00:00:00 GMT # 绝对过期时间(优先级低,已被 max-age 取代)
缓存命中时,状态码为 200(from memory cache / from disk cache)。
协商缓存
浏览器携带缓存标识向服务器验证,服务器判断资源是否变化。资源未变化时服务器返回 304 Not Modified,浏览器使用本地缓存;变化时返回 200 及新资源。
两种协商缓存机制:
# 机制一:ETag(精确,优先级高)
响应头:ETag: "abc123"
请求头:If-None-Match: "abc123"
# 机制二:Last-Modified(时间精度为秒,优先级低)
响应头:Last-Modified: Wed, 01 Jan 2025 00:00:00 GMT
请求头:If-Modified-Since: Wed, 01 Jan 2025 00:00:00 GMT
Cache-Control 指令
| 指令 | 适用方 | 说明 |
|---|---|---|
max-age=N |
响应 | 资源缓存有效期(秒),从响应时间起算 |
s-maxage=N |
响应 | 共享缓存(CDN/代理)的有效期,覆盖 max-age |
no-cache |
请求/响应 | 不直接使用缓存,每次必须向服务器验证(协商缓存仍可用) |
no-store |
响应 | 禁止任何缓存存储,每次必须重新请求(比 no-cache 更严格) |
public |
响应 | 允许任何缓存(包括 CDN)存储 |
private |
响应 | 仅允许浏览器私有缓存存储,不允许 CDN 缓存 |
must-revalidate |
响应 | 缓存过期后必须向服务器验证,不允许使用过期缓存 |
immutable |
响应 | 资源内容永不变化,缓存有效期内不发送验证请求 |
HTTPS / TLS
TLS 握手流程(简化版,TLS 1.2)
Client Server
|------ ClientHello ----------------->| 客户端发送:支持的 TLS 版本、密码套件、随机数(C)
|<----- ServerHello ------------------| 服务器选择:TLS 版本、密码套件、随机数(S)
|<----- Certificate ------------------| 服务器发送:数字证书(含公钥)
|<----- ServerHelloDone --------------|
| |
| (客户端验证证书合法性) |
| |
|------ ClientKeyExchange ----------->| 客户端发送:预主密钥(用服务器公钥加密)
|------ ChangeCipherSpec ------------>| 通知:后续使用协商的加密算法
|------ Finished -------------------->| 发送握手摘要(已加密)
|<----- ChangeCipherSpec -------------|
|<----- Finished ---------------------|
| |
|<========= 加密的应用数据 ==========>| 双方用会话密钥加密通信
双方用随机数(C) + 随机数(S) + 预主密钥共同生成会话密钥(对称加密),后续通信使用此密钥。
证书链验证
服务器证书由中间 CA 签发,中间 CA 证书由根 CA 签发。浏览器内置信任的根 CA 证书列表,从叶证书逐级向上验证签名直到根 CA,全部验证通过则信任该证书。
TLS 1.2 vs TLS 1.3
| 维度 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手轮次 | 2-RTT | 1-RTT(首次),0-RTT(会话恢复) |
| 密码套件 | 多种,包含已知弱算法 | 仅保留安全算法,废除 RSA 密钥交换 |
| 前向保密 | 可选 | 强制(ECDHE) |
| 加密起始 | 握手最后阶段加密 | 握手第二个消息即开始加密 |
| 兼容性 | 极广 | 现代浏览器和服务器均已支持 |
SNI(Server Name Indication)
TLS 握手的 ClientHello 中携带目标域名,允许同一 IP 的服务器为多个域名提供不同证书(类似 HTTP 中 Host 请求头的作用)。未携带 SNI 时,服务器只能返回默认证书。
HTTP/2 与 HTTP/3
HTTP/2 核心特性
- 多路复用(Multiplexing):单个 TCP 连接上并发传输多个请求/响应,帧(Frame)级别交错,解决了 HTTP/1.1 的队头阻塞(Head-of-Line Blocking)问题。
- 头部压缩(HPACK):使用静态表、动态表和 Huffman 编码压缩请求/响应头,减少重复头部的传输开销。
- 服务器推送(Server Push):服务器可以在客户端请求 HTML 之前主动推送 CSS/JS 资源,减少往返延迟。实际使用较少,已在 HTTP/3 中废弃。
- 二进制分帧:HTTP/2 将报文分割为二进制帧(Frame),比 HTTP/1.1 的文本协议解析更高效。
- 流优先级:客户端可声明请求的优先级,服务器按优先级分配资源。
HTTP/3(QUIC)
HTTP/3 将传输层从 TCP 替换为 QUIC(基于 UDP):
- 解决 TCP 层队头阻塞:HTTP/2 的多路复用仍受 TCP 丢包影响,一个包丢失会阻塞所有流。QUIC 在传输层实现多路复用,流之间完全独立,单流丢包不影响其他流。
- 0-RTT 连接建立:已知服务器时,QUIC 可以在握手的同时发送数据(0-RTT),比 TLS 1.2 over TCP 的 3-RTT 减少显著延迟。
- 连接迁移:QUIC 使用连接 ID 标识连接(而非四元组),网络切换(如 Wi-Fi 切换到 4G)时连接不中断。
Cookie
Set-Cookie 属性
服务器通过 Set-Cookie 响应头设置 Cookie:
Set-Cookie: session_id=abc123; Domain=example.com; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=Lax
| 属性 | 说明 |
|---|---|
Domain |
Cookie 适用的域名,默认为当前域名(不含子域)。设置为 .example.com 则子域名共享 |
Path |
Cookie 适用的路径,只有路径前缀匹配时才发送该 Cookie,默认为 / |
Expires |
绝对过期时间(GMT 格式),未设置则为会话 Cookie,浏览器关闭即删除 |
Max-Age |
相对过期时间(秒),从接收时起算,优先级高于 Expires |
HttpOnly |
禁止 JavaScript 通过 document.cookie 读取,防止 XSS 窃取 Cookie |
Secure |
仅在 HTTPS 连接中发送,HTTP 下不传输 |
SameSite |
跨站请求时的发送策略,见下方说明 |
SameSite 三个值的区别
| 值 | 发送规则 | 适用场景 |
|---|---|---|
Strict |
仅同站请求发送 Cookie,跨站完全不发送(包括从其他站点点链接跳转) | 最严格,防 CSRF,但从外部链接进入时需重新登录 |
Lax |
跨站顶级导航(GET 请求)发送 Cookie,跨站的 POST/img/iframe 等不发送 | 兼顾安全与可用性,现代浏览器默认值 |
None |
跨站请求始终发送 Cookie,必须同时设置 Secure 属性 |
第三方 Cookie 场景(如嵌入式 iframe 认证) |
CSRF 攻击原理与防御
攻击原理:恶意网站诱导用户浏览时,利用浏览器自动携带目标站点 Cookie 的机制,伪造用户身份发起请求。例如用户登录银行后访问恶意网站,恶意网站自动向银行发送转账请求,浏览器携带银行 Cookie,银行无法区分请求来源。
防御方案:
SameSite=Lax/Strict:阻止跨站请求携带 Cookie,最简单有效。- CSRF Token:服务器在表单或响应中嵌入随机 Token,请求时必须携带,服务器验证 Token 合法性。恶意网站无法读取目标站点的 Token。
- 验证
Origin/Referer请求头:服务器检查请求来源,拒绝非预期来源的请求。 - 双重 Cookie 验证:将 CSRF Token 同时存入 Cookie 和请求参数,服务器比对两者是否一致。
跨域(CORS)
同源策略(Same-Origin Policy)要求协议、域名、端口三者完全一致,否则浏览器阻止 JavaScript 读取响应。CORS(Cross-Origin Resource Sharing)是服务器声明允许跨域访问的机制。
简单请求 vs 预检请求
简单请求直接发送,满足以下全部条件:
- 方法为
GET、HEAD、POST之一 - 请求头仅包含
Accept、Accept-Language、Content-Language、Content-Type(且值为text/plain、multipart/form-data、application/x-www-form-urlencoded)
预检请求(Preflight):不满足简单请求条件时,浏览器先发送 OPTIONS 请求询问服务器是否允许该跨域请求,服务器确认后再发送实际请求。触发场景:使用 PUT/DELETE/PATCH、携带 Authorization 头、Content-Type 为 application/json 等。
# 预检请求
OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type
# 预检响应
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: Authorization, Content-Type
Access-Control-Max-Age: 86400
CORS 响应头
| 响应头 | 说明 |
|---|---|
Access-Control-Allow-Origin |
允许的来源,* 表示所有,或指定具体 origin(携带 Cookie 时不能用 *) |
Access-Control-Allow-Methods |
预检响应,允许的 HTTP 方法列表 |
Access-Control-Allow-Headers |
预检响应,允许的请求头列表 |
Access-Control-Expose-Headers |
允许浏览器 JavaScript 读取的响应头(默认只能读取基本响应头) |
Access-Control-Max-Age |
预检结果的缓存时间(秒),缓存期内不再发送预检请求 |
Access-Control-Allow-Credentials |
是否允许跨域请求携带 Cookie,值为 "true" 时客户端需设置 credentials: "include" |
FastAPI CORS 中间件配置
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com", "https://admin.example.com"],
allow_credentials=True, # 允许 Cookie,此时 allow_origins 不能为 ["*"]
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
allow_headers=["Authorization", "Content-Type", "X-Request-ID"],
expose_headers=["X-Total-Count"], # 允许前端 JS 读取的响应头
max_age=86400, # 预检缓存 24 小时
)
开发环境若需允许所有来源(不含 Cookie):
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
踩坑与注意事项
浏览器缓存导致请求未发出:强缓存命中时(Cache-Control: max-age 未过期),浏览器直接使用缓存,Network 面板中请求显示为 "from memory cache" 或 "from disk cache",完全不发送请求到服务器。调试时可在 DevTools 中勾选 "Disable cache" 或在请求中加 Cache-Control: no-cache 强制验证。API 接口通常应设置 Cache-Control: no-store 或 Cache-Control: no-cache, must-revalidate,避免动态数据被缓存。
CORS 预检缓存(Access-Control-Max-Age):未设置 Access-Control-Max-Age 时,浏览器对每个跨域非简单请求都发送预检,增加一次额外的网络往返。应设置合理的缓存时间(如 86400 秒),减少预检请求数量。注意 Chrome 的上限为 7200 秒,Firefox 上限为 86400 秒。
allow_credentials 与 allow_origins 的冲突:FastAPI(及所有标准 CORS 实现)要求:当 allow_credentials=True 时,allow_origins 不能包含通配符 "*",必须明确列出允许的 origin。否则浏览器会拒绝响应并报错。
304 响应的处理:协商缓存返回 304 时,响应体为空,浏览器使用本地缓存内容。服务器的 CORS 响应头(如 Access-Control-Allow-Origin)在 304 响应中也必须携带,否则浏览器会因 CORS 检查失败而报错,即使内容来自本地缓存。
最佳实践
API 接口设置 Cache-Control: no-store:动态数据(用户信息、订单列表)绝对不应被缓存。no-store 表示完全不缓存,每次必须请求服务器。no-cache 表示每次需要向服务器验证(发 If-None-Match),返回 304 时使用缓存。
静态资源使用长期缓存 + 内容 hash 文件名:JS/CSS 用 Vite/Webpack 生成 main.[hash].js 格式,Cache-Control: max-age=31536000, immutable;HTML 用 no-store 或短期缓存,保证新版本能及时下发。
HTTPS 响应头加 HSTS:防止降级攻击,告知浏览器未来一年内只用 HTTPS 访问本域名。
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
用 Connection: keep-alive 复用 TCP 连接:HTTP/1.1 默认 keep-alive,但超时后需重建连接。HTTP/2 多路复用解决了 HTTP/1.1 的队头阻塞问题,生产环境推荐开启 HTTP/2。
CORS 预检加 Access-Control-Max-Age 缓存:非简单请求每次都发 OPTIONS 预检,设置 Access-Control-Max-Age: 7200 让浏览器缓存预检结果(Chrome 上限 7200 秒),减少额外请求。
常见陷阱
陷阱:浏览器强缓存导致请求未发出
现象: 修改了接口响应,但浏览器始终返回旧数据,DevTools Network 显示 "from disk cache"。
原因: 服务器响应头包含 Cache-Control: max-age=3600,未过期时浏览器完全不发请求。
解决: API 接口设置 Cache-Control: no-store;调试时在 DevTools 勾选 "Disable cache"(仅限 DevTools 打开时生效)。
陷阱:allow_credentials=True 时 CORS allow_origins 不能用 *
现象: 开启 Cookie 跨域后,浏览器报错:The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'。
原因: 当请求携带 credentials(Cookie / Authorization / TLS 客户端证书)时,浏览器要求 Access-Control-Allow-Origin 必须是具体的 origin,不允许通配符。
解决: 将 allow_origins 改为明确的 origin 列表,动态读取请求 Origin 头也可行。
# FastAPI 示例
CORSMiddleware(app,
allow_origins=["https://app.example.com"], # 不能是 ["*"]
allow_credentials=True,
)
陷阱:HTTP/2 推送与现代浏览器的兼容性
现象: 配置了 HTTP/2 Server Push 推送 CSS/JS,Chrome 120+ 后推送失效,资源还是单独加载。
原因: Chrome 从 106 开始移除了对 HTTP/2 Server Push 的支持(利用率极低且可能浪费带宽),改为推荐使用 <link rel="preload"> 或 103 Early Hints。
解决: 用 <link rel="preload" as="script" href="/app.js"> 替代 Server Push;或使用 103 Early Hints 响应提前告知资源(Nginx 1.25.1+ 支持)。