ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

吃透HTTP/HTTPS:请求头、状态码与线上排障实战

吃透HTTP/HTTPS:请求头、状态码与线上排障实战 周五晚上十一点多线上告警突然响成一片所有走某个内部接口的下单流程都在报错错误码清一色是 502 Bad Gateway。我第一反应是后端服务挂了可登录服务器看了一圈进程活得好好的应用日志里只有一行刺眼的upstream returned http 403 forbidden再翻接入层配置发现一个入口服务的健康检查路径下午刚被人改过。最后真正定位到根因花了将近三个小时——问题不在业务代码而在一个响应头、一个状态码的语义理解上。也是从那天起我把 HTTP/HTTPS 协议从用过变成了吃透。这篇内容不是协议名词的堆砌而是把我这些年踩过的坑、抓过的包、排过的障揉进去把请求头、响应头、状态码、数据包结构这四块讲透。它适合这几类人后端开发动不动就要排查为什么接口返回404/403/502前端被跨域和下载文件折腾过测试同学用 JMeter 录脚本、压接口运维天天和网关、状态码打交道还有刚开始玩 CTF 的安全爱好者因为很多 Web 题本质就是考你会不会构造一个 HTTP 请求。1. HTTP与HTTPS的本质差异明信片、信封与保险箱1.1 先看HTTP在通信模型里的位置HTTPHyperText Transfer Protocol超文本传输协议是应用层协议它只关心客户端和服务端之间怎么约定请求和响应的格式不关心数据怎么跨过千山万水到达对端。真正负责传输的是下层的 TCP/IPIP 负责寻址把数据包送到哪台机器TCP 负责可靠传输保证数据不丢、不乱序、不重复HTTP 则负责解释这段数据到底是什么意思。用寄信类比就好懂得多IP 是门牌地址系统TCP 是那个保证挂号信不丢的邮政体系HTTP 是你写在信纸上的格式约定——你好请给我某某资源。这个层级关系非常重要因为很多诡异问题都出在跨层上。比如你访问一个页面白屏按 F12 看到请求 pending 很久最后超时这时候先别怀疑 HTTP 报文写错了很可能是 TCP 连不上、DNS 解析失败甚至网络本身断了。排查的第一步永远先确认问题出在哪一层。1.2 HTTPS到底改了什么TLS握手的情况先说结论HTTPS 并不是一种全新的协议它是HTTP over TLS——HTTP 报文外面再套一层 TLSTransport Layer Security传输层安全加密通道。默认端口从 80 变成 443URL scheme 从 http 变成 https仅此而已请求头、响应头、状态码、报文结构在进入加密通道之后和 HTTP 一模一样。TLS 要解决三个问题机密性数据加密防止被偷看。HTTP 本身是明信片所有内容URL、请求头、body都可以被沿途设备直接读取这是 HTTP 最大的隐私短板。完整性数据在传输中被篡改接收方能发现。身份认证确认你连的服务器确实是它自称的那台而不是中间某个冒牌货。TLS 握手简化成四步第一步 ClientHello客户端告诉服务器自己支持的 TLS 版本、加密套件列表、随机数第二步 ServerHello 加证书服务器选一个加密套件返回自己的数字证书包含公钥和域名信息第三步证书校验与密钥交换客户端校验证书是否可信是否由受信任的 CA 签发、域名是否匹配、是否过期然后生成预主密钥用服务器公钥加密后发过去第四步双方生成会话密钥之后所有 HTTP 数据都用对称加密的方式加解密。这里有个很多人误解的点TLS 握手阶段用的非对称加密RSA/ECDSA性能开销大所以只用来安全地协商出会话密钥真正传输数据时用的是 AES 这类对称加密速度快得多。换句话说公钥加密负责安全地递钥匙对称加密负责用钥匙锁好一整箱信。1.3 一次完整请求的端到端旅程把整条链路串起来看一次 HTTPS 请求大概是这样的浏览器解析域名走 DNS 查询拿到服务器 IP。TCP 三次握手建立连接SYN、SYN-ACK、ACK。TLS 握手协商出会话密钥HTTPS 才有这一步HTTP 直接跳过。发送 HTTP 请求请求行加请求头加空行加请求体明文写好然后用会话密钥加密成密文发出。服务器解密、解析、处理业务生成 HTTP 响应同样加密后返回。浏览器解密、解析响应渲染页面或触发后续动作。每一步失败表现出来的症状都不同DNS 失败是域名解析错误TCP 失败是连接超时TLS 失败是证书错误或握手失败只有最后到了 HTTP 层才会出现 404、403、502 这些状态码。所以看到状态码之前先想想它到底是在哪一层被定义出来的排障方向才不会跑偏。2. 客户端在说什么请求头的组成与实战用法2.1 请求行与报文格式HTTP 请求报文长得非常规整由四部分组成请求行、请求头、空行、请求体。空行必不可少它是头结束了的标记没有空行服务器就不知道头到哪结束、body 从哪开始。POST /api/login HTTP/1.1 Host: example.com Content-Type: application/json User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Content-Length: 36 {username:tom,password:123}请求行三个字段方法POST、请求目标/api/login、协议版本HTTP/1.1。方法常见的有 GET取数据、POST提交数据、PUT整体替换、PATCH部分更新、DELETE删除、HEAD只取响应头不取 body、OPTIONS探测服务器支持哪些方法CORS 预检就靠它。注意这个例子里{username:tom,password:123}正好是 36 个字节所以 Content-Length 是 36。服务器读取时靠 Content-Length 知道 body 读多少才不会把下一个请求的内容混进来。这就是HTTP 连接复用能成立的前提——同一个 TCP 连接上连续发多个请求如果没法精确切分每个请求的边界必然串包。2.2 高频请求头逐个说清请求头字段很多但业务里真正天天打交道的就下面这些请求头作用实战要点Host目标主机和端口HTTP/1.1 起必须携带一个服务器挂多个域名靠它区分User-Agent客户端身份标识反爬、统计、内容协商都看它非常容易被伪造Referer来源页面 URL防盗链、统计来源用注意和 Origin 区分Origin发起请求的来源协议域名端口CORS 场景必看不带路径Cookie会话凭证浏览器自动携带注意 HttpOnly 属性Authorization认证凭据常用 Bearer Token、Basic 认证Content-Type请求体格式application/json、x-www-form-urlencoded、multipart/form-dataContent-Length请求体字节数与 Transfer-Encoding 互斥Accept期望的响应格式告诉服务器希望返回 JSON 还是 XMLAccept-Encoding支持的压缩算法gzip、br不写可能返回未压缩大包Range请求资源的一部分断点续传、视频拖动播放靠它If-Modified-Since / If-None-Match条件请求配合 304 做协商缓存X-Forwarded-For原始客户端 IP由网关/负载均衡追加可伪造不能直接信任挑几个重点展开。Host为什么 URL 里已经有域名了请求头还要带 Host因为 HTTP/1.1 允许一台服务器同一个 IP上部署成百上千个虚拟主机。服务器收到请求后靠 Host 字段决定把请求交给哪个站点。所以Host 头和服务器上的域名如果不匹配就会得到 404 或默认站点页面。这个字段也是 Host 头攻击的核心目标。User-Agent我以前排查过一个诡异问题某个内部爬虫每天固定时间被第三方接口 403手动用浏览器访问完全正常。最后发现是爬虫的 User-Agent 没有伪装被对方 WAF 按非浏览器请求拦了。把 UA 改成和浏览器一致后问题消失。所有反爬第一步几乎都是看 UA。Referer 和 OriginReferer 会带上完整路径还可能带了敏感 query 参数Origin 只在跨域请求和 POST 请求里普遍携带值只有协议加域名加端口。为了安全很多框架会对 Referer 做校验来防 CSRF结果就是第三方页面里嵌入你的链接时 Referer 为空如果后端强制校验 Referer 就直接误杀这类问题要把 Referrer-Policy 策略配置对才能解决。2.3 a标签下载带不上token的三种解法我经常看到有人问用 a 标签下载视频接口要求带 token怎么带答案是a 标签做不到。它的下载请求是浏览器直接发起的能自动带的只有 Cookie、Referer 这类由浏览器管理的头你没办法在 href 里塞一个自定义请求头。推荐的三种方案fetch blob 下载先用 fetch 请求接口并带上 Authorization 等自定义头拿到响应后转成 blob再用 URL.createObjectURL 生成临时 URL最后动态创建 a 标签触发下载。token 放 Cookie如果视频接口和页面同源可以让接口从 Cookie 里读 token这样 a 标签、video 标签都能直接用。缺点是 Cookie 有大小限制4KB 左右、在第三方场景容易泄漏需要配合 HttpOnly 和 SameSite 使用。token 放短时效签名 URL把 token 或签名拼到 query 参数上生成一个几分钟内有效的下载地址适合分享给第三方播放器的场景。缺点是 URL 会出现在访问日志里签名一定要短时效最好再绑定 IP。给一个方案 1 的示例代码async function downloadWithToken(url, token, filename) { const resp await fetch(url, { headers: { Authorization: Bearer ${token} } }) if (!resp.ok) throw new Error(download failed: resp.status) const blob await resp.blob() const objectUrl URL.createObjectURL(blob) const a document.createElement(a) a.href objectUrl a.download filename || download.bin document.body.appendChild(a) a.click() a.remove() URL.revokeObjectURL(objectUrl) }注意事项大视频用 blob 方式会整段加载进内存容易把页面搞崩更稳妥的是以流式方式写入文件配合 File System Access API 或分片下载。文件名最好从响应头 Content-Disposition 解析而不是硬编码。还要确保服务端允许这个接口跨域访问否则 fetch 会先死在 CORS 上。2.4 用curl构造带请求头的请求curl 是排查 HTTP 问题的首选工具比起浏览器它能看到和控制的细节多得多。最常用的一组命令curl -v https://api.example.com/v1/users \ -H Authorization: Bearer my_token \ -H User-Agent: DebugClient/1.0 \ -H Content-Type: application/json \ -d {page:1,size:20}-v 会把请求头、响应头、TLS 握手过程全部打印出来我排查任何一个接口问题都会先跑一遍这个命令。真实开发中-H 可以写多个同一个头写两遍大部分服务端会取最后一个也有会合并的所以调试时不要指望靠重复头来模拟多值头。curl 还有一个常被忽略的点默认 curl 对多个 URL 会尽量复用 TCP 连接HTTP/1.1 Keep-Alive当你用curl URL1 URL2这种方式连续请求同一个站点时可以在输出里看到 Re-using existing connection 的字样这就是 HTTP 连接复用。理解了这个你就明白为什么服务器端的连接数不等于请求数——一个连接上可以跑成百上千个请求这也是 HTTP/1.1 相比 1.0 最大的性能改进。到了 HTTP/2更进一步一个连接上多个请求可以同时并行不用排队等前一个结束。2.5 不止浏览器嵌入式与桌面端怎么发HTTP很多人以为 HTTP 只有浏览器和服务器才用其实嵌入式、桌面端也大量在发 HTTP。STM32 这类 MCU 上常用 lwIP 协议栈里的 http client 组件或者用 cJSON 拼 JSON、自己按报文格式拼字符串再通过 socket 发送要求高一点会用 mbedTLS 做 HTTPS。Qt 桌面应用则直接用 QNetworkAccessManager一行request.setRawHeader(Authorization, token)就能给请求加头。前端 uni-app 生态里的 luch-request 请求库GET 请求配请求头也是在 header 对象里加字段比如request.get(url, { header: { Authorization: token } })原理和 curl 的 -H 完全一样。在这些场景里最容易犯的错是忘了 Host 头、忘了 Content-Length 要算准、以及 HTTP 头必须以\r\n而不是\n结尾。很多 C/C 手写 HTTP 的 Bug最后都出在这三个点上。3. 服务端的回话里藏着什么响应头拆解3.1 响应行与状态码串起来读HTTP 响应报文结构和请求报文对称响应行、响应头、空行、响应体。HTTP/1.1 200 OK Content-Type: application/json; charsetutf-8 Content-Length: 38 Cache-Control: no-store Set-Cookie: sessionabc123; Path/; HttpOnly {status:ok,data:{id:10086}}响应行三要素版本HTTP/1.1、状态码200、原因短语OK。状态码是机器读的原因短语是人读的不同服务器对同一状态码的原因短语可能略有不同比如有的返回200 Why Not但状态码本身是标准化的这就是为什么排障时要看码而不是看字。3.2 高频响应头逐个说清响应头作用实战要点Content-Type响应体 MIME 类型和编码浏览器渲染错误/乱码90% 和它有关Content-Length响应体字节数和请求一样用于切分报文边界Transfer-Encoding: chunked分块传输服务端不能预知长度时使用以 0 长度块结束Set-Cookie服务端种 Cookie属性 HttpOnly、Secure、SameSiteCache-Control缓存策略no-store、no-cache、max-age、public/privateETag资源版本标识配合 If-None-Match 做协商缓存Last-Modified资源最后修改时间配合 If-Modified-SinceLocation重定向目标地址配合 301/302/303/307/308Server服务器软件标识生产环境建议隐藏或伪装Access-Control-*CORS 跨域控制见 3.3Strict-Transport-SecurityHSTS 强制 HTTPS告诉浏览器以后只能走 HTTPSContent-Security-PolicyCSP 内容安全策略大幅降低 XSS 风险X-Content-Type-Options禁止 MIME 嗅探建议固定为 nosniffX-Frame-Options禁止页面被 iframe 嵌入防点击劫持挑几个重要的说。Content-Type 与乱码响应头写作charsetutf-8但页面仍乱码通常是因为 HTML 里又写了一个不同的 meta charset或者服务端实际输出的字节流编码和声明不一致。浏览器解码时响应头的 charset 优先级高于 HTML meta 标签。所以你的服务端如果统一输出 UTF-8别在业务代码里再造一个 GBK 的字符串出来。Cache-Control 与 ETag 的配合强缓存max-age是过期前根本不发请求协商缓存是每次发请求问服务器资源变没变没变返回 304变了返回 200 加新资源。ETag 是内容哈希Last-Modified 是时间戳一般 ETag 更准——Last-Modified 最低精度是秒同一秒内改了两次内容时间戳根本发现不了。Set-Cookie 属性HttpOnly 让 JavaScript 读不到防 XSS 偷 CookieSecure 只允许 HTTPS 下携带SameSiteLax/Strict 用来防 CSRF。我见过太多把 Session 放 Cookie 但没设这些属性的老系统基本等于把钥匙挂在门把手上。3.3 跨域场景下的响应头博弈跨域CORS是前端天天遇到的东西本质是浏览器基于同源策略做的一道安全检查。页面在 a.com用 fetch 请求 b.com 的接口浏览器会先看响应头里有没有允许 a.com 的Access-Control-Allow-Origin没有就报blocked by CORS policy虽然服务器其实已经返回了数据。对于非简单请求比如带自定义头 Authorization、Content-Type 为 application/json 的 POST浏览器还会先发一个 OPTIONS 预检请求问服务器允不允许我用这个方法、带这些头服务器用Access-Control-Allow-Methods和Access-Control-Allow-Headers回答。预检通过后才会发真实请求。开发时遇到 CORS 报错正确的排查顺序打开 DevTools看到底是预检失败还是真实请求失败。看响应头里是否带了Access-Control-Allow-*。看这几个头的值是否覆盖了请求的 Origin、Method、Headers。注意Access-Control-Allow-Origin不能是*的同时又带Access-Control-Allow-Credentials: true只要带 Cookies 就不能用通配符。3.4 自定义业务响应头与常见坑业务里经常用X-前缀定义自己的头比如X-Trace-Id链路追踪、X-RateLimit-Remaining限流剩余量。好处是调试特别方便——一个接口慢了看响应头里的 TraceId 就能去日志里捞全链路。三个坑必须知道HTTP 头有大小限制常见 Web 服务器默认单个头 8KB 到 16KB加起来几十 KB 就会报 431 Request Header Fields Too Large。别把大的业务数据放头里。头字段只能是 ASCII 字符中文字符要 URL 编码或 Base64 后放进去直接放中文会触发畸形请求。头会被中间层改写经过网关/负载均衡时部分头可能被丢弃、合并或追加尤其是 X-Forwarded-* 和 Cookie。自定义头命名别和标准头、网关保留头冲突否则你在服务端读到的和客户端发的可能完全不一样。4. 状态码实战手册从200到524一次讲透4.1 五类状态码的整体认知状态码第一位数字决定类别1xx信息请求已收到继续处理。常见的 100 Continue 表示body 可以发了101 Switching Protocols 用于 WebSocket 升级。2xx成功请求成功。200、201、204、206。3xx重定向需要进一步操作。301、302、303、304、307、308。4xx客户端错误请求有问题是客户端或调用方的责任。5xx服务器错误请求没错是服务器和上游的责任。记住这个分类后排障的第一步就能先划分责任4xx 先查自己发的请求5xx 先查服务器和上游。很多人拿到 403 就猛查服务器其实是自己 UA 被拦了方向完全反了。4.2 高频状态码逐一定义与场景2xx200 OK最正常。但注意很多接口即使业务失败也返回 200 加错误码这是国内 API 的常见风格。判断业务成败不能只看 HTTP 状态码。201 Created创建成功通常配合 Location 指向新资源。204 No Content成功但没有 body。删除操作、保存操作常用避免传一个空 JSON。206 Partial Content部分内容。Range 头请求的视频切片、断点续传靠它。3xx301 Moved Permanently永久重定向。换域名、http 跳 https 常用。搜索引擎会把权重转到新地址。302 Found临时重定向。语义上是这次先去别处下次还来原来地址。303 See OtherPOST 之后让你去 GET 结果页PRG 模式防刷新重复提交。304 Not Modified协商缓存命中没有新的 body浏览器直接用自己的缓存。307/308和 302/301 语义相同但规定重定向时不能把 POST 改成 GET。老系统改接口地址时别把 POST 的 307/308 处理成 301否则请求体丢了。4xx400 Bad Request报文格式错误或参数非法。服务端解析不了 body、JSON 格式错了都会给 400。401 Unauthorized未认证。没登录或 token 失效。规范做法是响应头带WWW-Authenticate提示客户端怎么认证。403 Forbidden已认证但无权限或者服务器就是不让你访问。UA 被 WAF 拦截、IP 被拉黑、目录无权限都可能 403。404 Not Found路径不存在。可能是路由没配、资源被删、前端请求路径写错也可能是服务端故意用 404 掩盖资源存在防目录探测。405 Method Not Allowed方法不对。接口只接受 POST你发了 GET 就 405。响应头 Allow 字段会列出允许的方法。408 Request Timeout服务器等请求等了太久。409 Conflict资源冲突常见于并发创建同名资源、版本号冲突。413 Payload Too Largebody 太大超过了上传限制。429 Too Many Requests限流了。看响应头 Retry-After 能知道多久后再试。5xx500 Internal Server Error服务器内部异常代码抛异常、进程崩溃都可能。api call failed after 3 retries: http 500: llama-server process has terminated这种报错就是本地推理服务进程挂掉了上游调用方重试三次全撞上 500。502 Bad Gateway网关/入口拿不到上游的合法响应。常见原因上游进程挂了、端口不对、防火墙拦截、上游响应格式非法比如 HTTP 头少个\r\n。503 Service Unavailable服务暂时不可用。常见于启动中、过载保护、主动下线的实例。配合 Retry-After 告诉客户端什么时候再来。504 Gateway Timeout网关连上了上游但上游在规定时间内没返回。和 502 的区别就是连没连上。524 A Timeout Occurred这个状态码不在 RFC 标准里是 CDN 厂商自创的——网关等源站响应等到了超时阈值之上主动断开连接。看到 524基本就是源站太慢该让源站背锅。4.3 三组最容易混的状态码对比对比组区分要点301 vs 302永久 vs 临时301 会被客户端缓存302 通常不会302 vs 303 vs 307302 允许把 POST 变 GET各浏览器处理不一致303 强制改 GET307 强制保留原方法401 vs 403401你没证明你是谁403服务器知道你是谁但你不许进502 vs 504502上游连接失败/响应非法504连接建立了但超时了403 vs 429403权限/规则拒绝429你请求太频繁被限流4.4 用状态码定位线上问题的完整思路结合几个真实报错讲案例。案例一unavailableinvalidchannel: http 403 forbidden for channel anaconda/pkgs/main。我装 Python 环境时经常看到conda 配了不存在的 channel 地址镜像源直接返回 403。解法不是去改权限而是把 channel 地址改成官方或镜像站的有效地址再清 conda 缓存。案例二unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:1572。本地调试工具请求本机端口返回 502。这时候我会先问端口上到底有没有服务在监听没有就是服务没起来有就再看是不是服务只监听了 IPv6 而请求走了 IPv4或反过来或者本机防火墙拦了回环流量。案例三imaauthapi start http 524。认证 API 启动后请求直接 524说明源站进程可能根本没监听端口或握手后不响应。优先看源站启动日志而不是反复重启。完整排查思路先用 curl -v 复现记录状态码、响应头、响应体。判断责任方是客户端、网关还是源站4xx 看请求5xx 看服务。有响应体先读响应体很多框架的错误信息写得比状态码细得多。看响应头的 Server 字段判断是哪个组件返回的这个错误Nginx 返回的 502 和 Spring Boot 自带错误页的格式完全不同。查网关/接入层日志和上游日志把 TraceId 对上。修完后用同样的 curl 命令验证并把结果存档。5. 数据包结构从以太网帧到TLS密文5.1 HTTP报文在TCP流中的真实形态当抓包工具抓到一段 HTTP 明文时你会看到它就是一段 ASCII 文本字段之间用\r\n分隔GET /index.html HTTP/1.1\r\n Host: example.com\r\n User-Agent: curl/8.0\r\n Accept: */*\r\n \r\n注意是\r\nCRLF不是\n。很多手写 HTTP 的 C 程序只发了\n严格解析的服务器会认为报文畸形。请求头结束后的空行本身就是\r\n表示头结束了下面如果有 body 就是 body。与直觉相反GET 请求通常没有 body但请求头后面依然有一个空行。5.2 在Wireshark里看数据包的分层用 Wireshark 打开一个 HTTP 抓包文件选中一个包协议树从上到下一般是层作用和 HTTP 的关系Frame物理帧信息包含抓包时间、帧长度Ethernet II以太网帧头源/目的 MAC 地址局域网转发用IPv4网络层源/目的 IP跨网段寻址TCP传输层源/目的端口、序号、确认号HTTP 载荷在这里HTTP应用层请求行、请求头、body 的明文内容Wireshark 里几个常用过滤表达式http所有 HTTP 包、http.request只看 HTTP 请求、http.response只看 HTTP 响应、tcp.port 443只看 443 端口流量、tcp.stream eq 0追踪某条 TCP 连接的全部包。5.3 HTTPS为什么抓到的是密文TLS记录层结构HTTPS 抓包抓到的不是 HTTP 明文而是 TLS 记录层TLS Record Layer。每个 TLS 记录开头有 5 个字节的明文头类型1 字节、版本2 字节、长度2 字节后面跟着载荷。在 Wireshark 里看TLS 协议树大概是TLS - TLSv1.3 Record Layer: Handshake / Application Data。TLS 记录类型常见五种Handshake22握手消息如 ClientHello、ServerHello、Certificate。ChangeCipherSpec20表示从下一条开始要加密了。TLS 1.3 里实际已基本废弃仅保留兼容。ApplicationData23加密后的业务数据也就是被保护的 HTTP 报文。Alert21告警比如证书错误、关闭通知。Heartbeat24心跳检测。抓包时你能看到握手的 ClientHello、ServerHello 里大部分字段还是明文的比如域名、加密套件但从 ChangeCipherSpec 之后所有 ApplicationData 都是密文——这就是HTTPS 抓包抓到一堆看不懂的字节的原因。5.4 解密HTTPS流量两大常用手法调试自己的客户端、服务端时有两种正经手段能看到 HTTPS 明文。手法一本地中间人调试工具。我用过的 Charles、Fiddler 这类工具会生成一个本地根证书你把它安装到系统信任区后工具会和目标服务器建立一条 TLS 连接和你再建立一条 TLS 连接于是它可以同时看到两个方向的明文。本质上是把一条 TLS 拆成两段。注意这只允许用在调试自己客户端或测试环境如果在公共网络里安装来路不明的根证书等于把 HTTPS 的信任链拱手让人。手法二导出会话密钥给 Wireshark。现代浏览器和多数网络库支持设置环境变量SSLKEYLOGFILE把每次 TLS 握手生成的会话密钥写进文件Wireshark 在协议设置里指定这个文件就能把抓到的密文用密钥解出来。这个方式不用装根证书对原生程序调试很友好。缺点是密钥文件非常敏感谁拿到它谁就能解密对应会话用完立刻删。5.5 HTTP/2、HTTP/3的数据包结构变化HTTP/1.1 是文本协议肉眼可读HTTP/2 改成二进制分帧一个 TCP 连接里可以同时跑多个请求流每个流由多个帧组成。常见帧类型有 HEADERS头部、DATA数据、SETTINGS设置、PING、RST_STREAM 等。头部还会用 HPACK 压缩所以抓包看 HTTP/2 时请求头不是一行行明文而是HEADERS 帧里的键值列表。这也是为什么浏览器和很多站点已经用 h2但你抓包感觉不像以前那么直白了。HTTP/3 则更进一步把传输层从 TCP 换成了基于 UDP 的 QUIC连 TLS 握手也和连接建立合并在一起0-RTT/1-RTT进一步减少了握手延迟。数据包结构上HTTP/3 的帧再套 QUIC 的封装再套 UDP 头。虽然结构复杂了但排障思路没变——依然先分清哪一层出问题。6. 实战复盘三个状态码串起来的十小时排障6.1 第一轮502与网关后端失联某天下午测试环境所有走网关的接口陆续开始报 502 Bad Gateway。我第一反应是后端服务挂了但登录服务器用 curl 直连后端 IP 加端口发现返回 200 完全正常。这说明问题不在后端应用而在网关和后端之间。接着看网关的错误日志里面出现大量upstream returned http 403 forbidden。这个报错的意思是网关向上游转发请求时上游返回了 403网关把这次异常回复转成了对客户端的 502。继续排查发现网关配置的健康检查路径被改成了/healthz而后端服务根本没有这个路径健康检查返回 404网关就认为后端不可用把请求全部判失败。最后定位到根因是下午有人改配置时把健康检查路径写错了。改回来之后502 立刻消失。这轮排障花了三个小时教训就一句话502 出现时先分清是谁报的 502直连后端能通那问题就在网关和后端的连接判断上。6.2 第二轮403与访问控制同一个体系里另一个接口报 403。仔细看 403 响应体里面有个 JSON 提示是 WAF 拦截理由是 User-Agent 命中黑名单。原来同事写了个内部工具把 UA 写成了python-requests/2.31.0测试环境的 WAF 规则对常见脚本 UA 做拦截于是整个工具都被 403。这个案例和前面 conda 的 403 本质上一样——403 不一定是你没权限也可能是你看起来不像人。遇到 403先别急着改权限把响应体读完看看是 WAF 拦的、应用层拦截的还是真没权限。6.3 第三轮524与源站超时还有一类情况接口偶尔返回 524。这个状态码是 CDN 厂商特有的含义是CDN 节点等源站响应等到了超时上限。查源站日志请求其实到了但处理了接近 60 秒超过了 CDN 的等待阈值。再看代码接口里有个同步调用第三方服务的逻辑第三方服务抖动时整个接口就卡住。最后方案把第三方调用改成异步加缓存接口本身控制在 2 秒内返回。调整之后 524 再也没出现过。6.4 复盘排障顺序与工具清单把三件事串起来背后是一个通用的排查顺序复现能稳定复现的问题都好查稳定复现不了先抓现场日志、抓包、监控。分层DNS/TCP/TLS/HTTP 逐层确认故障层。看状态码分类4xx 查请求5xx 查服务。看响应头Server 字段判断是谁返回的TraceId 对日志。看响应体很多时候错误信息就写在 body 里。查上下游日志把网关日志、应用日志、依赖服务日志对齐。工具清单curl -v最优先能复现、能看到完整头。浏览器 DevTools Network看前端请求、CORS、时序。tcpdump服务器上抓包取证。Wireshark分析抓包文件看 TCP/TLS/HTTP 分层。JMeter压测与录制脚本。JMeter 录制 HTTPS 时要先把录制组件的根证书导入 JVM 的信任库再把浏览器或客户端的流量指向 JMeter 监听端口才能录到 HTTPS 请求否则录到的全是 TLS 握手和 CONNECT。7. 请求头里的攻防从一道CTF题看HTTP头注入7.1 头注入是怎么发生的HTTP 头以\r\n作为字段分隔符如果服务端把用户输入直接拼进响应头且没有过滤\r\n攻击者就能注入新的响应头甚至响应体。举个典型场景一个重定向接口这样拼 LocationLocation: http://example.com/redirect?url用户输入如果用户输入的是/safe\r\nSet-Cookie: admin1拼出来的响应头就变成了两行攻击者可以因此种 Cookie、注入任意响应头甚至直接注入响应体内容两个\r\n\r\n之后就是响应体造成 XSS。这就是 CRLF 注入也叫 HTTP 响应拆分。7.2 从[极客大挑战 2019] http看这类题型CTF 里很多送分 Web 题就是欺负你没认真看过请求头。比如极客大挑战这题考点就是服务端检查了 User-Agent、Referer、X-Forwarded-For要求你依次改成指定值才给 flag。解题思路很简单先用 curl -v 访问看响应提示它想要什么。按提示加对应请求头curl -v http://target.challenge/ \ -H User-Agent: ClueBrowser/1.0 \ -H Referer: https://clue.example.com \ -H X-Forwarded-For: 127.0.0.1如果响应要求必须从本地访问就把 X-Forwarded-For 或 X-Real-IP 改成 127.0.0.1。这类题真正考的是协议熟悉度知道 Host、UA、Referer、XFF 各自是干嘛的会手写 curl 请求头就能解。这也是我建议所有 Web 方向的同学把 HTTP 报文格式吃透的原因——安全题的入口往往就是你对协议字段的理解。7.3 生产环境的头注入防护清单用真实项目经验说防 CRLF 注入就三件事所有要拼进响应头的用户输入先做校验把\r\n直接拒绝。能用框架 API 设置头就不要自己拼字符串。主流框架对设置 Header 的 API 会做换行过滤但如果你是用底层 socket 手写响应头必须自己处理。安全响应头建议全开Content-Security-Policy、X-Content-Type-Options: nosniff、X-Frame-Options: DENY、Strict-Transport-Security。前两个能挡住很大一部分和头注入相关的 XSS 与 MIME 嗅探风险。从我自己的经验说学 HTTP/HTTPS 最有效的方式不是背字段而是带着问题去抓包、去改请求头、去看响应头。每次排查问题我都会把完整的请求头、响应头复制到笔记里存够一两个月再回头看很多规律自己就浮出来了。建议新手从浏览器 DevTools 的Copy as cURL开始先把一个正常请求完整还原出来再逐字段删掉看服务端反馈变化感受每个头的分量。遇到 504/524 这类和时间相关的状态码记得先问一句这个超时到底是被谁定义的答案通常不在浏览器里而在网关和源站的配置中。
返回列表