{"record":{"id":"0ae0b8ccacdb466f","repo":"decolua/9router","slug":"mitm-http2-stream-error-e-message","errorCode":null,"errorMessage":"[mitm] http2 stream error: ${e.message}","messagePattern":"\\[mitm\\] http2 stream error: (.+?)","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"src/mitm/server.js","lineNumber":239,"sourceCode":"      res.writeHead(status, outHeaders);\n      if (dumper) dumper.writeHeader(status, outHeaders);\n\n      const chunks = [];\n      stream.on(\"data\", chunk => {\n        if (dumper) dumper.writeChunk(chunk);\n        if (onResponse) chunks.push(chunk);\n        res.write(chunk);\n      });\n      stream.on(\"end\", () => {\n        if (dumper) dumper.end();\n        if (!res.writableEnded) res.end();\n        if (onResponse) try { onResponse(Buffer.concat(chunks), outHeaders); } catch {}\n        try { client.close(); } catch {}\n        resolve();\n      });\n    });\n    stream.once(\"error\", (e) => {\n      err(`[mitm] http2 stream error: ${e.message}`);\n      if (dumper) { dumper.writeChunk(`\\n[ERROR h2-stream] ${e.message}\\n`); dumper.end(); }\n      if (!res.headersSent) res.writeHead(502);\n      if (!res.writableEnded) res.end();\n      try { client.close(); } catch {}\n      resolve();\n    });\n  });\n}\n\n// Fallback: raw https.request HTTP/1.1 with custom DNS (bypasses /etc/hosts MITM loop)\nasync function passthroughHttps(req, res, bodyBuffer, headers, targetHost, onResponse, dumper) {\n  const targetIP = await resolveTargetIP(targetHost);\n  const forwardReq = https.request({\n    hostname: targetIP,\n    port: 443,\n    path: req.url,\n    method: req.method,\n    headers,","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/mitm/server.js#L221-L257","documentation":"In passthroughHttp2, the HTTP/2 stream (opened with client.request after the session connects) errors out. The handler logs '[mitm] http2 stream error: <message>', records it in the dump, responds 502 if headers were not yet sent, ends the response, closes the client, and resolves.","triggerScenarios":"The h2 stream is reset (RST_STREAM/NGHTTP errors like PROTOCOL_ERROR, REFUSED_STREAM, CANCEL) after the client session connected — typically the upstream rejects or aborts the request, or the request stream is misused (headers/body sent after close).","commonSituations":"Upstream server sends GOAWAY or RST_STREAM under load; HTTP/2 protocol incompatibility with a specific provider; client aborted the request causing stream CANCEL propagation.","solutions":["Read the h2 error name (e.g. PROTOCOL_ERROR vs CANCEL) — CANCEL is usually client-aborted and benign.","Retry the request; REFUSED_STREAM under load is typically transient.","If PROTOCOL_ERROR persists, force HTTP/1.1 for that host or update the MITM server code.","Check upstream provider status for GOAWAY/reset storms."],"exampleFix":"// before\nstream.once('error', ...) → NGHTTP2_PROTOCOL_ERROR\n// after: disable h2 for the problem host (fall back to passthroughHttps)\nALPNProtocols: ['http/1.1'] // in the tls.connect options","handlingStrategy":"retry","validationCode":"// distinguish client-abort (benign) from protocol errors before reacting\nconst benign = ['NGHTTP2_CANCEL', 'NGHTTP2_REFUSED_STREAM'];\nif (!benign.includes(e.code)) console.warn(`h2 stream ${e.code} for ${targetHost}`);","typeGuard":"function isStreamReset(e) {\n  return e && typeof e.code === 'string' && e.code.startsWith('NGHTTP2_');\n}\nfunction isClientAborted(e) {\n  return isStreamReset(e) && (e.code === 'NGHTTP2_CANCEL' || e.code === 'NGHTTP2_REFUSED_STREAM');\n}","tryCatchPattern":"stream.once('error', (e) => {\n  if (isClientAborted(e)) { try { client.close(); } catch {} ; return resolve(); } // benign\n  err(`[mitm] http2 stream error: ${e.message}`);\n  if (!res.headersSent) res.writeHead(502);\n  if (!res.writableEnded) res.end();\n  try { client.close(); } catch {}\n  resolve();\n});","preventionTips":["Treat CANCEL/REFUSED_STREAM as client-side aborts, not server faults.","Fall back to HTTP/1.1 for hosts with persistent PROTOCOL_ERROR.","Retry once on REFUSED_STREAM — it is often transient under load.","Keep the h2 client settings (frame sizes, concurrency) within upstream defaults."],"tags":["http2","stream-reset","mitm","network"],"backgroundTag":"http2-stream-reset","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}