fix(sse): prevent client-abort (aborted/ECONNRESET) from crashing the server (#11556)

Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
This commit is contained in:
Az1muth
2026-08-26 15:11:14 +04:00
committed by GitHub
parent c63e519b09
commit 2d185de9d9
7 changed files with 332 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
"use strict";
/**
* Re-export of the shared client-abort guard so the Node-only dev server
* (`run-next.mjs`) keeps importing from its original relative path. The real
* implementation lives in `src/shared/utils/httpClientAbortGuard.mjs` (importable
* from both `.mjs` and the TypeScript servers under `src/`, tsconfig allowJs).
*
* @module
*/
export {
isClientAbortError,
shouldSwallowUncaught,
attachRequestStreamGuards,
installProcessCrashGuard,
} from "../../src/shared/utils/httpClientAbortGuard.mjs";

View File

@@ -16,6 +16,10 @@ import { isTurbopackCacheCorruption, purgeAllTurbopackCaches } from "./turbopack
import { randomUUID } from "node:crypto";
import { getMainServerTimeoutConfig } from "./main-server-timeouts.mjs";
import { createSystemdNotifier } from "./systemd-notify.mjs";
import {
attachRequestStreamGuards,
installProcessCrashGuard,
} from "./httpClientAbortGuard.mjs";
const { maybeHandleDisallowedMethod } = methodGuard;
const { wrapRequestListenerWithHeadResponseGuard } = headResponseGuard;
@@ -162,6 +166,13 @@ async function prepareWithHeal() {
}
async function start() {
// Safety net: a client aborting a connection (browser navigation, HMR reconnect,
// Back/Forward cache) can emit `Error: aborted`/`ECONNRESET` on the request
// stream. Without this the single missed listener becomes an uncaughtException
// that takes the whole server down — surfacing as a wall of ERR_CONNECTION_REFUSED
// after login. Benign aborts are swallowed; genuine errors still crash loudly.
installProcessCrashGuard();
await prepareWithHeal();
const requestHandler = nextApp.getRequestHandler();
@@ -176,6 +187,10 @@ async function start() {
const server = http.createServer(
wrapRequestListenerWithHeadResponseGuard((req, res) => {
// Absorb client-abort errors (browser closes the socket during
// navigation/HMR/bfcache) on the request/response streams so they never
// surface as an uncaughtException that kills the whole server (#fix-dev-server-aborted).
attachRequestStreamGuards(req, res);
if (maybeHandleDisallowedMethod(req, res)) return;
// Stamp the real TCP peer IP before Next sees the request, so the authz
// middleware can decide LOCAL_ONLY locality without trusting the Host header.