← 中文资源

简体中文版本

HTTP/2 — 会话中期的 ERR_HTTP2_STREAM_ERROR 与 GOAWAY

诊断会话中期的 HTTP/2 流重置与 GOAWAY 终止,区分协议错误与优雅停机,并隔离行为异常的中间盒。

阅读英文原文

症状

  帧轨迹就是全部故事:GOAWAY (last_stream_id=1141, error_code=NO_ERROR) 是优雅停机——1141 号之前的流已被处理,客户端应把 1142 及以后放到新连接重试,这是生命周期不是故障;RST_STREAM (error_code=PROTOCOL_ERROR) 打在新鲜流上则是真违规——路径上某个组件在破坏帧协议。消除法隔离元凶:直连源站的 HTTP/2 干净而公共路径失败,是中间层;同一路径上 HTTP/1.1 干净而 HTTP/2 失败,则精确锁定中间盒的 HTTP/2 实现。长连接上的 ERR_HTTP2_STREAM_ERROR、与特定代理或 WAF 相关的失败,都是这个分类问题的外显。

诊断思路

  先用 nghttp -v 观察帧:类型加错误码把优雅停机与真实违规分开;再做旁路实验逐层消除,定位行为异常的组件;最后做协议对照——1.1 过 2.0 挂即中间盒的 HTTP/2 实现,不是应用。WAF 重编码响应体破坏 HPACK 或帧不变量、内容长度与 trailer 分歧只在中间层触发,都是“中间盒违规”家族。

根因

  HTTP/2 在严格的帧不变量下多路复用流。违反者——流控实现错误、帧顺序错乱、重编码破坏 HPACK——会重置流或用 GOAWAY 撕裂会话。不同的错误码把这些故障与行为良好的服务器发出的优雅 GOAWAY 停机区分开。

修复与验证

  绕过或升级行为异常的中间盒;客户端正确处理 GOAWAY 的 last-stream-id 语义——高于该值的流重试、低于它的流等待完成;连接寿命钉在中间盒空闲超时之下;在问题组件修复期间,把 HTTP/1.1 回退限定在失败路径上(全局关 HTTP/2 会到处放弃多路复用)。验证时长连接 HTTP/2 会话在代表性流量下零协议错误,优雅 GOAWAY 处理让在途流无客户端失败地完成。

代码示例

$ nghttp -v https://api.example.com/data
[ 0.012] recv GOAWAY frame (last_stream_id=1141, error_code=NO_ERROR)
[ 0.014] recv RST_STREAM frame (stream_id=1142, error_code=PROTOCOL_ERROR)
const http2 = require('node:http2');

const session = http2.connect(origin);
session.on('goaway', (code, lastStreamID) => {
  // streams above lastStreamID were never processed: safe to retry
  // streams at or below it: already committed upstream — do not blind-retry
  retryAbove(lastStreamID, { idempotentOnly: true });
  session.close();
});