decolua/9router · error

[mitm] http2 stream error: ${e.message}

Error message

[mitm] http2 stream error: ${e.message}

What it means

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.

Source

Thrown at src/mitm/server.js:239

      res.writeHead(status, outHeaders);
      if (dumper) dumper.writeHeader(status, outHeaders);

      const chunks = [];
      stream.on("data", chunk => {
        if (dumper) dumper.writeChunk(chunk);
        if (onResponse) chunks.push(chunk);
        res.write(chunk);
      });
      stream.on("end", () => {
        if (dumper) dumper.end();
        if (!res.writableEnded) res.end();
        if (onResponse) try { onResponse(Buffer.concat(chunks), outHeaders); } catch {}
        try { client.close(); } catch {}
        resolve();
      });
    });
    stream.once("error", (e) => {
      err(`[mitm] http2 stream error: ${e.message}`);
      if (dumper) { dumper.writeChunk(`\n[ERROR h2-stream] ${e.message}\n`); dumper.end(); }
      if (!res.headersSent) res.writeHead(502);
      if (!res.writableEnded) res.end();
      try { client.close(); } catch {}
      resolve();
    });
  });
}

// Fallback: raw https.request HTTP/1.1 with custom DNS (bypasses /etc/hosts MITM loop)
async function passthroughHttps(req, res, bodyBuffer, headers, targetHost, onResponse, dumper) {
  const targetIP = await resolveTargetIP(targetHost);
  const forwardReq = https.request({
    hostname: targetIP,
    port: 443,
    path: req.url,
    method: req.method,
    headers,

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Read the h2 error name (e.g. PROTOCOL_ERROR vs CANCEL) — CANCEL is usually client-aborted and benign.
  2. Retry the request; REFUSED_STREAM under load is typically transient.
  3. If PROTOCOL_ERROR persists, force HTTP/1.1 for that host or update the MITM server code.
  4. Check upstream provider status for GOAWAY/reset storms.

Example fix

// before
stream.once('error', ...) → NGHTTP2_PROTOCOL_ERROR
// after: disable h2 for the problem host (fall back to passthroughHttps)
ALPNProtocols: ['http/1.1'] // in the tls.connect options
Defensive patterns

Strategy: retry

Validate before calling

// distinguish client-abort (benign) from protocol errors before reacting
const benign = ['NGHTTP2_CANCEL', 'NGHTTP2_REFUSED_STREAM'];
if (!benign.includes(e.code)) console.warn(`h2 stream ${e.code} for ${targetHost}`);

Type guard

function isStreamReset(e) {
  return e && typeof e.code === 'string' && e.code.startsWith('NGHTTP2_');
}
function isClientAborted(e) {
  return isStreamReset(e) && (e.code === 'NGHTTP2_CANCEL' || e.code === 'NGHTTP2_REFUSED_STREAM');
}

Try / catch

stream.once('error', (e) => {
  if (isClientAborted(e)) { try { client.close(); } catch {} ; return resolve(); } // benign
  err(`[mitm] http2 stream error: ${e.message}`);
  if (!res.headersSent) res.writeHead(502);
  if (!res.writableEnded) res.end();
  try { client.close(); } catch {}
  resolve();
});

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/0ae0b8ccacdb466f. Report an issue: GitHub.