网络(三):HTTP、HTTPS 与 Web 协议
网络(三):HTTP、HTTPS 与 Web 协议
导语:「HTTP 请求过程」热度 609 稳居第三,WebSocket 572 冲进前五——这一篇覆盖应用层最密集的考点。从无状态与报文结构讲起,串起方法与状态码,讲透 HTTP/1.1 → 2 → 3 的演进逻辑,补齐强缓存/协商缓存、Cookie/Session/Token/JWT、同源与跨域 CORS、WebSocket/SSE/长轮询选型四个高频实战题,最后拆开 HTTPS 握手、证书链与防中间人、会话复用与优化,共 12 题。
一、HTTP 基础
1. HTTP 的"无状态"是什么意思?长连接与短连接的本质是什么?
答:
无状态(Stateless):HTTP 协议本身不保存客户端上下文——每个请求都是独立的,服务器处理完就"忘了你是谁"。这带来两个好处:
- 简单、可水平扩展(任意请求可路由到任意实例,无需粘滞);
- 代价:需要状态时必须靠应用层自己维持(Cookie/Session/Token,见第 6 题)。
易错点:HTTP 是无状态的,但 TCP 是有连接的。两者不要混淆——HTTP 的"连接复用"是复用了 TCP 连接,而不是"HTTP 记住了你"。
长连接与短连接的本质:
| 维度 | 短连接 | 长连接 |
|---|---|---|
| 建连开销 | 每次 3 次握手 + 4 次挥手(1 RTT+ 用于握手,慢启动重新开始) | 一次握手复用多次请求 |
| 资源占用 | 无长占连接 | 需维持连接(内存/端口),需空闲超时与心跳 |
| HTTP 版本 | HTTP/1.0 默认(需显式 Connection: keep-alive) | HTTP/1.1 起默认;HTTP/2 天生单连接多路复用 |
| 适用 | 偶发、低频请求 | 高频、需要低延迟(页面加载、RPC) |
三个高频追问:
- 「keep-alive 的超时和最大请求数怎么配?」 → Nginx 的
keepalive_timeout(如 65s)、keepalive_requests(如 1000);要保证服务端的空闲超时 > 客户端(或上游 LB)的空闲超时,否则会出现"客户端刚要用,连接已被服务端关掉"的竞态(表现为偶发Connection reset或502)。 - 「长连接会不会导致 TIME_WAIT?」 → 最终关闭时主动关闭方仍会进 TIME_WAIT,但频率大幅降低(因为一次连接服务了成百上千个请求)。
- 「HTTP/2 还需要 keep-alive 吗?」 → 不需要,HTTP/2 一个连接上跑所有请求(多路复用),
Connection头在 HTTP/2 中被禁止使用(属于连接专用的头,由协议自身管理)。
2. GET 与 POST 的区别?可以自定义请求方法吗?
答:
| 维度 | GET | POST |
|---|---|---|
| 语义(RFC 9110) | 获取资源(安全、幂等) | 处理/提交数据(非安全、非幂等) |
| 参数位置 | 通常放 URL query | 通常放 请求体 |
| 长度限制 | 受 URL 长度限制(浏览器/服务器约 2KB~8KB) | 理论上无限制(受服务端配置) |
| 缓存 | 可被缓存(浏览器、CDN、代理) | 默认不缓存(除非显式指定) |
| 幂等/安全 | 幂等且安全(不改变服务端状态) | 非幂等(可能重复下单) |
| 书签/历史 | 可收藏、可回退 | 刷新会提示"重复提交" |
| 编码 | 只能 URL 编码 | 支持多种(form、JSON、multipart、二进制) |
必须修正的一个流行误解:
「GET 不能带 body、POST 不能带 URL 参数」是错的(但实践中不要这么做)。
- 规范层面:HTTP 规范没有禁止 GET 携带 body,语义未定义(undefined behavior);
- 实践层面:不要用——部分代理/网关/Nginx/浏览器会忽略或丢弃 GET 的 body,埋下"本地能跑线上不行"的隐患;
- 同理,POST 完全可以在 URL 上带参数(技术可行,但不推荐,会被日志记录)。
HTTP 方法的语义(面试常问"哪些是幂等的"):
| 方法 | 语义 | 安全 | 幂等 |
|---|---|---|---|
| GET | 获取 | ✅ | ✅ |
| HEAD | 只取响应头(用于探测资源是否存在/大小/Last-Modified) | ✅ | ✅ |
| OPTIONS | 查询支持的方法(CORS 预检用它) | ✅ | ✅ |
| PUT | 全量替换资源(覆盖式保存) | ❌ | ✅(替换是幂等的) |
| PATCH | 局部更新资源 | ❌ | ❌(取决于实现) |
| POST | 创建/提交/触发处理 | ❌ | ❌ |
| DELETE | 删除 | ❌ | ✅(删多次结果一样) |
幂等的判定口诀:「执行一次和执行 N 次,服务端的最终状态是否相同」。PUT/DELETE 幂等,POST 不幂等——这是面试必背的对比,也是重试机制设计的依据(幂等接口可以安全重试)。
关于"自定义方法":HTTP 方法本质是一个 token,规范允许扩展(如 WebDAV 的 PROPFIND、MKCOL)。但自定义方法实际很难用:浏览器 fetch/XMLHttpRequest 会限制;代理/CDN/WAF 可能不认识而拒绝或降级。结论:扩展方法需要服务端 + 全链路中间件都支持,生产上几乎不被采纳。
3. HTTP 常见状态码有哪些?502/504/499 分别代表什么?
答:
| 类别 | 含义 | 常见码 |
|---|---|---|
| 1xx 信息 | 请求已收到,继续处理 | 100 Continue(客户端可继续发 body)、101 Switching Protocols(WebSocket 升级成功) |
| 2xx 成功 | 请求成功处理 | 200 OK、201 Created、202 Accepted(已受理,异步处理)、204 No Content(成功但无响应体) |
| 3xx 重定向 | 需要进一步操作 | 301 永久(会被浏览器长期缓存,慎用)、302/307 临时(307 保证不改变方法)、304 Not Modified(协商缓存命中)、308 永久且保持方法 |
| 4xx 客户端错误 | 请求有问题 | 400 语法/参数错、401 未认证、403 已认证但无权限、404 不存在、405 方法不允许、408 请求超时、429 触发限流 |
| 5xx 服务端错误 | 服务端有问题 | 500 内部错误、502 Bad Gateway、503 Service Unavailable、504 Gateway Timeout |
三个高频实战码(几乎必问):
| 状态码 | 含义 | 常见根因 |
|---|---|---|
| 502 Bad Gateway | 网关从上游收到了无效响应(连上了但响应不合法/连接被重置) | 上游进程崩溃、上游返回了非 HTTP 响应、上游主动 close 了连接(keep-alive 竞态)、协议不匹配(如上游说 HTTP/2 实际是 HTTP/1.1) |
| 504 Gateway Timeout | 网关等上游响应超时(请求发出去了,没等到) | 上游处理太慢(慢 SQL、下游阻塞)、网关 proxy_read_timeout 太小、上游线程池打满 |
| 499(Nginx 专有) | 客户端在服务端返回前主动断开了连接 | 客户端/上游网关超时后放弃(如上游网关的 read_timeout 比本层小)、用户关闭页面、服务端处理太慢导致客户端等不及 |
「502 和 504 怎么区分」的标准答法:
排查顺序:先看是谁报的 5xx(网关/应用/数据库),再对齐各层的超时时间。多层网关(CDN → Nginx → 网关 → 应用)时,必须保证「外层超时 > 内层超时」,否则会出现"内层还在算,外层已经断开并报 502"的雪崩式误报。
4. HTTP/1.1、HTTP/2、HTTP/3 的核心演进与区别?
答: 这是近年最热门的网络演进题,答题主线是「为了解决什么痛点」。
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 传输层 | TCP | TCP | QUIC(基于 UDP) |
| 数据格式 | 文本(可读但低效) | 二进制分帧 | 二进制分帧(QUIC 帧) |
| 多路复用 | ❌(管线化有队头阻塞,浏览器实际用 6 个并行连接绕过) | ✅ 单连接多路复用(Stream + Frame) | ✅ 多路复用,且无 TCP 层队头阻塞 |
| 队头阻塞 | HTTP 层 + TCP 层都有 | HTTP 层解决,TCP 层仍存在 | 彻底解决 |
| 头部压缩 | 明文重复传输 | HPACK(静态表 + 动态表 + 哈夫曼) | QPACK(需适配乱序到达) |
| 服务器推送 | ❌ | ✅(实际因收益低已被逐步弃用) | ✅ |
| 建连开销 | 1 RTT TCP + TLS | 同 1.1 | 1-RTT,会话恢复可 0-RTT |
| 连接迁移 | ❌(IP 变则连接断) | ❌ | ✅ Connection ID 保持连接 |
演进的三步逻辑(面试按这个讲最清楚):
三个高频追问:
- 「HTTP/3 为什么选 UDP 而不是继续优化 TCP?」 → TCP 协议栈在内核,修改与部署成本极高(要升级所有操作系统);而且 TCP 是"面向字节流"的,无法感知上层有多个独立流,队头阻塞是协议本质而非实现缺陷。用 UDP + 用户态协议栈可以快速迭代、且完全掌控重传/拥塞策略。
- 「HTTP/2 的头部压缩为什么需要动态表?QPACK 又为什么要改?」 → HPACK 的动态表依赖"帧按顺序到达"(后续帧引用前面帧建立的表项);HTTP/3 的流可乱序到达,所以 QPACK 引入单向流传递表状态 + 阻塞控制来适配。
- 「HTTP/2 会不会影响服务端架构?」 → 会:连接数大幅减少(前端 6 连接变 1 连接)→ 后端 LB/Nginx 的连接数指标失去"用户数"含义、限流策略要从"连接数"改为"请求数/流数";同时gRPC 就构建在 HTTP/2 之上。
二、Web 实战四问
5. HTTP 缓存机制:强缓存与协商缓存是什么?如何配置?
答: 高频必考(对性能优化至关重要),核心区分是「还要不要问服务器」。
| 类型 | 相关头 | 特点 |
|---|---|---|
| 强缓存 | Cache-Control: max-age=3600(相对时间,优先级高)Expires: Wed, 21 Oct 2026 07:28:00 GMT(绝对时间,受客户端时钟影响) | 不发请求,性能最好;但过期前拿不到新内容 |
| 协商缓存 | ETag / If-None-Match(内容指纹,优先级高)Last-Modified / If-Modified-Since(最后修改时间) | 发请求但可能只有 304(无 body,省带宽);能保证内容最新 |
为什么 ETag 优于 Last-Modified(高频追问):
| 场景 | Last-Modified 的问题 | ETag 的优势 |
|---|---|---|
| 文件内容没变但被 touch 修改了 mtime | 会误判为"已修改",返回 200 全量 | 内容哈希未变 → 304 |
| 1 秒内多次修改 | 时间精度只到秒,无法识别 | 内容指纹能识别 |
| 文件被回滚到旧版本 | mtime 变新,误判 | 内容哈希相同 → 304 |
Cache-Control 的常用指令(面试常问):
| 指令 | 含义 |
|---|---|
max-age=N | 资源在 N 秒内新鲜(强缓存有效) |
s-maxage=N | 同 max-age,但只针对共享缓存(CDN、代理),优先级高于 max-age |
no-cache | 不是"不缓存"!而是"可以缓存,但每次必须去服务器做协商缓存校验"(易错点!) |
no-store | 真的不缓存(任何地方都不存),用于敏感数据 |
private | 只允许浏览器私有缓存,CDN 等共享缓存不得存储 |
public | 允许任何缓存(含 CDN) |
must-revalidate | 过期后必须去校验,不允许使用过期副本(应对网络中断) |
immutable | 在 max-age 内连刷新都不重新校验(适合带哈希的静态资源) |
最佳实践(一句能说服面试官的答案):
6. Cookie、Session、Token、JWT 有什么区别?怎么选?
答:
| 方案 | 存储位置 | 服务端是否有状态 | 核心机制 | 适用 |
|---|---|---|---|---|
| Cookie | 客户端(浏览器自动携带) | — | 服务端 Set-Cookie 下发,浏览器后续请求自动带上 | 作为载体(Session ID / Token 通常都放 Cookie 或 Header) |
| Session | 服务端(内存/Redis) | 有状态 | 服务端存会话数据,客户端只存 JSESSIONID | 单体/少量实例,需要服务端控制会话(强制下线) |
| Token(自定义) | 客户端(Header/参数) | 通常有状态(Redis 存) | 服务端发放随机串,校验时反查存储 | 需要可控失效(改密/登出立即失效) |
| JWT | 客户端(自包含) | 无状态 | Header.Payload.Signature 三段,服务端验签即可 | 微服务/多端、跨域、无状态校验 |
JWT 的结构与验证原理:
四个必须说清的要点:
- JWT 的 Payload 是"明文"(仅 Base64 编码),绝不能放敏感信息(密码、身份证号);
- JWT 无法"主动失效":签出去就有效到
exp。要支持登出/踢人,需要额外手段:- 维护黑名单(Redis 存被撤销的 jti)——这又变回了有状态;
- 或者用短过期 + Refresh Token 双令牌(Access Token 15min 过期,Refresh Token 长期且可撤销);
- 签名算法安全:要用
HS256/RS256,并显式指定算法——历史上出现过"把alg改成none绕过验签"的漏洞; - Cookie vs Header 的取舍:
- Cookie 会自动携带 → 天然有 CSRF 风险,所以要用
HttpOnly(防 XSS 偷取)、Secure、SameSite=Lax/Strict(防 CSRF); - Header 方式(
Authorization: Bearer xxx)不受 CSRF 影响,但需要前端手动管理,且容易在 XSS 下被读取。
- Cookie 会自动携带 → 天然有 CSRF 风险,所以要用
选型建议(面试万能答案):
7. 什么是同源策略与跨域?CORS 是怎么工作的?
答:
同源策略(Same-Origin Policy):浏览器的安全机制——只有「协议 + 域名 + 端口」三者完全一致才允许脚本读取对方资源(注意:请求可以发出,但不允许 JS 读取响应)。
五种跨域解决方案:
| 方案 | 原理 | 适用 |
|---|---|---|
| CORS(主流标准) | 服务端返回 Access-Control-Allow-* 头,浏览器据此放行 | 前后端分离的正解 |
| Nginx 反向代理 | 把跨域变成同源(前端请求 /api,由 Nginx 转发到真实后端) | 部署层统一处理,生产最常用 |
| JSONP | 用 <script> 标签(不受同源限制)加载回调 | 仅 GET,老方案,逐渐淘汰 |
| postMessage | 页面间跨域通信(iframe/popup) | 嵌入场景 |
| WebSocket / SSE | WebSocket 握手不受同源策略限制(服务端自行校验 Origin) | 长连接场景 |
CORS 的两类请求(高频考点):
四个高频实战点:
| 问题 | 答案 |
|---|---|
| 带 Cookie 的跨域请求 | 需要三处同时满足:① 前端 xhr.withCredentials = true(或 fetch credentials: 'include');② 服务端 Access-Control-Allow-Credentials: true;③ Access-Control-Allow-Origin 不能是 *,必须是具体域名 |
Access-Control-Max-Age 有什么用 | 缓存预检结果,避免每个请求都先发一次 OPTIONS(生产必须设置,否则接口 QPS 翻倍) |
| 为什么前端报跨域错但后端日志有请求 | 说明请求到达了服务端(可能是预检没通过,或响应缺少 Allow-Origin)→ 看浏览器控制台是"请求被拦截"还是"响应不可读" |
Vary: Origin 为什么重要 | CDN/代理缓存响应时,必须按 Origin 区分缓存,否则会把 A 站的 CORS 头返回给 B 站(安全漏洞) |
8. WebSocket、SSE、长轮询怎么选?
答: 近两年的新增热点(大模型流式输出、实时协作普及,「如何选型长连接方案」成为高频题)。
| 方案 | 底层 | 通信方向 | 协议 | 复杂度 | 适用 |
|---|---|---|---|---|---|
| 短轮询 | HTTP | 拉(客户端问) | 简单 | 最低 | 低频、可容忍延迟(如"每 30s 刷新状态") |
| 长轮询(Long Polling) | HTTP | 拉(服务端 hold 住请求直到有数据) | 简单 | 中 | 兼容性要求极高、消息不频繁(如配置中心 Nacos 用长轮询) |
| SSE(Server-Sent Events) | HTTP(长连接) | 单向:服务端 → 客户端 | text/event-stream | 低 | 服务端推送为主(AI 流式输出、日志/进度、行情) |
| WebSocket | HTTP Upgrade 后独立协议 | 双向 | ws:// / wss:// | 高(要心跳、重连、鉴权) | 双向实时(IM、协同编辑、游戏、交易) |
WebSocket 的建立过程(易错点:不是"完全独立于 HTTP",而是从 HTTP 升级而来):
SSE 的要点:
选型口诀(可以直接背):
WebSocket 生产的三个坑(加分点):
- 必须做心跳 + 断线重连:中间设备(NAT/LB)会回收空闲连接;客户端要带指数退避重连并做消息补偿(重连后拉取丢失消息);
- 鉴权:WebSocket 握手可以带 Cookie 或
token查询参数(浏览器 WebSocket API 不支持自定义 Header)→ 常见做法是先 HTTP 登录拿 ticket,再用 ticket 建立 WS 连接(一次性票据); - 负载均衡:WS 是长连接,需要 LB 支持(Nginx
proxy_set_header Upgrade、四层 LB),并且要考虑"连接粘滞"与有状态消息路由(推给哪台服务器上的哪个连接 → 需要会话注册表,如 Redis 记录userId → 网关实例)。
三、HTTPS 与安全
9. HTTPS 的握手过程?为什么用"非对称 + 对称"结合?
答: HTTPS = HTTP + TLS/SSL(默认 443)。以 TLS 1.2 + ECDHE 为例:
为什么"非对称 + 对称"结合(标准答案):
| 加密方式 | 特点 | 在 HTTPS 中的角色 |
|---|---|---|
| 非对称加密(RSA/ECC) | 安全但慢(比对称慢 2~3 个数量级) | 只用于握手阶段:交换/协商出对称密钥、验证身份 |
| 对称加密(AES/ChaCha20) | 快,适合大数据量 | 用于应用数据传输(真正的业务流量) |
一句话:用非对称"安全地协商出密钥",用对称"高效地传输数据"——兼顾安全与性能。
三个高频追问:
- 「为什么用三个随机数(client_random + server_random + pre_master_secret)生成密钥?」 →
- 增加随机性与熵:任何一个随机数被预测/控制,密钥仍然安全;
- 防止重放:随机数每次都变,相同会话无法重放;
- 双方共同参与:任一方都无法单独决定会话密钥(避免单点被控即失守);
- 早期 RSA 密钥交换时
pre_master_secret由客户端生成并用服务端公钥加密(无前向安全),ECDHE 则双方共同协商(有前向安全)。
- 「为什么现代推荐 ECDHE 而不是 RSA 密钥交换?」 → RSA 密钥交换下,攻击者只要事后拿到服务器私钥,就能解密历史全部流量(无前向安全);ECDHE 的临时密钥用完即弃,即使私钥泄露也无法解密历史流量(前向安全 / Forward Secrecy),且性能更好。
- 「TLS 1.3 有什么变化?」 → ①握手从 2-RTT 简化到 1-RTT(会话恢复 0-RTT);②废弃了 RSA 密钥交换与不安全的算法(只保留带前向安全的方案);③缩短了握手报文(ServerHello 之后立即 Finished);④支持 0-RTT 但有重放风险,只应用于幂等请求。
10. 数字证书、CA 与证书链是什么?浏览器如何防止中间人攻击?
答:
HTTPS 防中间人的四道防线:
| 防线 | 作用 |
|---|---|
| ① 证书链验证 | 中间人无法伪造 CA 签名 → 自签名/伪造证书无法通过验证 |
| ② 域名匹配(SAN) | 即使有合法证书,域名不匹配也会告警(防"用 B 站证书冒充 A 站") |
| ③ 有效期与吊销检查 | 过期证书告警;通过 CRL(吊销列表)或 OCSP(在线状态协议)检查是否被吊销 |
| ④ Finished 摘要校验 | 握手报文被篡改会在 Finished 校验时失败(防"中间人篡改握手内容") |
中间人攻击的三种典型形态与防御:
HSTS 与 OCSP Stapling(两个加分点):
| 机制 | 作用 |
|---|---|
HSTS(Strict-Transport-Security) | 告诉浏览器"这个域名在 N 秒内只能用 HTTPS",阻止用户点击"继续访问不安全"降级,也防 301 劫持;配合 Preload 列表可做到首次访问就强制 HTTPS |
| OCSP Stapling | 由服务端预先向 CA 拉取自己的证书状态并"钉"在握手响应里,客户端无需再单独请求 CA → 减少一次网络请求、保护隐私、加快握手 |
11. HTTPS 会话复用与性能优化有哪些手段?
答: 完整 TLS 握手需 1~2 RTT 且涉及非对称运算,是 HTTPS 的主要性能开销。
三种会话复用:
| 机制 | 原理 | RTT | 缺点 |
|---|---|---|---|
| Session ID | 服务端缓存会话密钥,客户端重连带 ID,命中就恢复 | 1 RTT | 服务端要存状态,集群需共享(Redis 等),否则负载均衡后命中率低 |
| Session Ticket | 服务端把加密后的会话状态发给客户端保存(ticket),重连时带回、服务端解密即可 | 1 RTT | 无需服务端存状态;但 ticket 密钥要定期轮换(否则泄露=历史会话可解密) |
| TLS 1.3 PSK / 0-RTT | 恢复会话时首个包即可携带应用数据 | 0 RTT | 有重放攻击风险,只应允许幂等请求(GET)使用 |
HTTPS 优化的完整清单(面试按"握手 + 传输 + 计算"三块答):
面试常问:「为什么上线 HTTPS 后 CPU 飙升?」→ 主要是握手阶段的非对称运算(RSA 签名/ECDHE)。解法优先是会话复用(提高复用率,避免重复握手),其次是 TLS 1.3 + ECDHE 降开销,最后才是硬件卸载。
12. HTTP 请求与响应报文的结构是怎样的?常见头有哪些?
答:
请求头(高频分组记忆):
| 分类 | 头 | 作用 |
|---|---|---|
| 请求上下文 | Host | 必填(HTTP/1.1),标识目标域名(配合虚拟主机/SNI) |
User-Agent / Referer / Origin | 客户端标识 / 来源页 / 跨域来源 | |
| 内容协商 | Accept / Accept-Encoding / Accept-Language | 期望的类型 / 压缩算法 / 语言 |
| 内容描述 | Content-Type / Content-Length | 请求体类型与长度 |
| 缓存与条件 | If-None-Match / If-Modified-Since | 协商缓存(见第 5 题) |
| 认证与状态 | Authorization / Cookie | 令牌 / 会话 |
| 连接控制 | Connection: keep-alive / close | 是否复用连接(HTTP/2 中禁用) |
响应头(高频分组记忆):
| 分类 | 头 | 作用 |
|---|---|---|
| 内容描述 | Content-Type / Content-Length | 响应体类型与长度 |
| 缓存 | Cache-Control / ETag / Last-Modified / Expires | 见第 5 题 |
| 安全 | Strict-Transport-Security(HSTS)、Content-Security-Policy(CSP)、X-Frame-Options、X-Content-Type-Options、Referrer-Policy | 浏览器安全策略 |
| 跨域 | Access-Control-Allow-Origin 等 | CORS |
| 跳转与限流 | Location / Retry-After / Set-Cookie | 重定向目标 / 限流后重试时间 / 下发 Cookie |
两个高频追问:
「
Content-Length和Transfer-Encoding: chunked的关系?」Content-Length:事先知道长度(一次性生成全部内容);chunked:不知道总长度时(流式生成、SSE、大文件边查边发)用"分块传输",每块带长度,最后以0\r\n\r\n结束;- 两者互斥(同时出现会有安全风险,可能被用于请求走私 Request Smuggling 攻击)。
「
GET/HEAD的响应体有什么特殊之处?」HEAD 只返回头、不返回体,常用于"探活/探测资源是否变化/取文件大小",也是最轻量的健康检查手段。
下一篇:《网络(四)》进入高并发网络编程——五种 IO 模型与同步/异步的本质区分、select/poll/epoll 的对比与 epoll 高效原理(含"mmap 共享内存"这一常见误解的纠正)、LT 与 ET、epoll 空轮询 bug 与 Netty 的解法、Reactor 三种模型、零拷贝(mmap/sendfile/splice)、epoll 惊群与
SO_REUSEPORT,以及高并发服务的 Linux 参数调优清单。
