资讯详情

资讯详情

从HTTP到HTTPS:报文结构、TLS握手与线上排障实战

搞懂HTTP协议这件事我印象最深的一次是上个月排查线上某接口大面积超时。应用日志完全正常数据库耗时也可疑地低查了半天才发现瓶颈根本不在业务代码而在HTTP连接复用上——客户端每发一次请求都重新建一条TCP连接TLS握手一轮接一轮光连接建立就吃掉了一半耗时。那一刻我突然意识到很多“用得很熟”的HTTP知识其实并没有真正吃透请求头是怎么带的、连接是怎么管理的、状态码背后到底是什么语义这些基础问题才是线上排障最快的钥匙。这篇博文想把HTTP从前到后完整过一遍。从一次请求从出发到返回的完整旅程讲起把请求和响应报文逐字段拆开再进入HTTPS加密链路看看一次加密握手到底做了什么顺带聊清楚HTTP/2、HTTP/3带来的变化最后落到日常开发最高频使用的排查命令和避坑清单。适合后端、前端、客户端、运维、测试不同角色的同学哪怕你之前只是“会用”HTTP相信读完也能在排查问题时多几分底气。1. 先搞清楚HTTP所处的位置协议栈与请求模型1.1 HTTP不是传输层它是应用层的约定很多人聊HTTP时会下意识把它和TCP混在一起这其实是一切的起点。网络分层里HTTP属于应用层它负责定义“消息长什么样、怎么理解”而真正负责把字节从一台机器搬到另一台机器的是下面的TCP。打个比方HTTP是信封上的书写格式和内容约定TCP是那辆真的把信送过去的货车IP则是路上的地址系统。HTTP之所以选择TCP而不是UDP作为运输工具是因为它需要可靠传输数据不能丢、顺序不能乱服务器得知道客户端是不是完整地把请求发完了。TCP提供的三次握手、确认重传、按序交付恰好满足这种需求。UDP虽然快但它不保证到达谁也不知道报文会不会半路蒸发这显然不满足网页浏览、接口调用这类场景的诉求。也因此HTTP报文本质上是一段结构化的文本约定。请求行、头部字段、空行、消息体一切都有明确规则而且特意设计成人类可读。为什么要可读因为HTTP诞生于上世纪九十年代那个年代的调试手段就是telnet连上端口手敲请求可读性差一点排查问题的成本都会高一大截。就算到今天能用curl -v直接看到完整请求和响应依然是最重要的排障手段。1.2 请求-响应模型与无状态特性HTTP的交互模型极其简单客户端发请求服务端回响应仅此而已。服务端永远不会主动往客户端推送数据你想拿数据就得自己发一次请求。这种“你问我答”的模型看起来笨却让整个Web架构变得异常健壮。关键设计是“无状态”。每个请求都是独立的服务器不记得你上一次来的时候干了什么。你登录之后再次访问服务器一脸茫然直到你把Cookie带上它才想起来“哦你还是那个人”。无状态带来的好处非常实际服务器不用维护一堆客户端上下文可以随便水平扩容任何一台机器都能独立处理请求哪台挂了换一台也不会影响整体协议语义。一次完整请求的生命周期大致是DNS解析域名得到IPTCP三次握手建立连接客户端把请求报文发出去服务器解析并处理返回响应报文然后要么关闭连接要么复用连接等待下一个请求。早期的HTTP/1.0每完成一次请求就断开TCP连接成本极高HTTP/1.1引入Keep-Alive默认复用连接这才让浏览器的性能上了一个台阶。等到了HTTPS时代连接复用的价值更大了因为每次连接还要叠加一次TLS握手代价成倍增长。2. 报文拆解请求与响应每层都在传什么2.1 请求行、请求头、请求体的分工一个最普通的HTTP请求报文长这样POST /api/login HTTP/1.1 Host: example.com Content-Type: application/json Content-Length: 38 {username:admin,password:123456}第一行是请求行包含方法、URI和协议版本三个部分。方法决定了你想做什么操作URI就是你要访问的资源路径版本则告诉服务器“我按哪个版本的协议跟你说话”。请求行之后是若干请求头每个头一行冒号分隔键值。空行之后是请求体不是每个请求都有体GET请求一般没有。URL的组成也值得拆一遍。以https://example.com:443/path/to/page?namealicepage2#section3为例scheme是httpshost是example.comport是443path是/path/to/pagequery是namealicepage2fragment是#section3。有个冷门但重要的知识fragment并不会随着HTTP请求发送到服务器它纯粹是浏览器本地用来定位滚动位置的。所以你以为服务端能看到#后面的内容其实它根本看不到。HTTP方法里GET和POST是被讨论最多的冤家。真正规范中GET是“安全且幂等”的查询方法POST是“可能改变服务器状态”的提交方法。所谓安全指GET不会对资源产生副作用刷新页面不应该弹出“确认重新提交表单”所谓幂等指同一个请求执行一次和执行一百次结果一样。PUT和DELETE同样要求幂等这才是它们和POST的本质区别而不是网上流传的“GET有长度限制POST没有”。GET的URL长度限制是浏览器和服务器的历史包袱不是HTTP协议本身的规定。2.2 请求头里哪些字段值得重点关注请求头字段非常多但日常开发真正绕不开的就那么几个。Host是HTTP/1.1中必须出现的头没有它服务器都不知道你找的是哪个虚拟主机。同一个IP上可以部署几百个域名靠的就是Host来区分。这就是为什么早期HTTP/1.0可以不带头而1.1强制要求。Content-Type和Content-Length是连接请求体的命脉。Content-Type告诉服务器body是什么格式JSON、表单、纯文本都靠它区分Content-Length告诉服务器body有多长这样接收方才知道要读多少字节。如果body用分块传输就换成Transfer-Encoding: chunked此时Content-Length就不能同时出现。Accept、Accept-Encoding、Accept-Language三个头是内容协商的三件套。客户端通过它们告诉服务器“我能接受什么格式、什么压缩方式、什么语言”服务器再根据自身能力返回最合适的内容。最典型的场景是请求头带Accept-Encoding: gzip, br服务器返回gzip或Brotli压缩后的响应传输体积能小一半以上。如果漏了这个头服务器一般会返回未压缩的正文流量就白白多走了。User-Agent是客户端的自我介绍浏览器、爬虫、各类SDK都会带这个头。注意它可以被伪造因此只适合做统计和降级策略绝不能作为安全校验的依据。Referer和Origin则用来标识来源页面防盗链和CSRF防护都会参考它们但同样不能轻信Origin只有跨域请求时会带Referer则可能被Referrer-Policy策略裁剪。2.3 状态码三位数背后的逻辑状态码是服务器对请求结果的“一句话总结”。理解状态码能让你一眼看出问题大致出在哪一层。状态码含义常见场景200 OK请求成功GET/POST正常返回204 No Content成功但无响应体删除操作、异步任务提交206 Partial Content返回部分内容断点续传、视频拖动播放301 Moved Permanently永久重定向http跳https、域名迁移302 Found临时重定向未登录跳登录页303 See Other重定向到GET表单提交后跳结果页307 Temporary Redirect临时重定向保留方法接口级别临时跳转308 Permanent Redirect永久重定向保留方法接口迁移且要求保持POST400 Bad Request请求报文格式错误JSON解析失败、参数缺失401 Unauthorized未认证或认证失败没登录、token过期403 Forbidden无权限访问登录了但没角色权限404 Not Found资源不存在路径写错405 Method Not Allowed请求方法不支持GET接口收到POST408 Request Timeout请求超时服务器迟迟收不到完整请求413 Payload Too Large请求体过大上传超过了服务端限制429 Too Many Requests请求过于频繁接口限流触发500 Internal Server Error服务端内部错误代码异常未捕获502 Bad Gateway网关收到无效响应上游服务挂了或返回异常503 Service Unavailable服务不可用过载、维护中504 Gateway Timeout网关请求上游超时上游处理太慢重定向体系是很多人的知识盲区。301和302是历史最悠久的但有一个很坑的细节301和302对POST请求的处理在浏览器里并不一致大部分实现会把POST改成GET同时丢掉原始body。所以如果你要让接口重定向时保留POST方法和body必须用307或308。308和301的区别就一条308严格保留方法不会偷偷改成GET。API迁移时用308页面迁移时用301这是最稳妥的选择。2.4 响应头与内容协商响应头里的Content-Type决定了浏览器怎么解析body。比如text/html; charsetutf-8告诉浏览器这是HTML文档并且编码是UTF-8。如果服务器返回了JSON但忘写application/json前端fetch还能处理但某些SDK和浏览器行为会变得很怪。缓存相关的响应头是性能优化的主战场。Cache-Control: max-age3600表示资源可以强缓存一小时在过期前浏览器不会发任何请求。ETag是资源的指纹类似版本号资源内容变化它就变下次请求带上If-None-Match如果指纹没变服务器返回304 Not Modifiedbody为空浏览器用本地缓存。Last-Modified和If-Modified-Since是更老的条件请求方案精度按秒算重要性已经不如ETag。Set-Cookie和CORS相关的头也都在响应头上。Set-Cookie由服务器发出要求浏览器保存一条CookieCookie属性里的HttpOnly可以防脚本读取SameSite可以缓解CSRF。CORS跨域请求的核心就是响应头里带Access-Control-Allow-Origin、Access-Control-Allow-Methods这些字段浏览器看到允许的声明才肯把响应交给JS代码否则就是熟悉的“CORS error”。跨域请求还会触发一个前置的OPTIONS预检预检通过后才会真正发送业务请求这个机制常让新手摸不着头脑。3. 从明文到密文HTTPS加密全链路3.1 明文HTTP的三个致命风险HTTP明文传输本质上就是把所有内容放在一辆没有防弹玻璃的货车上。危险来自三个方面窃听、篡改、冒充。窃听最直接。在同一个局域网里任何能截获网络包的人都可以看到你在HTTP请求里传了什么。登录密码、支付信息、业务数据全是明文躺在报文里。篡改比窃听更隐蔽攻击者可以拦截你的请求把内容改掉再发给服务器反过来也能篡改响应把页面里的下载链接换成一个恶意程序。冒充则是伪造身份你请求了一个域名攻击者可以在中间伪装成那个服务器与你建立连接你收到的所有数据都经过它的手。有人会说那我应用层自己加密不就完了问题在于自研加密最难的从来不是算法而是密钥怎么安全地分发到通信双方手里。密钥从网络上传就被截获加密形同虚设。HTTP把加密这件事从应用层抽离用一套完整的公钥体系解决密钥分发和身份验证问题这就是HTTPS的由来。3.2 TLS握手一次请求背后发生了什么HTTPS在HTTP和TCP之间多了一层TLSTLS握手是整个加密通信的地基。以TLS 1.2为例一次完整握手大概是这样的客户端先发ClientHello里面带支持的TLS版本、加密套件列表和一个随机数。服务器收到后回ServerHello选定双方都支持的版本和加密套件同时带上自己的随机数紧接着发送Certificate也就是服务器证书如果使用的密钥交换算法需要还会发ServerKeyExchange最后是ServerHelloDone表示“我这边能说的都说完了”。客户端拿到证书后会做一套非常关键的验证证书是否过期、域名是否匹配、签发它的CA是不是可信的、证书链是否完整。验证通过后客户端生成一个预主密钥用服务器证书里的公钥加密发送出去。只有持有对应私钥的服务器才能解开这个预主密钥。双方再用客户端随机数、服务器随机数、预主密钥一起推导出会话密钥。紧接着客户端发送ChangeCipherSpec告诉服务器“从这条消息开始后面我发的都是密文”随后发送Finished。服务器也做同样的动作双方各自验证Finished成功握手才算完成。从这一刻起所有HTTP报文都用会话密钥进行对称加密。这里最核心的设计是“非对称加密传递对称加密的密钥”。非对称加密安全但慢对称加密快但需要双方共享同一个密钥。现实方案就是用非对称加密安全地协商出对称密钥后续大数据量的通信全都走对称加密。非对称加密只用在握手阶段等会话密钥确立性能就能回到一个可接受的量级。还有一点值得深入理解前向保密。如果用RSA密钥交换服务器私钥一旦泄露历史上记录下的所有加密流量都能被解开。而ECDHE这种临时密钥方案每次握手都会生成一对临时密钥密钥交换完成即销毁即使服务器长期私钥泄露也无法解密历史会话。这就是为什么现代安全基线都要求只用DHE或ECDHE类套件禁用RSA密钥交换。3.3 证书体系到底是谁在证明“我是我”服务器证书解决的是信任问题。证书本身包含域名、公钥、签发者、有效期等信息最关键的是它带着签发者的签名。浏览器和操作系统内置了一批可信根证书这些根证书所属的CA机构再签发下级证书形成一条从根到叶子服务器的信任链。你访问某个网站时浏览器沿着证书链一路向上查直到找到自己信任的根证书链路完整且签名有效才认定这个服务器确实就是域名对应的那台机器。证书验证有四步域名匹配、有效期检查、信任链验证、吊销状态检查。域名匹配要求证书里的Common Name或SAN字段覆盖当前访问的域名否则即使证书有效也会报警。有效期检查看起来简单却是线上事故的头号杀手太多服务因为证书过期一夜之间全部报错。吊销状态检查通过OCSP或CRL确认证书没有被提前作废这一步在浏览器里通常是软失败查不到也不会完全阻断访问。开发环境往往没有公网域名和正规证书怎么办方案是搭建私有CA。本地生成一个自签名的根证书安装到自己设备的信任列表里再用它给测试域名签发服务器证书。这样做的好处是浏览器完全信任这条私有CA链HTTPS报错消失同时又保留了对加密流量的可控性。但注意这只是测试环境做法把私有根证书装进生产环境或分发给用户设备会带来严重信任风险绝对不要这么做。3.4 加密套件与安全参数该怎样选择加密套件是一组算法的组合规定了密钥交换、认证、对称加密和哈希校验分别用什么。一个典型套件名是ECDHE-RSA-AES128-GCM-SHA256意思是密钥交换用ECDHE证书认证用RSA对称加密用AES-128-GCM消息认证用SHA256。选择加密套件就是选择“安全强度”、“性能”和“兼容性”三者之间的平衡点。服务端配置里TLS版本优先是最重要的。TLS 1.0和1.1早已被证明存在严重弱点完成淘汰TLS 1.2仍然是绝对主流适合所有场景TLS 1.3则把握手从两次RTT压缩到一次还砍掉了一堆不安全的旧算法是当前的最优选择。给Nginx一类的反向代理配置时可以按这个思路来server { listen 443 ssl; server_name example.com; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_certificate /etc/ssl/example.com/fullchain.pem; ssl_certificate_key /etc/ssl/example.com/private.key; add_header Strict-Transport-Security max-age31536000; includeSubDomains always; }配置里有几个容易被忽略的点。ssl_prefer_server_ciphers on表示以服务端套件顺序为准避免客户端拿一个弱套件来协商。HSTS头的作用是强制浏览器在指定时间内只使用HTTPS访问杜绝用户手动输入http://或者被中间人降级到明文。之后如果发生证书更新nginx -t验证完配置再reload这是基本操作。4. 协议演进HTTP/2和HTTP/3改变了什么4.1 HTTP/1.1的痛点HTTP/1.1从1997年用到今天性能短板已经非常明显。最大的问题是队头阻塞一个TCP连接上同一时刻只能处理一个请求前一个请求的响应没回来后面的请求就只能排队等。浏览器为了缓解这个问题同一个域名最多开6个TCP连接但这只是“多开几条车道”每条车道依然是单行的。头部冗余是另一个大问题。每个请求都得把Cookie、User-Agent、Accept这些头原封不动发一遍几百字节的头部在请求里可能比body还大。对几十个资源的页面来说光头部流量就是一笔不小的开销。还有个不那么显眼的问题HTTP/1.1的报文是纯文本。文本解析有边界歧义有安全复杂性也不好做高效的压缩和并发处理。要解决这些问题不能靠打补丁只能重构协议。4.2 HTTP/2多路复用与头部压缩HTTP/2最大的变化是把报文从文本格式改成了二进制分帧。一个请求被拆成多个二进制帧每帧都带一个流ID属于同一个流的所有帧可以交错传输。这样多个请求的帧可以在一个TCP连接上并行发送和接收队头阻塞在HTTP层被彻底消除一个连接就能跑满整个页面的所有资源。头部压缩是HTTP/2的第二个绝活。它维护了一张静态表和动态表把高频出现的头字段映射成数字索引发送时只传索引和变化的部分。初次请求完动态表里已经缓存了常用头后续请求的头部体积能减少九成以上。HTTP/2还引入了伪头字段用:method、:scheme、:authority、:path、:status这些带冒号的字段代替原来的请求行。调试抓包时看到这些以冒号开头的字段不要懵它们是HTTP/2的“请求行”。但HTTP/2没有解决TCP层的队头阻塞。虽然HTTP层并行了底层TCP仍然是串行可靠传输一旦某个TCP包丢了后续所有流都得等重传。这就是HTTP/3出现的原因。4.3 HTTP/3与QUIC把传输层一起改掉HTTP/3把传输层从TCP换成了QUIC而QUIC是构建在UDP之上的。UDP本身不可靠QUIC在自己这一层实现了可靠传输和拥塞控制。这样每个流独立运输一个流丢包只影响这一个流其他流完全不受牵连TCP层的队头阻塞被连根拔起。QUIC把TLS握手也整合进了连接建立过程。传统的HTTPS需要TCP三次握手加TLS握手至少两个RTTQUIC在第一个包就同步完成传输握手和TLS握手再加上早前的会话恢复能做到0-RTT——上一次连接过的服务器下一次几乎发第一个包就能附带数据。对移动场景来说QUIC的网络切换能力是杀手级特性。TCP连接依赖四元组WiFi切到移动网络IP变了TCP连接立刻断开。QUIC用连接ID而不是IP来标识连接网络切换后连接依然存活不需要重新握手。那是不是所有系统都应该立刻全面切到HTTP/3我的建议是动态评估。绝大多数公网服务、大流量静态资源场景收益明显尤其在弱网和高丢包环境下。但UDP在某些网络环境里会被限速或丢弃这是老问题加上运维监控体系对QUIC的成熟度可能还不如TCP升级前需要灰度验证一段时间。5. 开发中的HTTP排查实战从curl到抓包5.1 curl才是排查第一工具排查HTTP相关问题我第一件工具永远是curl。它功能够用、哪里都能跑、输出直白。用curl -v能看到一次请求的完整细节curl -v https://example.com/api/users输出里*开头的是连接层和TLS细节开头的是发出的请求行和请求头开头的是收到的响应头。比如看到* Connected to example.com (203.0.113.10) port 443说明DNS解析完成且TCP连接建立成功看到* TLSv1.3 (IN), TLS handshake, ...说明TLS握手进行中看到 HTTP/2 200说明响应正常返回。常用参数按场景总结一下# 只看响应头和状态码不拉body curl -I https://example.com # 自定义请求方法和请求头发送JSON curl -X POST https://example.com/api/login \ -H Content-Type: application/json \ -d {username:admin,password:123456} # 输出结构化耗时信息 curl -w 状态码: %{http_code}\nDNS耗时: %{time_namelookup}\n连接耗时: %{time_connect}\nTLS耗时: %{time_appconnect}\n总耗时: %{time_total}\n \ https://example.com/api # 指定IP访问同时带上Host绕过DNS curl --resolve example.com:443:203.0.113.10 https://example.com/api-w参数对性能定位特别有用。如果time_namelookup很大说明域名解析有问题time_connect很大说明TCP链路慢time_appconnect很大说明TLS握手慢。这些本是浏览器Network面板才能看到的数据curl直接一秒钟给全。5.2 抓包分析看到真正的字节流curl能看到的还是“封装好”的信息想确认协议层真实行为抓包是唯一可靠的办法。浏览器DevTools的Network面板是日常首选瀑布图里每一阶段耗时都可视化能直观看到某次请求卡在DNS、TCP连接、TLS握手还是等待响应。重复请求、未缓存资源、超过1MB的大响应在瀑布图下一目了然。更底层的分析用Wireshark。抓包过滤器值得记几条http只看HTTP流量tcp.port 80锁端口tls.handshake.type 1定位ClientHello。抓到TLS握手包后可以展开看客户端支持的加密套件列表、SNI里带的域名、证书链内容和服务器选中的套件。这些信息在排查“为什么这个客户端连不上”“为什么TLS版本协商失败”时几乎是唯一指定用。如果是自己开发的客户端排查HTTPS问题干净的方案是在本机抓包工具里安装一个调试用的根证书对测试环境的域名进行SSL解密这样TLS握手之后的明文HTTP内容全部可见。注意只在测试环境、自己的设备上操作别把根证书装到生产环境或无关设备否则会破坏信任边界。5.3 连接层问题排查线上HTTP故障里一大半其实发生在连接层应用代码反而是最后才需要怀疑的。这里有几个高频场景要区分清楚现象可能原因排查方向502 Bad Gateway网关后面没有可达的服务实例检查上游服务是否存活、注册中心健康检查503 Service Unavailable服务在过载或主动拒绝请求检查服务负载、熔断降级策略504 Gateway Timeout上游处理超过网关超时时间查看上游慢日志、数据库慢查询Connection refused目标端口没有服务监听检查进程是否启动、端口是否被占用Connection reset连接中途被对端断开检查防火墙、服务端主动关闭策略Operation timed out网络层数据包丢失分段ping、traceroute定位链路Connection refused和Connection reset是最容易被混淆的两种。refused的意思是对端根本没有程序在监听这个端口包发过去直接被系统内核拒绝reset则是连接建立后被主动打断比如服务端超时关闭了空闲连接、防火墙发送了RST包、后端进程崩溃重启等。超时则完全是另一个维度——包发了但没有收到任何回应多半在路上丢了。HTTPS层的问题用openssl做快速体检# 查看远端证书信息和协商结果 openssl s_client -connect example.com:443 -servername example.com # 只看证书内容 openssl s_client -connect example.com:443 -servername example.com /dev/null 2/dev/null | \ openssl x509 -noout -subject -issuer -dates -ext subjectAltName如果证书过期输出里的notAfter日期会小于当前时间如果证书链不完整verify return code会报错并指出哪一环失败。这些信息能在浏览器弹出大红页之前就帮你定位到问题。6. 我踩过的HTTP相关的坑避坑清单6.1 头部与报文的常见坑Content-Length和Transfer-Encoding: chunked同时出现是协议级错误但不少框架和网关在异常情况下会同时给出两个字段接收方一旦优先处理Transfer-Encoding而忽略Content-Lengthbody解析就会错位。排查诡异乱码或者body截断时先确认这两个头是不是同时在响应里。请求体编码是中文乱码的主因。JSON一般默认UTF-8问题不大表单提交时麻烦比较多浏览器默认用页面编码提交如果服务端预期UTF-8而客户端实际发送GBK编码解析全靠运气。正确做法是服务端显式声明期望字符集客户端按Content-Type里的charset发送别依赖“大家都用UTF-8”的默契。URL编码也是一个常被忽略的细节。URL中的空格必须编码为%20但在表单数据里空格允许写成。很多人在通用URL里也用表示空格服务器解析时可能把它当成字面量就会出怪问题。中文、emoji、特殊符号在URL里都要做百分号编码用encodeURIComponent这类方法别直接拼字符串。Cookie的尺寸限制同样容易踩。单个Cookie大小不能超过4KB各家浏览器略有差异超过的部分会被静默丢弃。有些系统把用户权限列表整个塞进Cookie数据一膨胀就出现用户“随机掉线”的诡异现象其实是Cookie被截断后的连锁反应。6.2 缓存与304的坑Cache-Control的优先级比Expires高浏览器同时看到两者时会以Cache-Control为准。如果只设置Expires客户端和服务端时钟不一致就会出偏差所以现代应用中Expires基本可以退休了。Cache-Control: no-cache大概是误解最深的头部。它并不意味着“完全不缓存”而是“使用缓存前必须先向服务器验证一下”。真正禁止缓存的是Cache-Control: no-store。一个设置了no-cache的响应如果配合ETag仍然会在每次请求时走条件请求链路验证通过后返回304省流量但多一次请求只有no-store才真正做到不落盘。304响应有个隐蔽特性它没有body。有些调用方在收到304后尝试解析body结果拿到空串还以为是bug。其实304的语义就是“内容没变用本地缓存吧”body为空是正常的。调试时看到304却找不到body不要慌去看上一份200的响应体那个才是实际内容。静态资源缓存里文件名指纹是更现代的做法。每次构建生成带哈希的文件名配合Cache-Control: max-age31536000, immutable浏览器一年都不用来问文件名一变才重新拉。这比协商缓存的304方案更省一次请求资源变更也能精确控制是当前静态资源缓存的最优解。6.3 性能优化连接复用与压缩连接复用是HTTP性能的一等大事。HTTP/1.1下每个域名浏览器最多开6个连接如果请求密集6条队列很容易排满。服务端KeepAlive超时不能设得太短否则空闲连接被回收下一个请求就得重新握手也不能设太长否则一堆半死连接占着资源。实践中常见的KeepAlive超时在60秒到75秒之间具体要看业务请求频率来调。gzip和Brotli压缩是立竿见影的优化。文本类资源压缩率普遍能到70%以上图片、视频、压缩包这些本身已经压缩过的内容不要再压CPU开销白白浪费还会增大延迟。Brotli的压缩率比gzip更高但CPU开销也更大Nginx这类服务上可以用动态Brotli模块按资源类型选择性压缩。HTTP/2环境下减少请求数量这件事的优先级其实下降了。多路复用让几十个资源可以在一个连接里并发传输“合并JS文件”反而可能牺牲缓存粒度。这时候更值得关注的是每个资源本身的体积、是否命中了缓存、有没有按需加载。优化思路要跟着协议演进走不能拿着HTTP/1.1时代的经验硬套。6.4 安全头配置一条也不能少HTTPS之外安全响应头是低成本高回报的加固手段。CSP能限制页面加载资源的白名单注入脚本基本失效X-Content-Type-Options: nosniff可以阻止浏览器把非CSS文件当CSS解析X-Frame-Options: DENY能避免页面被嵌入恶意iframeReferrer-Policy可以控制Referer的泄露范围HSTS强制浏览器只走HTTPS。这一组头配置五分钟就能完成却能在真实攻击中挡下很大一类问题。还有一类不那么显眼的隐患是错误页面信息泄露。有些服务异常时把堆栈、内部IP、JDBC连接串直接甩在响应体里这些信息到了攻击者手里就是详细地图。生产环境的错误响应应该瘦身到只剩一个错误码和一句话内部信息全部记进服务端日志单日流量再大也不要图省事直接回传。最后提醒一点被反复念叨但依然频繁出现的事POST不幂等重试可能导致下单两次、扣款两次。如果业务接口在超时场景下需要客户端重试要么改成PUT配合幂等键要么在服务端做去重校验。HTTP协议本身不会帮你管这个这属于业务语义但一个严谨的协议使用者会从一开始就把幂等性设计进去。我个人排查线上问题这几年一个特别深的感受是HTTP协议真正的门槛不在记忆字段和状态码而在建立起“报文视角”——当故障发生你能在脑子里看到请求在网络里走过的每一步能想象出报文字节长什么样还能判断哪一环最可能出问题。现在我遇到线上故障第一反应永远是curl -v、抓包、拆Header先把报文亲眼看到再谈猜测和推断。希望这篇从请求到加密链路的全攻略能帮你少走一些我当年走过的弯路。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →