From b1c82c317884177b43da00cda67f537788344fd6 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:08:35 +0000 Subject: [PATCH 01/15] node:http: count response writes as activity for the socket inactivity timeout A write on a ServerResponse goes through the native response handle and never reached the socket's inactivity timer, so server.timeout, req.setTimeout(), res.setTimeout() and socket.setTimeout() fired in the middle of a response that was still streaming. Record when a response last wrote to the connection and let the timer expiry move its deadline to that write. A write after a 'timeout' that kept the socket open starts the timer again, like timer.refresh() does in Node.js. --- src/js/node/_http_server.ts | 51 +++++++ .../http/node-http-server-timeouts.test.ts | 139 ++++++++++++++++++ 2 files changed, 190 insertions(+) diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index ba91ed192fe3..3e3a7aea305a 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1355,6 +1355,17 @@ const kKeepAliveTimeoutSet = Symbol("keepAliveTimeoutSet"); // the socket timer on every response; onSocketTimeoutTimerExpired reads it to // grant the remaining idle budget when the timer actually fires. const kKeepAliveIdleStart = Symbol("keepAliveIdleStart"); +// When a response last wrote to the connection. Node.js restarts the socket's +// inactivity timer on every write (net.Socket._writeGeneric and onWriteComplete +// call _unrefTimer()). noteResponseWrite records the write instead, for the same +// reason as kKeepAliveIdleStart, and onSocketTimeoutTimerExpired moves the +// deadline when the timer fires. +// https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L1019 +// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/stream_base_commons.js#L101 +const kLastResponseWrite = Symbol("lastResponseWrite"); +// Set once 'timeout' has been emitted: the timer is spent, and like +// timer.refresh() in Node.js the next response write starts it again. +const kSocketTimeoutEmitted = Symbol("socketTimeoutEmitted"); // HTTP/1.1 pipelining (responses queued behind an in-flight response): // - on the socket: array of queued ServerResponses, in arrival order // - on a queued response: { ops, bytes, needDrain, ended, isAncient } while it @@ -1462,6 +1473,22 @@ function onSocketTimeoutTimerExpired(socket) { return; } } + // A response wrote to the connection inside the window: the inactivity + // period starts at that write. Re-arm this timer and move its start back + // to the write (the deadline is _idleStart + _idleTimeout, like Node.js). + // A new, shorter timer in the slot would make the next _unrefTimer() + // refresh that shorter interval. + const lastWrite = socket[kLastResponseWrite]; + if (lastWrite !== undefined) { + socket[kLastResponseWrite] = undefined; + const sinceWrite = DateNow() - lastWrite; + const timer = socket[kSocketTimeoutTimer]; + if (sinceWrite < socket.timeout && timer !== undefined) { + timer.refresh(); + if (sinceWrite > 0) timer._idleStart -= sinceWrite; + return; + } + } // A fired keep-alive idle timer is dead; drop the reference so the next // response-finish re-arms via setTimeout instead of trusting a fired // timer whose _idleTimeout still matches (a 'timeout' listener may keep @@ -1506,6 +1533,8 @@ function getNodeHTTPServerSocket() { [kStreamingEnabled] = false; [kBoundOnAbort] = null; [kKeepAliveIdleStart] = undefined; + [kLastResponseWrite]: number | undefined = undefined; + [kSocketTimeoutEmitted] = false; [kBytesWritten] = 0; [kHandle]; [kUpgradeIncoming]: import("node:http").IncomingMessage | undefined = undefined; @@ -1724,6 +1753,7 @@ function getNodeHTTPServerSocket() { this._unrefTimer(); return; } + this[kSocketTimeoutEmitted] = true; this.emit("timeout"); } _unrefTimer() { @@ -2667,6 +2697,7 @@ function advanceResponsePipeline(server, socket) { if (kind === "raw") { // Buffered 1xx bytes: route through the same AsyncSocket buffer the // response's own writeHead/end use so they precede the final response. + noteResponseWrite(res); handle.writeInformational(op[1], op[2]); if (typeof op[3] === "function") process.nextTick(op[3]); } else if (kind === "write") { @@ -3000,6 +3031,19 @@ Object.defineProperty(ServerResponse.prototype, "headersSent", { }, }); +// A response write is activity on the connection's inactivity timeout +// (socket.setTimeout / server.timeout), see kLastResponseWrite. +function noteResponseWrite(res) { + const socket = res[kSocket]; + if (socket == null || !socket.timeout) return; + if (socket[kSocketTimeoutEmitted]) { + socket[kSocketTimeoutEmitted] = false; + socket._unrefTimer(); + } else { + socket[kLastResponseWrite] = DateNow(); + } +} + ServerResponse.prototype._writeRaw = function (chunk, encoding, callback) { if (!this[kHandle]) { // Standalone path: OutgoingMessage._writeRaw buffers to outputData while @@ -3024,6 +3068,7 @@ ServerResponse.prototype._writeRaw = function (chunk, encoding, callback) { // Write through the response handle's AsyncSocket buffer (same path as // writeHead/end) so 1xx lines share ordering with the final response bytes; // socket.write() would land in the socket handle's separate stream buffer. + noteResponseWrite(this); this[kHandle].writeInformational(chunk, encoding); if (typeof callback === "function") process.nextTick(callback); return true; @@ -3124,6 +3169,7 @@ ServerResponse.prototype.writeContinue = function (cb) { this._sent100 = true; return; } + noteResponseWrite(this); const native = this.socket?.[kHandle]?.response; if (native) native.writeContinue(); else this[kHandle]?.writeContinue?.(); @@ -3223,6 +3269,7 @@ ServerResponse.prototype.end = function (chunk, encoding, callback) { // and will not throw or emit an error return true; } + noteResponseWrite(this); const sentState = NodeHTTPHeaderState.sent; if (headerState !== sentState) { { @@ -3393,6 +3440,7 @@ ServerResponse.prototype.write = function (chunk, encoding, callback) { return true; } + noteResponseWrite(this); if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) { handle.cork(() => { const renderedHeaders = renderNativeHeaders(this); @@ -3577,6 +3625,7 @@ ServerResponse.prototype._send = function (data, encoding, callback, _byteLength return OutgoingMessagePrototype._send.$apply(this, arguments); } + noteResponseWrite(this); if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) { handle.cork(() => { const renderedHeaders = renderNativeHeaders(this); @@ -3707,6 +3756,7 @@ ServerResponse.prototype.flushHeaders = function () { const handle = this[kHandle]; if (handle) { + noteResponseWrite(this); if (this[headerStateSymbol] === NodeHTTPHeaderState.assigned) { this[headerStateSymbol] = NodeHTTPHeaderState.sent; @@ -3787,6 +3837,7 @@ function callWriteHeadIfObservable(self, headerState, fromEnd?) { } function allowWritesToContinue() { + noteResponseWrite(this); this._callPendingCallbacks(); this.emit("drain"); } diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index f526143895c8..f5fcad3e932e 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -423,3 +423,142 @@ describe("node:http server timeout enforcement", () => { } }); }); + +// A write on the response is activity on the connection, like a read: Node's +// net.Socket restarts its inactivity timer in _writeGeneric. Each probe sends +// one GET on a raw TCP socket and stays silent, so only the response side can +// keep the connection active. The timing checks are one-sided: 'timeout' must +// not come sooner than a full period after the last write. +describe("node:http response writes restart the socket inactivity timeout", () => { + const TIMEOUT = 400; + // The server times the write with Date.now() and the timer runs on the monotonic + // clock, both in whole milliseconds. A server that ignores the write fires + // TIMEOUT / 2 early, far outside this. + const SLACK = 50; + + async function get(port: number, onClose: (received: string) => void) { + const client = net.connect(port, "127.0.0.1"); + let received = ""; + client.on("data", chunk => (received += chunk.toString("latin1"))); + client.on("error", () => {}); + client.on("close", () => onClose(received)); + await once(client, "connect"); + client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); + return client; + } + + test.concurrent.each(["server.timeout", "res.setTimeout()", "req.setTimeout()"])( + "%s does not fire while the response keeps writing", + async armedBy => { + const events: string[] = []; + const server = http.createServer((req, res) => { + if (armedBy === "res.setTimeout()") res.setTimeout(TIMEOUT); + if (armedBy === "req.setTimeout()") req.setTimeout(TIMEOUT); + req.socket.on("timeout", () => events.push("timeout")); + res.writeHead(200, { "Content-Type": "text/plain" }); + // Stream for longer than the timeout, one chunk every TIMEOUT / 8. + const startedAt = performance.now(); + const interval = setInterval(() => { + if (performance.now() - startedAt < TIMEOUT * 1.5) { + res.write("chunk\n"); + return; + } + clearInterval(interval); + events.push("end"); + res.end("done\n"); + }, TIMEOUT / 8); + res.on("close", () => clearInterval(interval)); + }); + if (armedBy === "server.timeout") server.timeout = TIMEOUT; + // The idle connection is closed one more period after the response. + server.keepAliveTimeout = TIMEOUT; + server.keepAliveTimeoutBuffer = 0; + const port = await listen(server); + const { promise: closed, resolve: onClosed } = Promise.withResolvers(); + const client = await get(port, onClosed); + try { + const received = await closed; + expect({ events, complete: received.endsWith("done\n\r\n0\r\n\r\n") }).toEqual({ + events: ["end", "timeout"], + complete: true, + }); + } finally { + client.destroy(); + server.closeAllConnections(); + server.close(); + } + }, + ); + + const activities: Record void> = { + "res.write()": res => res.write("chunk\n"), + "res.end()": res => res.end("done\n"), + "res.flushHeaders()": res => res.flushHeaders(), + "res.writeProcessing()": res => res.writeProcessing(), + "res.writeContinue()": res => res.writeContinue(), + }; + + test.concurrent.each(Object.keys(activities))("the inactivity period restarts at %s", async name => { + let activityAt = 0; + const { promise: timedOut, resolve: onTimedOut } = Promise.withResolvers(); + const server = http.createServer((req, res) => { + setTimeout(() => { + activityAt = performance.now(); + activities[name](res); + }, TIMEOUT / 2); + }); + server.timeout = TIMEOUT; + // No keep-alive timeout: server.timeout stays armed after res.end(). + server.keepAliveTimeout = 0; + server.on("timeout", socket => { + // Only on a machine that stalls past the timeout before the activity runs. The + // activity then starts the timer again, and the next 'timeout' is the one measured. + if (activityAt === 0) return; + onTimedOut(performance.now()); + socket.destroy(); + }); + const port = await listen(server); + const client = await get(port, () => {}); + try { + const quietFor = (await timedOut) - activityAt; + expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + } finally { + client.destroy(); + server.closeAllConnections(); + server.close(); + } + }); + + test.concurrent("a write after a 'timeout' that kept the socket open starts the timer again", async () => { + const events: string[] = []; + let writeAt = 0; + const { promise: timedOutAgain, resolve: onTimedOutAgain } = Promise.withResolvers(); + let response: http.ServerResponse | undefined; + const server = http.createServer((req, res) => { + response = res; + res.writeHead(200, { "Content-Type": "text/plain" }); + }); + server.timeout = TIMEOUT; + server.on("timeout", () => { + events.push("timeout"); + if (writeAt !== 0) return onTimedOutAgain(performance.now()); + // A listener keeps the socket open. Nothing reads or writes until this write. + setTimeout(() => { + events.push("write"); + writeAt = performance.now(); + response!.write("late\n"); + }, TIMEOUT / 4); + }); + const port = await listen(server); + const client = await get(port, () => {}); + try { + const quietFor = (await timedOutAgain) - writeAt; + expect(events).toEqual(["timeout", "write", "timeout"]); + expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + } finally { + client.destroy(); + server.closeAllConnections(); + server.close(); + } + }); +}); From 70db310e2eada8691886ce5a63ca7d79727b04a2 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:09:18 +0000 Subject: [PATCH 02/15] test: a request body chunk after a moved deadline gets a full period --- .../http/node-http-server-timeouts.test.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index f5fcad3e932e..766cda9ef6f0 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -561,4 +561,45 @@ describe("node:http response writes restart the socket inactivity timeout", () = server.close(); } }); + + test.concurrent("a request body chunk after a write moved the deadline gets a full period", async () => { + // The write at 0.6 * TIMEOUT moves the deadline from TIMEOUT to 1.6 * TIMEOUT. The body + // chunk arrives between the two. A timer that was re-armed for only the rest of the + // period would count 0.6 * TIMEOUT from the chunk. + let chunkAt = 0; + const { promise: timedOut, resolve: onTimedOut } = Promise.withResolvers(); + const { promise: wrote, resolve: onWrote } = Promise.withResolvers(); + const server = http.createServer((req, res) => { + req.on("data", chunk => { + if (chunk.toString("latin1") === "late") chunkAt = performance.now(); + }); + setTimeout(() => { + res.write("chunk\n"); + onWrote(); + }, TIMEOUT * 0.6); + }); + server.timeout = TIMEOUT; + server.on("timeout", socket => { + // Only on a machine that stalls until the moved deadline has passed. The chunk + // then starts the timer again, and the next 'timeout' is the one measured. + if (chunkAt === 0) return; + onTimedOut(performance.now()); + socket.destroy(); + }); + const port = await listen(server); + const client = net.connect(port, "127.0.0.1"); + try { + client.on("error", () => {}); + await once(client, "connect"); + client.write("POST / HTTP/1.1\r\nHost: a\r\nContent-Length: 100\r\n\r\nearly"); + await wrote; + setTimeout(() => client.write("late"), TIMEOUT * 0.7); + const quietFor = (await timedOut) - chunkAt; + expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + } finally { + client.destroy(); + server.closeAllConnections(); + server.close(); + } + }); }); From a6c21b46892b76ed5d6e26644eec2c7bee4b369b Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 22 Sep 2026 03:08:31 +0000 Subject: [PATCH 03/15] node:http: time response writes on the monotonic clock, skip discarded writes A write to a response without a body (HEAD, 204, 304) never reaches the socket in Node.js, so it is no activity for the inactivity timeout. The tests now pin the exact deadline with a reference timer, cover socket.setTimeout(), and cover a handler that keeps writing to a HEAD response. --- src/js/node/_http_server.ts | 20 ++-- .../http/node-http-server-timeouts.test.ts | 113 +++++++++++++----- 2 files changed, 92 insertions(+), 41 deletions(-) diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index 3e3a7aea305a..e95450a797a3 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1355,11 +1355,12 @@ const kKeepAliveTimeoutSet = Symbol("keepAliveTimeoutSet"); // the socket timer on every response; onSocketTimeoutTimerExpired reads it to // grant the remaining idle budget when the timer actually fires. const kKeepAliveIdleStart = Symbol("keepAliveIdleStart"); -// When a response last wrote to the connection. Node.js restarts the socket's -// inactivity timer on every write (net.Socket._writeGeneric and onWriteComplete -// call _unrefTimer()). noteResponseWrite records the write instead, for the same -// reason as kKeepAliveIdleStart, and onSocketTimeoutTimerExpired moves the -// deadline when the timer fires. +// When a response last wrote to the connection, on performance.now() (monotonic, +// like the timer). Node.js restarts the socket's inactivity timer on every write +// (net.Socket._writeGeneric and onWriteComplete call _unrefTimer()). +// noteResponseWrite records the write instead, for the same reason as +// kKeepAliveIdleStart, and onSocketTimeoutTimerExpired moves the deadline when +// the timer fires. // https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L1019 // https://github.com/nodejs/node/blob/v26.3.0/lib/internal/stream_base_commons.js#L101 const kLastResponseWrite = Symbol("lastResponseWrite"); @@ -1481,7 +1482,7 @@ function onSocketTimeoutTimerExpired(socket) { const lastWrite = socket[kLastResponseWrite]; if (lastWrite !== undefined) { socket[kLastResponseWrite] = undefined; - const sinceWrite = DateNow() - lastWrite; + const sinceWrite = performance.now() - lastWrite; const timer = socket[kSocketTimeoutTimer]; if (sinceWrite < socket.timeout && timer !== undefined) { timer.refresh(); @@ -3040,7 +3041,7 @@ function noteResponseWrite(res) { socket[kSocketTimeoutEmitted] = false; socket._unrefTimer(); } else { - socket[kLastResponseWrite] = DateNow(); + socket[kLastResponseWrite] = performance.now(); } } @@ -3440,7 +3441,10 @@ ServerResponse.prototype.write = function (chunk, encoding, callback) { return true; } - noteResponseWrite(this); + // Node's write_() drops a write to a response without a body (HEAD, 204, 304) + // before it reaches the socket, so it is no activity. + // https://github.com/nodejs/node/blob/v26.3.0/lib/_http_outgoing.js#L983 + if (this._hasBody) noteResponseWrite(this); if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) { handle.cork(() => { const renderedHeaders = renderNativeHeaders(this); diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index 766cda9ef6f0..a0aa80f0ab28 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -425,35 +425,37 @@ describe("node:http server timeout enforcement", () => { }); // A write on the response is activity on the connection, like a read: Node's -// net.Socket restarts its inactivity timer in _writeGeneric. Each probe sends -// one GET on a raw TCP socket and stays silent, so only the response side can -// keep the connection active. The timing checks are one-sided: 'timeout' must -// not come sooner than a full period after the last write. +// net.Socket restarts its inactivity timer in _writeGeneric. Each probe holds +// one request open on a raw TCP socket. The timing checks are one-sided +// ('timeout' must not come sooner than a full period after the activity) or +// compare against a reference timer. Timers fire in deadline order, so a slow +// machine cannot swap the socket's timer and the reference. describe("node:http response writes restart the socket inactivity timeout", () => { const TIMEOUT = 400; - // The server times the write with Date.now() and the timer runs on the monotonic - // clock, both in whole milliseconds. A server that ignores the write fires - // TIMEOUT / 2 early, far outside this. + // The timer clock counts whole milliseconds. A server that ignores the + // activity fires TIMEOUT / 2 early, far outside this. const SLACK = 50; - async function get(port: number, onClose: (received: string) => void) { + async function send(port: number, request: string, onClose?: (received: string) => void) { const client = net.connect(port, "127.0.0.1"); let received = ""; client.on("data", chunk => (received += chunk.toString("latin1"))); client.on("error", () => {}); - client.on("close", () => onClose(received)); + client.on("close", () => onClose?.(received)); await once(client, "connect"); - client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); + client.write(request); return client; } + const GET = "GET / HTTP/1.1\r\nHost: a\r\n\r\n"; - test.concurrent.each(["server.timeout", "res.setTimeout()", "req.setTimeout()"])( + test.concurrent.each(["server.timeout", "res.setTimeout()", "req.setTimeout()", "socket.setTimeout()"])( "%s does not fire while the response keeps writing", async armedBy => { const events: string[] = []; const server = http.createServer((req, res) => { if (armedBy === "res.setTimeout()") res.setTimeout(TIMEOUT); if (armedBy === "req.setTimeout()") req.setTimeout(TIMEOUT); + if (armedBy === "socket.setTimeout()") req.socket.setTimeout(TIMEOUT); req.socket.on("timeout", () => events.push("timeout")); res.writeHead(200, { "Content-Type": "text/plain" }); // Stream for longer than the timeout, one chunk every TIMEOUT / 8. @@ -474,16 +476,17 @@ describe("node:http response writes restart the socket inactivity timeout", () = server.keepAliveTimeout = TIMEOUT; server.keepAliveTimeoutBuffer = 0; const port = await listen(server); - const { promise: closed, resolve: onClosed } = Promise.withResolvers(); - const client = await get(port, onClosed); + let client: net.Socket | undefined; try { + const { promise: closed, resolve: onClosed } = Promise.withResolvers(); + client = await send(port, GET, onClosed); const received = await closed; expect({ events, complete: received.endsWith("done\n\r\n0\r\n\r\n") }).toEqual({ events: ["end", "timeout"], complete: true, }); } finally { - client.destroy(); + client?.destroy(); server.closeAllConnections(); server.close(); } @@ -500,9 +503,14 @@ describe("node:http response writes restart the socket inactivity timeout", () = test.concurrent.each(Object.keys(activities))("the inactivity period restarts at %s", async name => { let activityAt = 0; + let reference: ReturnType | undefined; + let referenceFired = false; const { promise: timedOut, resolve: onTimedOut } = Promise.withResolvers(); const server = http.createServer((req, res) => { setTimeout(() => { + // After the exact deadline (the activity plus TIMEOUT) and before the deadline of + // a timer that only starts over when it fires (the activity plus 1.5 * TIMEOUT). + reference = setTimeout(() => (referenceFired = true), TIMEOUT * 1.25); activityAt = performance.now(); activities[name](res); }, TIMEOUT / 2); @@ -518,12 +526,15 @@ describe("node:http response writes restart the socket inactivity timeout", () = socket.destroy(); }); const port = await listen(server); - const client = await get(port, () => {}); + let client: net.Socket | undefined; try { + client = await send(port, GET); const quietFor = (await timedOut) - activityAt; expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + expect(referenceFired).toBe(false); } finally { - client.destroy(); + clearTimeout(reference); + client?.destroy(); server.closeAllConnections(); server.close(); } @@ -550,28 +561,63 @@ describe("node:http response writes restart the socket inactivity timeout", () = }, TIMEOUT / 4); }); const port = await listen(server); - const client = await get(port, () => {}); + let client: net.Socket | undefined; try { + client = await send(port, GET); const quietFor = (await timedOutAgain) - writeAt; expect(events).toEqual(["timeout", "write", "timeout"]); expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { - client.destroy(); + client?.destroy(); + server.closeAllConnections(); + server.close(); + } + }); + + test.concurrent("a write that a response without a body discards does not restart the timer", async () => { + // Node drops a write to a HEAD response before it reaches the socket (write_), so a + // handler that keeps writing is still timed out. A server that ignores every write + // passes this too: it guards against counting the writes that Node drops. + let reference: ReturnType | undefined; + const { promise: settled, resolve: onSettled } = Promise.withResolvers(); + const server = http.createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/event-stream" }); + const interval = setInterval(() => res.write("data: ping\n\n"), TIMEOUT / 8); + res.on("close", () => clearInterval(interval)); + // Long after the 'timeout' of a connection without activity. + reference = setTimeout(() => onSettled("the reference timer"), TIMEOUT * 2.5); + }); + server.timeout = TIMEOUT; + server.on("timeout", socket => { + onSettled("'timeout'"); + socket.destroy(); + }); + const port = await listen(server); + let client: net.Socket | undefined; + try { + client = await send(port, "HEAD / HTTP/1.1\r\nHost: a\r\n\r\n"); + expect(await settled).toBe("'timeout'"); + } finally { + clearTimeout(reference); + client?.destroy(); server.closeAllConnections(); server.close(); } }); test.concurrent("a request body chunk after a write moved the deadline gets a full period", async () => { - // The write at 0.6 * TIMEOUT moves the deadline from TIMEOUT to 1.6 * TIMEOUT. The body - // chunk arrives between the two. A timer that was re-armed for only the rest of the - // period would count 0.6 * TIMEOUT from the chunk. - let chunkAt = 0; + // The write at 0.6 * TIMEOUT moves the deadline from TIMEOUT to 1.6 * TIMEOUT, and the + // body chunk arrives between the two. This guards how the deadline moves, not the bug. + // A server that ignores writes passes too: the 'timeout' at TIMEOUT is swallowed below + // and the chunk restarts the spent timer. It fails when the expiry puts a new, shorter + // timer in the slot, because the chunk then refreshes only 0.6 * TIMEOUT. + let chunkSentAt = 0; + let chunkReceived = false; const { promise: timedOut, resolve: onTimedOut } = Promise.withResolvers(); const { promise: wrote, resolve: onWrote } = Promise.withResolvers(); const server = http.createServer((req, res) => { req.on("data", chunk => { - if (chunk.toString("latin1") === "late") chunkAt = performance.now(); + if (chunk.toString("latin1") === "late") chunkReceived = true; }); setTimeout(() => { res.write("chunk\n"); @@ -580,24 +626,25 @@ describe("node:http response writes restart the socket inactivity timeout", () = }); server.timeout = TIMEOUT; server.on("timeout", socket => { - // Only on a machine that stalls until the moved deadline has passed. The chunk - // then starts the timer again, and the next 'timeout' is the one measured. - if (chunkAt === 0) return; + // Before the chunk: a server that ignores writes, or a machine that stalls until the + // moved deadline has passed. The chunk then starts the timer again. + if (!chunkReceived) return; onTimedOut(performance.now()); socket.destroy(); }); const port = await listen(server); - const client = net.connect(port, "127.0.0.1"); + let client: net.Socket | undefined; try { - client.on("error", () => {}); - await once(client, "connect"); - client.write("POST / HTTP/1.1\r\nHost: a\r\nContent-Length: 100\r\n\r\nearly"); + client = await send(port, "POST / HTTP/1.1\r\nHost: a\r\nContent-Length: 100\r\n\r\nearly"); await wrote; - setTimeout(() => client.write("late"), TIMEOUT * 0.7); - const quietFor = (await timedOut) - chunkAt; + setTimeout(() => { + chunkSentAt = performance.now(); + client!.write("late"); + }, TIMEOUT * 0.7); + const quietFor = (await timedOut) - chunkSentAt; expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { - client.destroy(); + client?.destroy(); server.closeAllConnections(); server.close(); } From f59ae7e22a6fd94cf006dfde6308984d4a864c97 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 22 Sep 2026 04:36:24 +0000 Subject: [PATCH 04/15] node:http: record a response write after the native call accepts it A write that throws (an invalid status code, a strict Content-Length mismatch) never reaches the socket in Node.js, so it is no activity for the inactivity timeout. A handler that retried such a write kept its connection open forever. --- src/js/node/_http_server.ts | 24 +++++++------- .../http/node-http-server-timeouts.test.ts | 31 ++++++++++++++----- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index e95450a797a3..42d61c340ce5 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -2698,8 +2698,8 @@ function advanceResponsePipeline(server, socket) { if (kind === "raw") { // Buffered 1xx bytes: route through the same AsyncSocket buffer the // response's own writeHead/end use so they precede the final response. - noteResponseWrite(res); handle.writeInformational(op[1], op[2]); + noteResponseWrite(res); if (typeof op[3] === "function") process.nextTick(op[3]); } else if (kind === "write") { lastWriteResult = res.write(op[1], op[2], op[3]); @@ -3033,7 +3033,8 @@ Object.defineProperty(ServerResponse.prototype, "headersSent", { }); // A response write is activity on the connection's inactivity timeout -// (socket.setTimeout / server.timeout), see kLastResponseWrite. +// (socket.setTimeout / server.timeout), see kLastResponseWrite. Callers run +// it after the native write: a write that throws is no activity, as in Node.js. function noteResponseWrite(res) { const socket = res[kSocket]; if (socket == null || !socket.timeout) return; @@ -3069,8 +3070,8 @@ ServerResponse.prototype._writeRaw = function (chunk, encoding, callback) { // Write through the response handle's AsyncSocket buffer (same path as // writeHead/end) so 1xx lines share ordering with the final response bytes; // socket.write() would land in the socket handle's separate stream buffer. - noteResponseWrite(this); this[kHandle].writeInformational(chunk, encoding); + noteResponseWrite(this); if (typeof callback === "function") process.nextTick(callback); return true; }; @@ -3170,10 +3171,10 @@ ServerResponse.prototype.writeContinue = function (cb) { this._sent100 = true; return; } - noteResponseWrite(this); const native = this.socket?.[kHandle]?.response; if (native) native.writeContinue(); else this[kHandle]?.writeContinue?.(); + noteResponseWrite(this); this._sent100 = true; cb?.(); }; @@ -3270,7 +3271,6 @@ ServerResponse.prototype.end = function (chunk, encoding, callback) { // and will not throw or emit an error return true; } - noteResponseWrite(this); const sentState = NodeHTTPHeaderState.sent; if (headerState !== sentState) { { @@ -3318,6 +3318,7 @@ ServerResponse.prototype.end = function (chunk, encoding, callback) { handle.end(chunk, encoding, undefined, strictContentLength(this)); } } + noteResponseWrite(this); this._header = " "; const req = this.req; if (!req._consuming && !req?._readableState?.resumeScheduled) { @@ -3441,10 +3442,6 @@ ServerResponse.prototype.write = function (chunk, encoding, callback) { return true; } - // Node's write_() drops a write to a response without a body (HEAD, 204, 304) - // before it reaches the socket, so it is no activity. - // https://github.com/nodejs/node/blob/v26.3.0/lib/_http_outgoing.js#L983 - if (this._hasBody) noteResponseWrite(this); if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) { handle.cork(() => { const renderedHeaders = renderNativeHeaders(this); @@ -3471,6 +3468,11 @@ ServerResponse.prototype.write = function (chunk, encoding, callback) { result = handle.write(chunk, encoding, allowWritesToContinue.bind(this), strictContentLength(this)); } + // Node's write_() drops a write to a response without a body (HEAD, 204, 304) + // before it reaches the socket, so it is no activity. + // https://github.com/nodejs/node/blob/v26.3.0/lib/_http_outgoing.js#L983 + if (this._hasBody) noteResponseWrite(this); + if (result < 0) { if (callback) { // The write was buffered due to backpressure. @@ -3629,7 +3631,6 @@ ServerResponse.prototype._send = function (data, encoding, callback, _byteLength return OutgoingMessagePrototype._send.$apply(this, arguments); } - noteResponseWrite(this); if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) { handle.cork(() => { const renderedHeaders = renderNativeHeaders(this); @@ -3652,6 +3653,7 @@ ServerResponse.prototype._send = function (data, encoding, callback, _byteLength } else { handle.write(data, encoding, callback, strictContentLength(this)); } + noteResponseWrite(this); }; const kSnapshotStatusCode = Symbol("kSnapshotStatusCode"); @@ -3760,7 +3762,6 @@ ServerResponse.prototype.flushHeaders = function () { const handle = this[kHandle]; if (handle) { - noteResponseWrite(this); if (this[headerStateSymbol] === NodeHTTPHeaderState.assigned) { this[headerStateSymbol] = NodeHTTPHeaderState.sent; @@ -3780,6 +3781,7 @@ ServerResponse.prototype.flushHeaders = function () { } } handle.flushHeaders(); + noteResponseWrite(this); } else { // Standalone path: _storeHeader rendered this._header; _send('') pushes // it to the assigned socket like OutgoingMessage.flushHeaders does. diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index a0aa80f0ab28..7e95f7546444 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -574,15 +574,32 @@ describe("node:http response writes restart the socket inactivity timeout", () = } }); - test.concurrent("a write that a response without a body discards does not restart the timer", async () => { - // Node drops a write to a HEAD response before it reaches the socket (write_), so a - // handler that keeps writing is still timed out. A server that ignores every write - // passes this too: it guards against counting the writes that Node drops. + // Writes that never reach the socket in Node: write_() drops a write to a response without + // a body, and it validates before it sends anything. A server that ignores every write + // passes these too. They guard against counting such a write as activity, which keeps the + // connection of a handler that writes forever open. + const noActivity: Record void }> = { + "a write that a HEAD response discards": { + request: "HEAD / HTTP/1.1\r\nHost: a\r\n\r\n", + setup: res => res.writeHead(200, { "Content-Type": "text/event-stream" }), + }, + "a write that throws": { + request: GET, + // The implicit header is invalid, so every write throws before it sends anything. + setup: res => (res.statusCode = 1000), + }, + }; + + test.concurrent.each(Object.keys(noActivity))("%s does not restart the timer", async name => { let reference: ReturnType | undefined; const { promise: settled, resolve: onSettled } = Promise.withResolvers(); const server = http.createServer((req, res) => { - res.writeHead(200, { "Content-Type": "text/event-stream" }); - const interval = setInterval(() => res.write("data: ping\n\n"), TIMEOUT / 8); + noActivity[name].setup(res); + const interval = setInterval(() => { + try { + res.write("data: ping\n\n"); + } catch {} + }, TIMEOUT / 8); res.on("close", () => clearInterval(interval)); // Long after the 'timeout' of a connection without activity. reference = setTimeout(() => onSettled("the reference timer"), TIMEOUT * 2.5); @@ -595,7 +612,7 @@ describe("node:http response writes restart the socket inactivity timeout", () = const port = await listen(server); let client: net.Socket | undefined; try { - client = await send(port, "HEAD / HTTP/1.1\r\nHost: a\r\n\r\n"); + client = await send(port, noActivity[name].request); expect(await settled).toBe("'timeout'"); } finally { clearTimeout(reference); From 34d193eef3363885dc3d472db4af580ffc7d2ed2 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 22 Sep 2026 04:39:29 +0000 Subject: [PATCH 05/15] node:http: shorten the comments on the response write timer --- src/js/node/_http_server.ts | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index 42d61c340ce5..aea209e90faf 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1355,17 +1355,10 @@ const kKeepAliveTimeoutSet = Symbol("keepAliveTimeoutSet"); // the socket timer on every response; onSocketTimeoutTimerExpired reads it to // grant the remaining idle budget when the timer actually fires. const kKeepAliveIdleStart = Symbol("keepAliveIdleStart"); -// When a response last wrote to the connection, on performance.now() (monotonic, -// like the timer). Node.js restarts the socket's inactivity timer on every write -// (net.Socket._writeGeneric and onWriteComplete call _unrefTimer()). -// noteResponseWrite records the write instead, for the same reason as -// kKeepAliveIdleStart, and onSocketTimeoutTimerExpired moves the deadline when -// the timer fires. -// https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L1019 -// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/stream_base_commons.js#L101 +// performance.now() of the last response write. Settled when the timer fires, like kKeepAliveIdleStart. +// Node.js refreshes the timer on every socket write: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L1019 const kLastResponseWrite = Symbol("lastResponseWrite"); -// Set once 'timeout' has been emitted: the timer is spent, and like -// timer.refresh() in Node.js the next response write starts it again. +// 'timeout' was emitted: the next response write re-arms the spent timer, like refresh() in Node.js. const kSocketTimeoutEmitted = Symbol("socketTimeoutEmitted"); // HTTP/1.1 pipelining (responses queued behind an in-flight response): // - on the socket: array of queued ServerResponses, in arrival order @@ -1474,11 +1467,8 @@ function onSocketTimeoutTimerExpired(socket) { return; } } - // A response wrote to the connection inside the window: the inactivity - // period starts at that write. Re-arm this timer and move its start back - // to the write (the deadline is _idleStart + _idleTimeout, like Node.js). - // A new, shorter timer in the slot would make the next _unrefTimer() - // refresh that shorter interval. + // The period restarts at a response write inside the window. Not a new, shorter timer + // as above: the next _unrefTimer() would refresh the shorter interval. const lastWrite = socket[kLastResponseWrite]; if (lastWrite !== undefined) { socket[kLastResponseWrite] = undefined; @@ -3032,9 +3022,7 @@ Object.defineProperty(ServerResponse.prototype, "headersSent", { }, }); -// A response write is activity on the connection's inactivity timeout -// (socket.setTimeout / server.timeout), see kLastResponseWrite. Callers run -// it after the native write: a write that throws is no activity, as in Node.js. +// Runs after the native write: a write that throws is no activity, as in Node.js. function noteResponseWrite(res) { const socket = res[kSocket]; if (socket == null || !socket.timeout) return; @@ -3468,8 +3456,7 @@ ServerResponse.prototype.write = function (chunk, encoding, callback) { result = handle.write(chunk, encoding, allowWritesToContinue.bind(this), strictContentLength(this)); } - // Node's write_() drops a write to a response without a body (HEAD, 204, 304) - // before it reaches the socket, so it is no activity. + // Node.js drops a write to a response without a body before it reaches the socket: // https://github.com/nodejs/node/blob/v26.3.0/lib/_http_outgoing.js#L983 if (this._hasBody) noteResponseWrite(this); From 2fb29ab32c1e8067463e9694c35c9b3a6fe2cca8 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 22 Sep 2026 04:41:24 +0000 Subject: [PATCH 06/15] node:http: one-line comments on the response write timer --- src/js/node/_http_server.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index aea209e90faf..9295015d1823 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1355,8 +1355,7 @@ const kKeepAliveTimeoutSet = Symbol("keepAliveTimeoutSet"); // the socket timer on every response; onSocketTimeoutTimerExpired reads it to // grant the remaining idle budget when the timer actually fires. const kKeepAliveIdleStart = Symbol("keepAliveIdleStart"); -// performance.now() of the last response write. Settled when the timer fires, like kKeepAliveIdleStart. -// Node.js refreshes the timer on every socket write: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L1019 +// performance.now() of the last response write, settled when the timer fires like kKeepAliveIdleStart. const kLastResponseWrite = Symbol("lastResponseWrite"); // 'timeout' was emitted: the next response write re-arms the spent timer, like refresh() in Node.js. const kSocketTimeoutEmitted = Symbol("socketTimeoutEmitted"); @@ -1467,8 +1466,7 @@ function onSocketTimeoutTimerExpired(socket) { return; } } - // The period restarts at a response write inside the window. Not a new, shorter timer - // as above: the next _unrefTimer() would refresh the shorter interval. + // Not a new, shorter timer as above: the next _unrefTimer() would refresh the shorter interval. const lastWrite = socket[kLastResponseWrite]; if (lastWrite !== undefined) { socket[kLastResponseWrite] = undefined; @@ -3456,8 +3454,7 @@ ServerResponse.prototype.write = function (chunk, encoding, callback) { result = handle.write(chunk, encoding, allowWritesToContinue.bind(this), strictContentLength(this)); } - // Node.js drops a write to a response without a body before it reaches the socket: - // https://github.com/nodejs/node/blob/v26.3.0/lib/_http_outgoing.js#L983 + // Node.js drops this write before the socket: https://github.com/nodejs/node/blob/v26.3.0/lib/_http_outgoing.js#L983 if (this._hasBody) noteResponseWrite(this); if (result < 0) { From e45cb548d03472c931a3a10a9159930e96219a64 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 22 Sep 2026 05:40:49 +0000 Subject: [PATCH 07/15] node:http: a response write that only backs up is no socket activity A client that stops reading must still be timed out while the handler keeps calling res.write(). Only a write that the transport accepts, and the drain of a backed-up one, count for the inactivity timeout. performance.now is bound at load like Date.now, and the streaming tests no longer depend on the scheduling of the machine. --- src/js/node/_http_server.ts | 24 ++++--- .../http/node-http-server-timeouts.test.ts | 67 ++++++++++--------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index 9295015d1823..dca582ed648d 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -106,6 +106,11 @@ const ObjectKeys = Object.keys; const MathMin = Math.min; const MathFloor = Math.floor; const DateNow = Date.now; +// Monotonic. Bound at load like DateNow, so a replaced `performance` global cannot break a response write. +const performanceNow = + typeof performance === "object" && typeof performance.now === "function" + ? performance.now.bind(performance) + : DateNow; let cluster; @@ -1355,7 +1360,7 @@ const kKeepAliveTimeoutSet = Symbol("keepAliveTimeoutSet"); // the socket timer on every response; onSocketTimeoutTimerExpired reads it to // grant the remaining idle budget when the timer actually fires. const kKeepAliveIdleStart = Symbol("keepAliveIdleStart"); -// performance.now() of the last response write, settled when the timer fires like kKeepAliveIdleStart. +// performanceNow() of the last response write, settled when the timer fires like kKeepAliveIdleStart. const kLastResponseWrite = Symbol("lastResponseWrite"); // 'timeout' was emitted: the next response write re-arms the spent timer, like refresh() in Node.js. const kSocketTimeoutEmitted = Symbol("socketTimeoutEmitted"); @@ -1470,7 +1475,7 @@ function onSocketTimeoutTimerExpired(socket) { const lastWrite = socket[kLastResponseWrite]; if (lastWrite !== undefined) { socket[kLastResponseWrite] = undefined; - const sinceWrite = performance.now() - lastWrite; + const sinceWrite = performanceNow() - lastWrite; const timer = socket[kSocketTimeoutTimer]; if (sinceWrite < socket.timeout && timer !== undefined) { timer.refresh(); @@ -3021,14 +3026,16 @@ Object.defineProperty(ServerResponse.prototype, "headersSent", { }); // Runs after the native write: a write that throws is no activity, as in Node.js. -function noteResponseWrite(res) { +function noteResponseWrite(res, result = 0) { + // Nor is a write that only reached the backpressure buffer: a peer that stopped reading must still time out. + if (result < 0) return; const socket = res[kSocket]; if (socket == null || !socket.timeout) return; if (socket[kSocketTimeoutEmitted]) { socket[kSocketTimeoutEmitted] = false; socket._unrefTimer(); } else { - socket[kLastResponseWrite] = performance.now(); + socket[kLastResponseWrite] = performanceNow(); } } @@ -3455,7 +3462,7 @@ ServerResponse.prototype.write = function (chunk, encoding, callback) { } // Node.js drops this write before the socket: https://github.com/nodejs/node/blob/v26.3.0/lib/_http_outgoing.js#L983 - if (this._hasBody) noteResponseWrite(this); + if (this._hasBody) noteResponseWrite(this, result); if (result < 0) { if (callback) { @@ -3615,6 +3622,7 @@ ServerResponse.prototype._send = function (data, encoding, callback, _byteLength return OutgoingMessagePrototype._send.$apply(this, arguments); } + let result = 0; if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) { handle.cork(() => { const renderedHeaders = renderNativeHeaders(this); @@ -3632,12 +3640,12 @@ ServerResponse.prototype._send = function (data, encoding, callback, _byteLength releaseRenderedHeaders(renderedHeaders); } this[headerStateSymbol] = NodeHTTPHeaderState.sent; - handle.write(data, encoding, callback, strictContentLength(this)); + result = handle.write(data, encoding, callback, strictContentLength(this)); }); } else { - handle.write(data, encoding, callback, strictContentLength(this)); + result = handle.write(data, encoding, callback, strictContentLength(this)); } - noteResponseWrite(this); + noteResponseWrite(this, result); }; const kSnapshotStatusCode = Symbol("kSnapshotStatusCode"); diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index 7e95f7546444..2d0165f069b3 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -436,12 +436,12 @@ describe("node:http response writes restart the socket inactivity timeout", () = // activity fires TIMEOUT / 2 early, far outside this. const SLACK = 50; - async function send(port: number, request: string, onClose?: (received: string) => void) { + // A stalled client never reads, so the server's writes back up. + async function send(port: number, request: string, stalled = false) { const client = net.connect(port, "127.0.0.1"); - let received = ""; - client.on("data", chunk => (received += chunk.toString("latin1"))); + if (stalled) client.pause(); + else client.resume(); client.on("error", () => {}); - client.on("close", () => onClose?.(received)); await once(client, "connect"); client.write(request); return client; @@ -451,22 +451,23 @@ describe("node:http response writes restart the socket inactivity timeout", () = test.concurrent.each(["server.timeout", "res.setTimeout()", "req.setTimeout()", "socket.setTimeout()"])( "%s does not fire while the response keeps writing", async armedBy => { - const events: string[] = []; + let lastWriteAt = 0; + const { promise: firstTimeout, resolve: onFirstTimeout } = Promise.withResolvers(); const server = http.createServer((req, res) => { if (armedBy === "res.setTimeout()") res.setTimeout(TIMEOUT); if (armedBy === "req.setTimeout()") req.setTimeout(TIMEOUT); if (armedBy === "socket.setTimeout()") req.socket.setTimeout(TIMEOUT); - req.socket.on("timeout", () => events.push("timeout")); + req.socket.on("timeout", () => onFirstTimeout(performance.now() - lastWriteAt)); res.writeHead(200, { "Content-Type": "text/plain" }); // Stream for longer than the timeout, one chunk every TIMEOUT / 8. const startedAt = performance.now(); const interval = setInterval(() => { - if (performance.now() - startedAt < TIMEOUT * 1.5) { + lastWriteAt = performance.now(); + if (lastWriteAt - startedAt < TIMEOUT * 1.5) { res.write("chunk\n"); return; } clearInterval(interval); - events.push("end"); res.end("done\n"); }, TIMEOUT / 8); res.on("close", () => clearInterval(interval)); @@ -478,13 +479,10 @@ describe("node:http response writes restart the socket inactivity timeout", () = const port = await listen(server); let client: net.Socket | undefined; try { - const { promise: closed, resolve: onClosed } = Promise.withResolvers(); - client = await send(port, GET, onClosed); - const received = await closed; - expect({ events, complete: received.endsWith("done\n\r\n0\r\n\r\n") }).toEqual({ - events: ["end", "timeout"], - complete: true, - }); + client = await send(port, GET); + // A server that ignores writes fires within TIMEOUT / 8 of one. A machine that stalls + // between two writes can only make this longer. + expect(await firstTimeout).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { client?.destroy(); server.closeAllConnections(); @@ -574,21 +572,28 @@ describe("node:http response writes restart the socket inactivity timeout", () = } }); - // Writes that never reach the socket in Node: write_() drops a write to a response without - // a body, and it validates before it sends anything. A server that ignores every write - // passes these too. They guard against counting such a write as activity, which keeps the - // connection of a handler that writes forever open. - const noActivity: Record void }> = { - "a write that a HEAD response discards": { - request: "HEAD / HTTP/1.1\r\nHost: a\r\n\r\n", - setup: res => res.writeHead(200, { "Content-Type": "text/event-stream" }), - }, - "a write that throws": { - request: GET, - // The implicit header is invalid, so every write throws before it sends anything. - setup: res => (res.statusCode = 1000), - }, - }; + // Writes that are no activity in Node: write_() drops a write to a response without a body + // and validates before it sends anything, and a write to a peer that stopped reading only + // backs up. A server that ignores every write passes these too. They guard against counting + // such a write, which keeps the connection of a handler that writes forever open. + const noActivity: Record void }> = + { + "a write that a HEAD response discards": { + request: "HEAD / HTTP/1.1\r\nHost: a\r\n\r\n", + setup: res => res.writeHead(200, { "Content-Type": "text/event-stream" }), + }, + "a write that throws": { + request: GET, + // The implicit header is invalid, so every write throws before it sends anything. + setup: res => (res.statusCode = 1000), + }, + "a write to a peer that stopped reading": { + request: GET, + stalled: true, + // More than the kernel buffers take: this write and every later one back up. + setup: res => res.write(Buffer.alloc(16 * 1024 * 1024, "a")), + }, + }; test.concurrent.each(Object.keys(noActivity))("%s does not restart the timer", async name => { let reference: ReturnType | undefined; @@ -612,7 +617,7 @@ describe("node:http response writes restart the socket inactivity timeout", () = const port = await listen(server); let client: net.Socket | undefined; try { - client = await send(port, noActivity[name].request); + client = await send(port, noActivity[name].request, noActivity[name].stalled); expect(await settled).toBe("'timeout'"); } finally { clearTimeout(reference); From f27690418a290db990c6f6be5ee1cbe47e5390ac Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 22 Sep 2026 05:58:05 +0000 Subject: [PATCH 08/15] ci: retrigger From 670efcdf7a7e3226025b2bd35c8a5b9aa7fae960 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 22 Sep 2026 06:08:18 +0000 Subject: [PATCH 09/15] test: fail the new timeout tests at once when the connection is lost Each awaited 'timeout' now races a promise that rejects on the client's 'error' or 'close', so a regression that closes the connection without a 'timeout' reports the cause and does not wait for the test timeout. --- .../http/node-http-server-timeouts.test.ts | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index 2d0165f069b3..2459894d6d1e 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -441,11 +441,20 @@ describe("node:http response writes restart the socket inactivity timeout", () = const client = net.connect(port, "127.0.0.1"); if (stalled) client.pause(); else client.resume(); - client.on("error", () => {}); await once(client, "connect"); client.write(request); return client; } + + // Rejects when the connection fails or closes before the awaited event, so a regression + // fails at once with the cause. The close at the end of a passing test settles nothing. + function lost(client: net.Socket) { + const { promise, reject } = Promise.withResolvers(); + promise.catch(() => {}); + client.on("error", reject); + client.on("close", () => reject(new Error("the connection closed before the awaited event"))); + return promise; + } const GET = "GET / HTTP/1.1\r\nHost: a\r\n\r\n"; test.concurrent.each(["server.timeout", "res.setTimeout()", "req.setTimeout()", "socket.setTimeout()"])( @@ -482,7 +491,7 @@ describe("node:http response writes restart the socket inactivity timeout", () = client = await send(port, GET); // A server that ignores writes fires within TIMEOUT / 8 of one. A machine that stalls // between two writes can only make this longer. - expect(await firstTimeout).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + expect(await Promise.race([firstTimeout, lost(client)])).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { client?.destroy(); server.closeAllConnections(); @@ -527,7 +536,7 @@ describe("node:http response writes restart the socket inactivity timeout", () = let client: net.Socket | undefined; try { client = await send(port, GET); - const quietFor = (await timedOut) - activityAt; + const quietFor = (await Promise.race([timedOut, lost(client)])) - activityAt; expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); expect(referenceFired).toBe(false); } finally { @@ -562,7 +571,7 @@ describe("node:http response writes restart the socket inactivity timeout", () = let client: net.Socket | undefined; try { client = await send(port, GET); - const quietFor = (await timedOutAgain) - writeAt; + const quietFor = (await Promise.race([timedOutAgain, lost(client)])) - writeAt; expect(events).toEqual(["timeout", "write", "timeout"]); expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { @@ -618,7 +627,7 @@ describe("node:http response writes restart the socket inactivity timeout", () = let client: net.Socket | undefined; try { client = await send(port, noActivity[name].request, noActivity[name].stalled); - expect(await settled).toBe("'timeout'"); + expect(await Promise.race([settled, lost(client)])).toBe("'timeout'"); } finally { clearTimeout(reference); client?.destroy(); @@ -658,12 +667,13 @@ describe("node:http response writes restart the socket inactivity timeout", () = let client: net.Socket | undefined; try { client = await send(port, "POST / HTTP/1.1\r\nHost: a\r\nContent-Length: 100\r\n\r\nearly"); - await wrote; + const connectionLost = lost(client); + await Promise.race([wrote, connectionLost]); setTimeout(() => { chunkSentAt = performance.now(); client!.write("late"); }, TIMEOUT * 0.7); - const quietFor = (await timedOut) - chunkSentAt; + const quietFor = (await Promise.race([timedOut, connectionLost])) - chunkSentAt; expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { client?.destroy(); From 4a922e7a2d236b22adcff68bdc93fcf705ea0093 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 8 Nov 2026 06:47:50 +0000 Subject: [PATCH 10/15] node:http: refresh the socket inactivity timer in the native write path A server with a socket inactivity timeout (server.timeout, server.setTimeout, req/res/socket.setTimeout) cut a response that it was still writing: only socket._unrefTimer() moved the timer, and no response write called it. socket.setTimeout() now hands the armed Timeout to the native connection (JSNodeHTTPServerSocket). The write entries of NodeHTTPResponse refresh it, and so does each drain that moves bytes of a pending write (the uWS buffer, the zero-copy tail, the tunnel buffer). _onTimeout() holds 'timeout' back once while a write is pending, so a peer that stops reading is timed out at the second expiry, as in Node. The keep-alive timer is never handed over. A server without a timeout makes no new native call and tests one state bit per native write. --- packages/bun-uws/src/HttpContext.h | 7 + packages/bun-uws/src/HttpResponseData.h | 4 +- src/js/ahead-of-time/node/_http_server.ts | 35 +- src/js/node/_http_server.ts | 21 +- src/js/private.d.ts | 6 +- .../bindings/node/JSNodeHTTPServerSocket.cpp | 49 ++ .../bindings/node/JSNodeHTTPServerSocket.h | 16 + .../node/JSNodeHTTPServerSocketPrototype.cpp | 20 + src/runtime/server/NodeHTTPResponse.rs | 55 ++- src/runtime/timer/Timer.rs | 8 + src/uws_sys/Response.rs | 28 ++ ...-own-keys-through-an-exchange.expected.txt | 38 +- ...wn-values-through-an-exchange.expected.txt | 30 +- .../http/node-http-server-timeouts.test.ts | 455 ++++++++++++++---- 14 files changed, 617 insertions(+), 155 deletions(-) diff --git a/packages/bun-uws/src/HttpContext.h b/packages/bun-uws/src/HttpContext.h index 14c58b249928..488469a58000 100644 --- a/packages/bun-uws/src/HttpContext.h +++ b/packages/bun-uws/src/HttpContext.h @@ -39,6 +39,7 @@ extern "C" void Bun__NodeHTTP__onReadsResumable(int ssl, struct us_socket_t *s); extern "C" void Bun__NodeHTTP__onReadParsed(int ssl, struct us_socket_t *s); +extern "C" void Bun__NodeHTTP__touchInactivityTimer(int ssl, struct us_socket_t *s); namespace uWS { @@ -899,6 +900,12 @@ struct HttpContext { if (bufferedAmount > 0) { /* Try to flush pending data from the socket's buffer to the network */ size_t flushed = asyncSocket->flush(); + if constexpr (IsNodeHttp) { + /* node:http compat: bytes of a pending write left, which is activity for an armed socket inactivity timeout. */ + if (flushed > 0 && (httpResponseData->state & HttpResponseData::HTTP_NODE_TRACKS_ACTIVITY)) [[unlikely]] { + Bun__NodeHTTP__touchInactivityTimer(SSL, s); + } + } /* Check if there's still data waiting to be sent after flush attempt */ if (asyncSocket->getBufferedAmount() > 0) { /* onEnd deferred close for these bytes; a writable event that diff --git a/packages/bun-uws/src/HttpResponseData.h b/packages/bun-uws/src/HttpResponseData.h index bb1afd7e34ed..c3c4a2d3b07d 100644 --- a/packages/bun-uws/src/HttpResponseData.h +++ b/packages/bun-uws/src/HttpResponseData.h @@ -178,11 +178,13 @@ struct HttpResponseData : AsyncSocketData, HttpParser { * word (resetResponseState) - these have to survive that. */ /* node:http: call Bun__NodeHTTP__onReadParsed once the read being parsed is consumed. */ HTTP_NODE_NOTIFY_READ_PARSED = 1 << 19, + /* node:http: JSNodeHTTPServerSocket holds the Timeout of an armed socket inactivity timeout. Server writes and their progress refresh it (Bun__NodeHTTP__touchInactivityTimer). */ + HTTP_NODE_TRACKS_ACTIVITY = 1 << 24, HTTP_CONNECTION_SCOPED = HTTP_NODE_PARSING_STOPPED | HTTP_NODE_READS_PAUSED | HTTP_NODE_TUNNEL_AFTER_BODY | HTTP_NODE_RECEIVED_FIN | HTTP_CLOSE_WHEN_IDLE | HTTP_NODE_CLOSE_AFTER_MESSAGE | HTTP_NODE_CLOSE_AFTER_DRAIN | HTTP_NODE_PEER_ENDED - | HTTP_NODE_HALF_CLOSED, + | HTTP_NODE_HALF_CLOSED | HTTP_NODE_TRACKS_ACTIVITY, }; /* Begin a new response on this connection. Clearing the word in one go is diff --git a/src/js/ahead-of-time/node/_http_server.ts b/src/js/ahead-of-time/node/_http_server.ts index aa946e2a188e..3375d331ad9a 100644 --- a/src/js/ahead-of-time/node/_http_server.ts +++ b/src/js/ahead-of-time/node/_http_server.ts @@ -1,5 +1,5 @@ // What the compiler reads in place of src/js/node/_http_server.ts, which `bun run` evaluates. The two do the same. -// Written against that file with the SHA-256 ff5fd0cf3ffd5e9a3fb246c83dbbefe854e635a64472500199657149d7819c82. +// Written against that file with the SHA-256 b193d24210dd78d099109bea822f2a2db1ea10994a6953ec80c11fbae3e365c8. // When that file changes, bring the change here and write the new hash: test/internal/second-texts-of-modules.test.ts. // Hardcoded module "node:_http_server" @@ -2741,11 +2741,8 @@ function getNodeHTTPServerSocket(): NodeHTTPServerSocketConstructor { _onTimeout() { const handle = this[kHandle]; - const response = handle?.response; - // If there is a response, and it has pending data, - // we suppress the timeout because a write is in progress. - if (response && response.writableLength! > 0) { - // Re-arm so the timeout still fires once the write actually stalls. + // A write in progress holds 'timeout' back once: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 + if ((this.writableLength > 0 || handle?.response?.bufferedAmount! > 0) && handle?.holdTimeoutBackOnce()) { this._unrefTimer(); return; } @@ -2968,6 +2965,7 @@ function getNodeHTTPServerSocket(): NodeHTTPServerSocketConstructor { clearTimeout(existingTimer); this[kSocketTimeoutTimer] = undefined; this.$armedTimeout = 0; + this[kHandle]?.setInactivityTimer(undefined); } if (callback !== undefined) { validateFunction(callback, "callback"); @@ -2985,6 +2983,10 @@ function getNodeHTTPServerSocket(): NodeHTTPServerSocketConstructor { this[kSocketTimeoutTimer] = timer; this.$armedTimeout = msecs; } + // Server writes refresh it there (Node: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L1019). Never the keep-alive timer: nothing writes under it. + if (!this[kKeepAliveTimeoutSet]) { + this[kHandle]?.setInactivityTimer(this[kSocketTimeoutTimer]); + } if (callback !== undefined) { validateFunction(callback, "callback"); this.once("timeout", callback); @@ -3881,13 +3883,18 @@ function onResponseFinishHandleSocket( // idle period started; onSocketTimeoutTimerExpired grants the remaining // budget if the timer fires early, so the socket still closes after // exactly `total` ms of idle. - if (socket.$armedTimeout === total || isArmedWith(socket, total)) { + // The native connection has the timer of a timeout that was armed for this request. Nothing writes under the keep-alive one. + const armed = socket.timeout !== 0; + if (armed) socket[kHandle]?.setInactivityTimer(undefined); + // Before setTimeout(), which hands any other timer to the native connection. + socket[kKeepAliveTimeoutSet] = true; + // The timer of an armed timeout can have fired, with a 'timeout' listener that kept the socket: setTimeout() starts it again. + if (!armed && (socket.$armedTimeout === total || isArmedWith(socket, total))) { socket.timeout = total; } else { socket.setTimeout(total); } socket[kKeepAliveIdleStart] = DateNow(); - socket[kKeepAliveTimeoutSet] = true; } } @@ -5067,13 +5074,19 @@ const finishConnection = function onResponseFinishHandleSocket( if (parser != null && parser.incoming === req && req.readableEnded) parser.incoming = null; } const idleTimeout = exchange.idleTimeout; - if (socket.$armedTimeout === idleTimeout || isArmedWith(socket, idleTimeout)) socket.timeout = idleTimeout; - else { + // The native connection has the timer of a timeout that was armed for this request. Nothing writes under the keep-alive one. + const armed = socket.timeout !== 0; + if (armed) socket[kHandle]?.setInactivityTimer(undefined); + // Before setTimeout(), which hands any other timer to the native connection. + socket[kKeepAliveTimeoutSet] = true; + // The timer of an armed timeout can have fired, with a 'timeout' listener that kept the socket: setTimeout() starts it again. + if (!armed && (socket.$armedTimeout === idleTimeout || isArmedWith(socket, idleTimeout))) { + socket.timeout = idleTimeout; + } else { ("unlikely"); socket.setTimeout(idleTimeout); } socket[kKeepAliveIdleStart] = DateNow(); - socket[kKeepAliveTimeoutSet] = true; if (exchange.detachedFrom === null) { if (socket._httpMessage === res) { socket[kCloseCallback] = undefined; diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index c4513292b7c7..f68d6342511d 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1966,11 +1966,8 @@ function getNodeHTTPServerSocket() { _onTimeout() { const handle = this[kHandle]; - const response = handle?.response; - // If there is a response, and it has pending data, - // we suppress the timeout because a write is in progress. - if (response && response.writableLength > 0) { - // Re-arm so the timeout still fires once the write actually stalls. + // A write in progress holds 'timeout' back once: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 + if ((this.writableLength > 0 || handle?.response?.bufferedAmount > 0) && handle?.holdTimeoutBackOnce()) { this._unrefTimer(); return; } @@ -2187,6 +2184,7 @@ function getNodeHTTPServerSocket() { if (existingTimer) { clearTimeout(existingTimer); this[kSocketTimeoutTimer] = undefined; + this[kHandle]?.setInactivityTimer(undefined); } if (callback !== undefined) { validateFunction(callback, "callback"); @@ -2203,6 +2201,10 @@ function getNodeHTTPServerSocket() { timer.unref(); this[kSocketTimeoutTimer] = timer; } + // Server writes refresh it there (Node: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L1019). Never the keep-alive timer: nothing writes under it. + if (!this[kKeepAliveTimeoutSet]) { + this[kHandle]?.setInactivityTimer(this[kSocketTimeoutTimer]); + } if (callback !== undefined) { validateFunction(callback, "callback"); this.once("timeout", callback); @@ -2755,13 +2757,18 @@ function onResponseFinishHandleSocket(server, socket, res) { // budget if the timer fires early, so the socket still closes after // exactly `total` ms of idle. const timer = socket[kSocketTimeoutTimer]; - if (timer !== undefined && timer._idleTimeout === total) { + // The native connection has the timer of a timeout that was armed for this request. Nothing writes under the keep-alive one. + const armed = socket.timeout !== 0; + if (armed) socket[kHandle]?.setInactivityTimer(undefined); + // Before setTimeout(), which hands any other timer to the native connection. + socket[kKeepAliveTimeoutSet] = true; + // The timer of an armed timeout can have fired, with a 'timeout' listener that kept the socket: setTimeout() starts it again. + if (!armed && timer !== undefined && timer._idleTimeout === total) { socket.timeout = total; } else { socket.setTimeout(total); } socket[kKeepAliveIdleStart] = DateNow(); - socket[kKeepAliveTimeoutSet] = true; } } diff --git a/src/js/private.d.ts b/src/js/private.d.ts index 2359db7aa4f9..a093accc89ab 100644 --- a/src/js/private.d.ts +++ b/src/js/private.d.ts @@ -2440,8 +2440,6 @@ declare namespace $Node { takeRequestTrailers(lenient: boolean): string[] | undefined; takeRawHeaders(headers: IncomingHttpHeaders | null): string[] | undefined; headersObject(): IncomingHttpHeaders | undefined; - /** No member of the native class. The server's socket asks for it in `_onTimeout()`. */ - readonly writableLength?: number; } /** The native class `NodeHTTPServerSocket` (src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp): one connection. */ @@ -2481,6 +2479,10 @@ declare namespace $Node { stopParsing(): void; closeWhenDrained(): void; closeIfIdle(): boolean; + /** Hands over the `Timeout` of an armed socket inactivity timeout, or takes it back. While the connection holds it, the writes of the server and their progress refresh it. */ + setInactivityTimer(timer: Timeout | undefined): void; + /** For a timer that fired while a write is pending. True once, then false until a write or its progress refreshes the timer. Always false while the connection holds no timer, as under the keep-alive timeout. */ + holdTimeoutBackOnce(): boolean; } interface Timeout { diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp b/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp index 6f09c589e38c..4f84bab548ee 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp @@ -26,6 +26,7 @@ extern "C" uint64_t uws_res_get_local_address_info(void* res, const char** dest, extern "C" EncodedJSValue us_socket_buffered_js_write(void* socket, bool is_ssl, bool ended, bool hold, bool flushesBufferOnDrain, us_socket_stream_buffer_t* streamBuffer, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue data, JSC::EncodedJSValue encoding); extern "C" int us_socket_is_ssl_handshake_finished(struct us_socket_t* s); extern "C" int us_socket_ssl_handshake_callback_has_fired(struct us_socket_t* s); +extern "C" void Bun__Timer__refreshTimeout(JSC::JSGlobalObject*, JSC::EncodedJSValue timer); namespace Bun { @@ -472,6 +473,37 @@ bool JSNodeHTTPServerSocket::isRequestTimedOut(uint64_t headersTimeoutMs, uint64 return isRequestTimedOutImpl(socket, headersTimeoutMs, requestTimeoutMs); } +template +static void setTracksActivity(us_socket_t* socket, bool tracks) +{ + auto* httpResponseData = reinterpret_cast*>(us_socket_ext(socket)); + httpResponseData->setFlag(uWS::HttpResponseData::HTTP_NODE_TRACKS_ACTIVITY, tracks); +} + +void JSNodeHTTPServerSocket::setInactivityTimer(JSC::VM& vm, JSC::JSObject* timer) +{ + timeoutHeldBack = false; + // After an upgrade to a WebSocket the ext block is no HttpResponseData, and nothing refreshes the timer. + if (!socket || upgraded || us_socket_is_closed(socket)) { + m_inactivityTimer.clear(); + return; + } + m_inactivityTimer.setMayBeNull(vm, this, timer); + if (is_ssl) { + setTracksActivity(socket, !!timer); + } else { + setTracksActivity(socket, !!timer); + } +} + +void JSNodeHTTPServerSocket::touchInactivityTimer() +{ + if (auto* timer = m_inactivityTimer.get()) { + Bun__Timer__refreshTimeout(globalObject(), JSValue::encode(timer)); + timeoutHeldBack = false; + } +} + bool JSNodeHTTPServerSocket::isAuthorized() const { // is secure means that tls was established successfully @@ -558,6 +590,7 @@ JSNodeHTTPServerSocket::JSNodeHTTPServerSocket(JSC::VM& vm, JSC::Structure* stru void JSNodeHTTPServerSocket::detach() { this->m_duplex.clear(); + this->m_inactivityTimer.clear(); this->m_currentResponse.clear(); { Locker locker { m_pipelinedResponsesLock }; @@ -755,6 +788,7 @@ void JSNodeHTTPServerSocket::onUpgraded(us_socket_t* adopted) { socket = adopted; upgraded = true; + m_inactivityTimer.clear(); releaseTunnelReadsForUpgrade(); // The HTTP connection is gone for the responses that did not upgrade it. They stay queued: the close still tells them. @@ -914,6 +948,7 @@ void JSNodeHTTPServerSocket::onDrain() if (this->streamBuffer.bufferedSize() > 0) { auto* globalObject = defaultGlobalObject(this->globalObject()); auto scope = DECLARE_TOP_EXCEPTION_SCOPE(globalObject->vm()); + const size_t bufferedBefore = this->streamBuffer.bufferedSize(); us_socket_buffered_js_write(this->socket, this->is_ssl, this->ended, this->hasUnsentResponseBytes(), this->flushesStreamBufferOnDrain(), &this->streamBuffer, globalObject, JSValue::encode(JSC::jsUndefined()), JSValue::encode(JSC::jsUndefined())); if (auto* exception = scope.exception()) { (void)scope.tryClearException(); @@ -922,6 +957,11 @@ void JSNodeHTTPServerSocket::onDrain() return; } + // The tunnel's bytes are not in the uWS buffer, whose flush HttpContext::onWritable reports. + if (this->streamBuffer.bufferedSize() < bufferedBefore) { + touchInactivityTimer(); + } + if (this->streamBuffer.bufferedSize() > 0) { // need to drain more return; @@ -1031,6 +1071,7 @@ void JSNodeHTTPServerSocket::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(fn->m_remoteAddress); visitor.append(fn->m_localAddress); visitor.append(fn->m_duplex); + visitor.append(fn->m_inactivityTimer); { Locker locker { fn->m_pipelinedResponsesLock }; for (auto& entry : fn->m_pipelinedResponses) { @@ -1074,6 +1115,14 @@ extern "C" JSC::EncodedJSValue Bun__getNodeHTTPServerSocketThisValue(bool is_ssl return JSValue::encode(getNodeHTTPServerSocket(socket)); } +/* The server wrote to a connection with HTTP_NODE_TRACKS_ACTIVITY, or bytes of a pending write left it. */ +extern "C" void Bun__NodeHTTP__touchInactivityTimer(int ssl, us_socket_t* socket) +{ + if (auto* serverSocket = ssl ? getNodeHTTPServerSocket(socket) : getNodeHTTPServerSocket(socket)) { + serverSocket->touchInactivityTimer(); + } +} + extern "C" void Bun__NodeHTTP__onReadParsed(int ssl, us_socket_t* socket) { if (us_socket_is_closed(socket)) { diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocket.h b/src/jsc/bindings/node/JSNodeHTTPServerSocket.h index be9eff8308ee..f10ee5467ce3 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocket.h +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocket.h @@ -65,6 +65,8 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { unsigned heldWriteAwaitsDrain : 1 = 0; /* Set by onClose() for the peerEnded / closeError getters: the peer's FIN, the error of a failed read. */ unsigned peer_ended : 1 = 0; + /* holdTimeoutBackOnce() said yes, and nothing has refreshed m_inactivityTimer since. */ + unsigned timeoutHeldBack : 1 = 0; int closeReadError = 0; /* Whether the connection closed before the kernel had taken everything, also after an upgrade to a WebSocket. */ std::optional closedWithUnsentData; @@ -104,6 +106,19 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { * (ms; 0 disables a check). Reports a given message at most once. */ bool isRequestTimedOut(uint64_t headersTimeoutMs, uint64_t requestTimeoutMs); + /* node:http: takes the Timeout of an armed socket inactivity timeout (never the keep-alive one), or null. Server writes and the progress of a pending write refresh it, like net.Socket in Node. */ + void setInactivityTimer(JSC::VM& vm, JSC::JSObject* timer); + void touchInactivityTimer(); + /* For a timer that fired with a write still pending, where Node looks at writeQueueSize. True once, then false until something refreshes the timer. */ + bool holdTimeoutBackOnce() + { + if (!m_inactivityTimer || timeoutHeldBack) { + return false; + } + timeoutHeldBack = true; + return true; + } + /* node:http server compat - HTTP/1.1 pipelining. Responses for requests * that were parsed while an earlier response on this connection was still * in flight are queued here (in arrival order) and become the connection's @@ -180,6 +195,7 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { mutable JSC::WriteBarrier m_remoteAddress; mutable JSC::WriteBarrier m_localAddress; mutable JSC::WriteBarrier m_duplex; + mutable JSC::WriteBarrier m_inactivityTimer; /* Queued pipelined responses (see appendPipelinedResponse). The lock keeps * mutation on the JS thread coherent with the concurrent GC marker walking diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocketPrototype.cpp b/src/jsc/bindings/node/JSNodeHTTPServerSocketPrototype.cpp index 1d73c52dd2e7..d0e052ef298d 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocketPrototype.cpp +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocketPrototype.cpp @@ -47,6 +47,8 @@ JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketReadStop); JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketReadStart); JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketSetResponseTrailers); JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketIsRequestTimedOut); +JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketSetInactivityTimer); +JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketHoldTimeoutBackOnce); JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketStartPipelinedResponse); JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketStopParsing); JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketCloseWhenDrained); @@ -93,6 +95,8 @@ static const JSC::HashTableValue JSNodeHTTPServerSocketPrototypeTableValues[] = { "readStart"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketReadStart, 0 } }, { "setResponseTrailers"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketSetResponseTrailers, 1 } }, { "isRequestTimedOut"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketIsRequestTimedOut, 2 } }, + { "setInactivityTimer"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketSetInactivityTimer, 1 } }, + { "holdTimeoutBackOnce"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketHoldTimeoutBackOnce, 0 } }, { "startPipelinedResponse"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketStartPipelinedResponse, 3 } }, { "stopParsing"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketStopParsing, 0 } }, { "closeWhenDrained"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketCloseWhenDrained, 0 } }, @@ -219,6 +223,22 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketIsRequestTimedOut, (JSC:: return JSValue::encode(JSC::jsBoolean(thisObject->isRequestTimedOut(headersTimeoutMs, requestTimeoutMs))); } +// node:http: socket.setTimeout() hands over the Timeout of the inactivity timeout that it armed, or takes it back with undefined. +JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketSetInactivityTimer, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = dynamicDowncast(callFrame->thisValue()); + if (thisObject) [[likely]] { + thisObject->setInactivityTimer(globalObject->vm(), callFrame->argument(0).getObject()); + } + return JSValue::encode(JSC::jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketHoldTimeoutBackOnce, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = dynamicDowncast(callFrame->thisValue()); + return JSValue::encode(JSC::jsBoolean(thisObject && thisObject->holdTimeoutBackOnce())); +} + // node:http HTTP/1.1 pipelining: make a queued pipelined response the // connection's current response right before its buffered output is flushed. // Arguments: (responseHandle, isAncient, connectionClose). Returns false when diff --git a/src/runtime/server/NodeHTTPResponse.rs b/src/runtime/server/NodeHTTPResponse.rs index 6e10d6d94523..b14f4abf820f 100644 --- a/src/runtime/server/NodeHTTPResponse.rs +++ b/src/runtime/server/NodeHTTPResponse.rs @@ -187,6 +187,8 @@ unsafe extern "C" { // False when no read of this socket is being parsed. safe fn Bun__NodeHTTP__notifyWhenReadParsed(ssl: core::ffi::c_int, socket: *mut c_void) -> bool; + // Refreshes the Timeout of the socket inactivity timeout, which the server socket holds while the state has `HTTP_NODE_TRACKS_ACTIVITY`. + safe fn Bun__NodeHTTP__touchInactivityTimer(ssl: core::ffi::c_int, socket: *mut c_void); // Moves the connection's captured node:http request-trailer section out. `*out` points into // a C++ thread-local valid until the next call on this thread; caller copies immediately. @@ -875,6 +877,26 @@ impl NodeHTTPResponse { } } +/// Only for a connection whose state has `tracks_node_activity()`. +#[cold] +fn refresh_inactivity_timer(raw_response: uws::AnyResponse) { + Bun__NodeHTTP__touchInactivityTimer( + any_response_is_ssl(&raw_response) as core::ffi::c_int, + raw_response.socket().cast(), + ); +} + +impl NodeHTTPResponse { + /// A server write restarts an armed socket inactivity timeout, like Node's `net.Socket._writeGeneric`. Call it behind the last check that can throw and before the write, which can close the socket. + #[inline] + fn note_write_dispatched(&self, raw_response: uws::AnyResponse, state: uws::State) { + // Node holds a write back behind one that is pending, so a peer that stopped reading still times out. The progress of the pending one counts instead. + if state.tracks_node_activity() && !self.has_unflushed_write() { + refresh_inactivity_timer(raw_response); + } + } +} + fn handle_ended_if_necessary(state: uws::State, global_object: &JSGlobalObject) -> JsResult<()> { if !state.is_response_pending() { return err_throw( @@ -1314,6 +1336,7 @@ impl NodeHTTPResponse { ) { return None; } + self.note_write_dispatched(raw_response, state); self.resume_socket(); self.bytes_written.set(bytes_written); @@ -1380,6 +1403,7 @@ impl NodeHTTPResponse { let state = raw_response.state(); handle_ended_if_necessary(state, global_object)?; + self.note_write_dispatched(raw_response, state); raw_response.write_continue(); Ok(()) } @@ -1413,7 +1437,9 @@ impl NodeHTTPResponse { let Some(raw_response) = self.writer() else { return Ok(()); }; - handle_ended_if_necessary(raw_response.state(), global_object)?; + let state = raw_response.state(); + handle_ended_if_necessary(state, global_object)?; + self.note_write_dispatched(raw_response, state); raw_response.write_informational(string_or_buffer.slice()); Ok(()) } @@ -2012,8 +2038,14 @@ impl NodeHTTPResponse { // sibling `flushed == 0 && RECEIVED_FIN` close in HttpContext:: // onWritable decides; without FIN (SSL WANT_READ, ENOBUFS) retries. let pinned_before = self.pending_pinned_write.get().remaining.len(); - if self.drain_pending_pinned_write(response) { - if self.pending_pinned_write.get().remaining.len() < pinned_before { + let still_pinned = self.drain_pending_pinned_write(response); + let pinned_progress = self.pending_pinned_write.get().remaining.len() < pinned_before; + // The zero-copy tail is not in the uWS buffer, whose flush HttpContext::onWritable counts. + if pinned_progress && response.state().tracks_node_activity() { + refresh_inactivity_timer(response); + } + if still_pinned { + if pinned_progress { return false; } if response.state().is_node_received_fin() { @@ -2186,6 +2218,18 @@ impl NodeHTTPResponse { return Ok(JSValue::js_number_from_int32(-1)); } + if state.tracks_node_activity() { + // Node sends nothing for a chunk that a response without a body discards, nor for an end() behind a complete body. + let sends_bytes = if IS_END { + !bytes.is_empty() || !state.is_http_write_called() || state.is_chunked_body() + } else { + !bytes.is_empty() || !state.is_no_body_status() + }; + if sends_bytes { + self.note_write_dispatched(self.writer().unwrap(), state); + } + } + // A previous zero-copy write's tail must hit the wire before this one; // copy it into backpressure so ordering is preserved. No-op when the // caller correctly waited for 'drain' (the tail was already consumed). @@ -2516,6 +2560,11 @@ impl NodeHTTPResponse { let flags = self.flags.get(); if !flags.contains(Flags::UPGRADED) && !self.is_socket_closed_or_closing() { if let Some(raw_response) = self.writer() { + // Only the call that sends the head: write() with a callback calls this behind every chunk. + let state = raw_response.state(); + if !state.is_http_write_called() { + self.note_write_dispatched(raw_response, state); + } // Don't flush immediately; queue a microtask to uncork the socket. raw_response.flush_headers(false); if raw_response.is_corked() { diff --git a/src/runtime/timer/Timer.rs b/src/runtime/timer/Timer.rs index 4da7bf3c3a08..287a21478aea 100644 --- a/src/runtime/timer/Timer.rs +++ b/src/runtime/timer/Timer.rs @@ -699,6 +699,14 @@ pub(crate) fn clear_interval_export(global: &JSGlobalObject, id: JSValue) -> JsR All::clear_interval(global, id) } +// HOST_EXPORT(Bun__Timer__refreshTimeout, c) +pub(crate) fn refresh_timeout_export(global: &JSGlobalObject, timer: JSValue) { + if let Some(timeout) = TimeoutObject::from_js(timer) { + // SAFETY: `timer` is the wrapper of `timeout`, and the caller keeps it alive. + let _ = unsafe { (*timeout).internals.do_refresh(global, timer) }; + } +} + pub(crate) mod internal_bindings { use super::*; diff --git a/src/uws_sys/Response.rs b/src/uws_sys/Response.rs index e581779ebfc5..c23d447bcbb7 100644 --- a/src/uws_sys/Response.rs +++ b/src/uws_sys/Response.rs @@ -1227,8 +1227,12 @@ bitflags::bitflags! { const HTTP_RESPONSE_PENDING = 8; const HTTP_CONNECTION_CLOSE = 16; const HTTP_WROTE_CONTENT_LENGTH_HEADER = 32; + const HTTP_ANCIENT_REQUEST = 1 << 8; + const HTTP_NO_BODY_STATUS = 1 << 9; + const HTTP_CLOSE_DELIMITED = 1 << 10; const HTTP_NODE_RECEIVED_FIN = 1 << 15; const HTTP_NODE_CLOSE_AFTER_MESSAGE = 1 << 20; + const HTTP_NODE_TRACKS_ACTIVITY = 1 << 24; } } @@ -1273,6 +1277,30 @@ impl State { pub fn is_node_close_after_message(self) -> bool { self.bits() & State::HTTP_NODE_CLOSE_AFTER_MESSAGE.bits() != 0 } + + /// node:http: the connection holds the Timeout of an armed socket inactivity timeout, which the writes of the server refresh (see HttpResponseData.h). + #[inline] + pub fn tracks_node_activity(self) -> bool { + self.bits() & State::HTTP_NODE_TRACKS_ACTIVITY.bits() != 0 + } + + /// A response without a body: HEAD, 1xx, 204, 304. + #[inline] + pub fn is_no_body_status(self) -> bool { + self.bits() & State::HTTP_NO_BODY_STATUS.bits() != 0 + } + + /// The body ends with a terminating chunk, so `end()` still has bytes to send after the last `write()`. + #[inline] + pub fn is_chunked_body(self) -> bool { + self.bits() + & (State::HTTP_WROTE_CONTENT_LENGTH_HEADER + | State::HTTP_ANCIENT_REQUEST + | State::HTTP_CLOSE_DELIMITED + | State::HTTP_NO_BODY_STATUS) + .bits() + == 0 + } } pub enum WriteResult { diff --git a/test/js/node/http/node-http-own-keys-through-an-exchange.expected.txt b/test/js/node/http/node-http-own-keys-through-an-exchange.expected.txt index ba89b38277a9..0dd4bdd132d5 100644 --- a/test/js/node/http/node-http-own-keys-through-an-exchange.expected.txt +++ b/test/js/node/http/node-http-own-keys-through-an-exchange.expected.txt @@ -49,7 +49,7 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /end res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /end res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /end req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture -/end socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/end socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /end res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /end res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /end req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @@ -57,12 +57,12 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /end a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /end req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /end res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/end socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/end socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /end res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /end res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /end res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /end req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture -/end socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/end socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /end res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /end res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /end req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @@ -70,12 +70,12 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /end a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /empty req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /empty res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/empty socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/empty socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /empty res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /empty res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /empty res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /empty req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture -/empty socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/empty socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /empty res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /empty res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kDetachedFrom /empty req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @@ -83,13 +83,13 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /empty a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /write req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /write res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/write socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/write socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /write res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /write res after write: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled /write res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled /write res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /write req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture -/write socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/write socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /write res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /write res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /write req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @@ -97,13 +97,13 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /write a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /head req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /head res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/head socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/head socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /head res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /head res after writeHead: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage /head res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage /head res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage @kDetachedFrom /head req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture -/head socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/head socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /head res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage @kDetachedFrom /head res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage @kDetachedFrom /head req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @@ -111,13 +111,13 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /head a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /length req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /length res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/length socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/length socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /length res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /length res after write: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled /length res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled /length res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /length req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture -/length socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/length socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /length res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /length res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /length req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @@ -125,13 +125,13 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /length a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /callback req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /callback res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/callback socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/callback socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /callback res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /callback res after write: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled /callback res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled /callback res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /callback req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture -/callback socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/callback socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /callback res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /callback res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kBytesBuffered @kAccountingFlushScheduled @kDetachedFrom /callback req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @@ -139,13 +139,13 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /callback a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /flush req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /flush res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/flush socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/flush socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /flush res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /flush res after flushHeaders: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage @kFramingFrozenChunked /flush res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage @kFramingFrozenChunked /flush res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage @kFramingFrozenChunked @kDetachedFrom /flush req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture -/flush socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/flush socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /flush res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage @kFramingFrozenChunked @kDetachedFrom /flush res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback @kSnapshotStatusCode @kSnapshotStatusMessage @kFramingFrozenChunked @kDetachedFrom /flush req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @@ -153,12 +153,12 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /flush a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /late req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /late res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/late socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/late socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /late res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /late res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /late res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /late req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @kAutoDestroyed -/late socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/late socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /late res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /late res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /late req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @kAutoDestroyed @@ -166,12 +166,12 @@ a socket at connection: _events _readableState _writableState allowHalfOpen _max /late a client's response at close: _consuming _dumped complete _closed rawTrailers _events _readableState _maxListeners socket httpVersionMajor httpVersionMinor httpVersion joinDuplicateHeaders upgrade url method statusCode statusMessage client req _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @shapeMode @kCapture @noBody @kAutoDestroyed /post req: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture /post res: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback -/post socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/post socket: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /post res, for-in: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage setHeader appendHeader setHeaders removeHeader cork uncork setTimeout _ended _writeRaw writeEarlyHints writeInformation writeProcessing writeContinue end write _callPendingCallbacks _finish detachSocket _implicitHeader _send writeHead assignSocket destroy emit flushHeaders _isLenientHeaderValidation _renderHeaders _storeHeader getHeader getHeaderNames getRawHeaderNames getHeaders hasHeader addTrailers _flush _flushOutput pipe eventNames setMaxListeners getMaxListeners addListener on prependListener once prependOnceListener removeListener off removeAllListeners listeners rawListeners listenerCount /post res after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /post res at finish: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /post req at finish: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @kAutoDestroyed -/post socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @socketTimeoutTimer @keepAliveTimeoutSet +/post socket at finish: _events _readableState _writableState allowHalfOpen _maxListeners _parent _parentWrap bytesRead connecting _host _port timeout _pendingData _pendingEncoding _hadError isServer _handle server _server _peername _sockname _closeAfterHandlingError _eventsCount parser _httpMessage _secureEstablished _paused encrypted @shapeMode @kCapture @kupgraded @kStandaloneWrap @kOnUpgradedClose @kOwesRawClose @kSetNoDelay @kSetKeepAlive @kSetKeepAliveInitialDelay @khandlers @kDestroyOnRead @kBytesWritten @closed @ended @::buntlsconnectoptions:: @lastWriteQueueSize @::buntimeout:: @writeCallback @ksocket @kStreamingEnabled @kBoundOnAbort @keepAliveIdleStart @kBytesWritten @kClosedWithUnsentData @handle @kUpgradeIncoming @kOnReadParsed @kHandoffResponse @kDestroySoon @http.server.socketHandedOff @request @kStopParsingOnCloseListener @closeCallback @keepAliveTimeoutSet @socketTimeoutTimer /post res a tick after end: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /post res at close: _events _eventsCount _maxListeners outputData outputSize destroyed _last maxRequestsOnConnectionReached _defaultKeepAlive useChunkedEncodingByDefault sendDate _removedConnection _removedContLen _removedTE strictContentLength _contentLength _hasBody _trailer finished _headerSent _closed _header _keepAliveTimeout _onPendingData req _sent100 statusCode statusMessage _ended @shapeMode @kCapture @kChunkedEncoding @kShouldKeepAlive @kBytesWritten @kNeedDrain @corked @kChunkedBuffer @kChunkedLength @kSocket @kOutHeaders @errored @kHighWaterMark @kRejectNonStandardBodyWrites @kLenientValidation @headerState @pendingCallbacks @handle @fakeSocket @closeCallback /post req at the close of res: _consuming _dumped complete _closed rawTrailers url method socket client upgrade _events _readableState _maxListeners _eventsCount @aborted @eofInProgress @kHeaders @kReqShouldKeepAlive @kRawHeaders @kHeaderSource @kHeadersCount @kTrailers @kTrailersCount @kAbortController @handle @noBody @shapeMode @kCapture @kAutoDestroyed diff --git a/test/js/node/http/node-http-own-values-through-an-exchange.expected.txt b/test/js/node/http/node-http-own-values-through-an-exchange.expected.txt index 18618f2e12a6..4ddd9a938b0d 100644 --- a/test/js/node/http/node-http-own-values-through-an-exchange.expected.txt +++ b/test/js/node/http/node-http-own-values-through-an-exchange.expected.txt @@ -4,62 +4,62 @@ level 1 above a socket: constructor bytesWritten destroyNow _onTimeout _unrefTim /end socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=undefined | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose /end res after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=5 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /end res at finish: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=5 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket -/end socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=true +/end socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @keepAliveTimeoutSet=true | @socketTimeoutTimer=a Timeout /end res a tick after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=5 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket /end res at close: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=true | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=5 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=true | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket /header res: _events=[null] {finish: function emitResponseFinish} | _eventsCount=1 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose -/header socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=false +/header socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @keepAliveTimeoutSet=false | @socketTimeoutTimer=a Timeout /header res after setHeader: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {x-one: ["X-One","1"]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /header res after four stores: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {x-one: ["X-One",["1","2"]], x-two: ["X-Two",["a","b"]], x-three: ["x-three",3]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /header res after removeHeader: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {x-one: ["X-One",["1","2"]], x-three: ["x-three",3]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /header res after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {x-one: ["X-One",["1","2"]], x-three: ["x-three",3]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /header res at finish: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {x-one: ["X-One",["1","2"]], x-three: ["x-three",3]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket -/header socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=true +/header socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @keepAliveTimeoutSet=true | @socketTimeoutTimer=a Timeout /header res a tick after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {x-one: ["X-One",["1","2"]], x-three: ["x-three",3]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket /header res at close: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=true | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=true | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {x-one: ["X-One",["1","2"]], x-three: ["x-three",3]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket /removed res: _events=[null] {finish: function emitResponseFinish} | _eventsCount=1 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose -/removed socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=false +/removed socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @keepAliveTimeoutSet=false | @socketTimeoutTimer=a Timeout /removed res after removeHeader: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /removed res after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /removed res at finish: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket -/removed socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=true +/removed socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @keepAliveTimeoutSet=true | @socketTimeoutTimer=a Timeout /removed res a tick after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket /removed res at close: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=true | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=true | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kDetachedFrom=a Socket /head res: _events=[null] {finish: function emitResponseFinish} | _eventsCount=1 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose -/head socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=false +/head socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @keepAliveTimeoutSet=false | @socketTimeoutTimer=a Timeout /head res after writeHead: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=a string of 2 | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {x-one: ["x-one","1"], content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=1 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose | @kSnapshotStatusCode=200 | @kSnapshotStatusMessage=a string of 2 /head res after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=a string of 2 | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {x-one: ["x-one","1"], content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose | @kSnapshotStatusCode=200 | @kSnapshotStatusMessage=a string of 2 /head res at finish: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=a string of 2 | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {x-one: ["x-one","1"], content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kSnapshotStatusCode=200 | @kSnapshotStatusMessage=a string of 2 | @kDetachedFrom=a Socket -/head socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=true +/head socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @keepAliveTimeoutSet=true | @socketTimeoutTimer=a Timeout /head res a tick after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=a string of 2 | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {x-one: ["x-one","1"], content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kSnapshotStatusCode=200 | @kSnapshotStatusMessage=a string of 2 | @kDetachedFrom=a Socket /head res at close: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=true | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=2 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=true | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=a string of 2 | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {x-one: ["x-one","1"], content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined | @kSnapshotStatusCode=200 | @kSnapshotStatusMessage=a string of 2 | @kDetachedFrom=a Socket /length res: _events=[null] {finish: function emitResponseFinish} | _eventsCount=1 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose -/length socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=false +/length socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @keepAliveTimeoutSet=false | @socketTimeoutTimer=a Timeout /length res after write: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose | @kBytesBuffered=2 | @kAccountingFlushScheduled=true /length res after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=[null] {content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose | @kBytesBuffered=2 | @kAccountingFlushScheduled=true /length res at finish: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=undefined | @kBytesBuffered=0 | @kAccountingFlushScheduled=false | @kDetachedFrom=a Socket -/length socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=true +/length socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @keepAliveTimeoutSet=true | @socketTimeoutTimer=a Timeout /length res a tick after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=undefined | @kBytesBuffered=0 | @kAccountingFlushScheduled=false | @kDetachedFrom=a Socket /length res at close: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=true | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=true | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=[null] {content-length: ["content-length",2]} | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=false | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=undefined | @kBytesBuffered=0 | @kAccountingFlushScheduled=false | @kDetachedFrom=a Socket /write res: _events=[null] {finish: function emitResponseFinish} | _eventsCount=1 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose -/write socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=false +/write socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @keepAliveTimeoutSet=false | @socketTimeoutTimer=a Timeout /write res after write: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose | @kBytesBuffered=6 | @kAccountingFlushScheduled=true /write res after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose | @kBytesBuffered=6 | @kAccountingFlushScheduled=true /write res at finish: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=undefined | @kBytesBuffered=0 | @kAccountingFlushScheduled=false | @kDetachedFrom=a Socket -/write socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=true +/write socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @keepAliveTimeoutSet=true | @socketTimeoutTimer=a Timeout /write res a tick after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=undefined | @kBytesBuffered=0 | @kAccountingFlushScheduled=false | @kDetachedFrom=a Socket /write res at close: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=true | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=true | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=undefined | @kBytesBuffered=0 | @kAccountingFlushScheduled=false | @kDetachedFrom=a Socket /late res: _events=[null] {finish: function emitResponseFinish} | _eventsCount=1 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose -/late socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=false +/late socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @keepAliveTimeoutSet=false | @socketTimeoutTimer=a Timeout /late res after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=4 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /late res at finish: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=4 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=function onClose -/late socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: null, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=true +/late socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: null, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @keepAliveTimeoutSet=true | @socketTimeoutTimer=a Timeout /late res a tick after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=4 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=function onClose /late res at close: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=true | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=4 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=true | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined /post res: _events=[null] {finish: function emitResponseFinish} | _eventsCount=1 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=null | _hasBody=true | _trailer=a string of 0 | finished=false | _headerSent=false | _closed=false | _header=null | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=0 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose -/post socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=false +/post socket: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=0 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: a IncomingMessage, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=a ServerResponse | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=a IncomingMessage | @kStopParsingOnCloseListener=true | @closeCallback=function onServerResponseClose | @keepAliveTimeoutSet=false | @socketTimeoutTimer=a Timeout /post res after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=4 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=a Socket | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=a Socket | @closeCallback=function onClose /post res at finish: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=4 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=function onClose -/post socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: null, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @socketTimeoutTimer=a Timeout | @keepAliveTimeoutSet=true +/post socket at finish: _events=[null] {timeout: function onNodeHTTPServerSocketTimeout, error: function socketOnError, close: function onSocketCloseStopParsing} | _readableState=a Object | _writableState=a Object | allowHalfOpen=true | _maxListeners=undefined | _parent=null | _parentWrap=null | bytesRead=0 | connecting=false | _host=undefined | _port=undefined | timeout=6000 | _pendingData=undefined | _pendingEncoding=undefined | _hadError=false | isServer=false | _handle=null | server=a Server | _server=undefined | _peername=null | _sockname=null | _closeAfterHandlingError=false | _eventsCount=3 | parser={6: function serverParserShimOnTimeout, incoming: null, outgoing: null, socket: a Socket, free: function serverParserShimFree, close: function serverParserShimClose} | _httpMessage=null | _secureEstablished=false | _paused=false | encrypted=false | @shapeMode=false | @kCapture=false | @kupgraded=null | @kStandaloneWrap=false | @kOnUpgradedClose=undefined | @kOwesRawClose=false | @kSetNoDelay=false | @kSetKeepAlive=false | @kSetKeepAliveInitialDelay=0 | @khandlers={open: function open, data: function data, drain: function drain, end: function end, session: function session, keylog: function keylog, close: function close, handshake: function handshake, error: function error, timeout: function timeout, connectError: function connectError} | @kDestroyOnRead=false | @kBytesWritten=undefined | @closed=false | @ended=false | @::buntlsconnectoptions::=null | @lastWriteQueueSize=0 | @::buntimeout::=null | @writeCallback=undefined | @ksocket=undefined | @kStreamingEnabled=false | @kBoundOnAbort=function bound onServerRequestEvent | @keepAliveIdleStart=a large number | @kBytesWritten=0 | @kClosedWithUnsentData=undefined | @handle=a Object | @kUpgradeIncoming=undefined | @kOnReadParsed=undefined | @kHandoffResponse=undefined | @kDestroySoon=false | @http.server.socketHandedOff=false | @request=undefined | @kStopParsingOnCloseListener=true | @closeCallback=undefined | @keepAliveTimeoutSet=true | @socketTimeoutTimer=a Timeout /post res a tick after end: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=false | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=4 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=false | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=function onClose /post res at close: _events=[null] {finish: [null,null], close: function } | _eventsCount=2 | _maxListeners=undefined | outputData=an array of 0 | outputSize=0 | destroyed=true | _last=false | maxRequestsOnConnectionReached=false | _defaultKeepAlive=true | useChunkedEncodingByDefault=true | sendDate=true | _removedConnection=false | _removedContLen=false | _removedTE=false | strictContentLength=false | _contentLength=4 | _hasBody=true | _trailer=a string of 0 | finished=true | _headerSent=false | _closed=true | _header=a string of 1 | _keepAliveTimeout=5000 | _onPendingData=function nop | req=a IncomingMessage | _sent100=false | statusCode=200 | statusMessage=undefined | _ended=true | @shapeMode=false | @kCapture=false | @kChunkedEncoding=false | @kShouldKeepAlive=true | @kBytesWritten=0 | @kNeedDrain=false | @corked=0 | @kChunkedBuffer=an array of 0 | @kChunkedLength=0 | @kSocket=null | @kOutHeaders=null | @errored=null | @kHighWaterMark=a large number | @kRejectNonStandardBodyWrites=false | @kLenientValidation=undefined | @headerState=2 | @pendingCallbacks=an array of 0 | @handle=a Object | @fakeSocket=null | @closeCallback=undefined diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index eb69a7860627..a2c7a406b595 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -1,8 +1,12 @@ +import { setSocketOptions } from "bun:internal-for-testing"; import { describe, expect, test } from "bun:test"; +import { isWindows, tls as tlsOptions } from "harness"; import { once } from "node:events"; import http from "node:http"; +import https from "node:https"; import net from "node:net"; import { pipeline, Writable } from "node:stream"; +import tls from "node:tls"; // Each test opens a raw TCP socket against a server whose timeout knob is a // few hundred ms and waits for the server to close the connection. A small @@ -584,13 +588,13 @@ describe.concurrent("node:http server socket setTimeout(msecs) checks", () => { ); }); -// A write on the response is activity on the connection, like a read: Node's -// net.Socket restarts its inactivity timer in _writeGeneric. Each probe holds -// one request open on a raw TCP socket. The timing checks are one-sided -// ('timeout' must not come sooner than a full period after the activity) or -// compare against a reference timer. Timers fire in deadline order, so a slow -// machine cannot swap the socket's timer and the reference. -describe("node:http response writes restart the socket inactivity timeout", () => { +// What the server writes is activity on the connection, like what it reads: Node's +// net.Socket restarts its inactivity timer when it hands a write to the handle and +// when a pending write completes. Each probe holds one request open on a raw TCP +// socket. A timing check is one-sided ('timeout' must not come sooner than a full +// period after the activity) or compares against a reference timer. Timers fire in +// deadline order, so a slow machine cannot swap the socket's timer and the reference. +describe("node:http server writes restart the socket inactivity timeout", () => { const TIMEOUT = 400; // The timer clock counts whole milliseconds. A server that ignores the // activity fires TIMEOUT / 2 early, far outside this. @@ -617,42 +621,116 @@ describe("node:http response writes restart the socket inactivity timeout", () = } const GET = "GET / HTTP/1.1\r\nHost: a\r\n\r\n"; - test.concurrent.each(["server.timeout", "res.setTimeout()", "req.setTimeout()", "socket.setTimeout()"])( - "%s does not fire while the response keeps writing", - async armedBy => { + test.concurrent.each([ + "server.timeout", + "server.setTimeout(ms, callback)", + "res.setTimeout(ms)", + "res.setTimeout(ms, callback)", + "req.setTimeout(ms)", + "socket.setTimeout(ms)", + ])("%s does not fire while the response keeps writing", async armedBy => { + let lastWriteAt = 0; + const { promise: firstTimeout, resolve: onFirstTimeout } = Promise.withResolvers(); + const onTimeout = () => onFirstTimeout(performance.now() - lastWriteAt); + const server = http.createServer((req, res) => { + if (armedBy === "res.setTimeout(ms)") res.setTimeout(TIMEOUT); + if (armedBy === "res.setTimeout(ms, callback)") res.setTimeout(TIMEOUT, onTimeout); + if (armedBy === "req.setTimeout(ms)") req.setTimeout(TIMEOUT); + if (armedBy === "socket.setTimeout(ms)") req.socket.setTimeout(TIMEOUT); + req.socket.on("timeout", onTimeout); + res.writeHead(200, { "Content-Type": "text/plain" }); + // Stream for longer than the timeout, one chunk every TIMEOUT / 8. + const startedAt = performance.now(); + const interval = setInterval(() => { + lastWriteAt = performance.now(); + if (lastWriteAt - startedAt < TIMEOUT * 1.5) { + res.write("chunk\n"); + return; + } + clearInterval(interval); + res.end("done\n"); + }, TIMEOUT / 8); + res.on("close", () => clearInterval(interval)); + }); + if (armedBy === "server.timeout") server.timeout = TIMEOUT; + if (armedBy === "server.setTimeout(ms, callback)") server.setTimeout(TIMEOUT, onTimeout); + // The idle connection is closed one more period after the response. + server.keepAliveTimeout = TIMEOUT; + server.keepAliveTimeoutBuffer = 0; + const port = await listen(server); + let client: net.Socket | undefined; + try { + client = await send(port, GET); + // A server that ignores writes fires within TIMEOUT / 8 of one. A machine that stalls + // between two writes can only make this longer. + expect(await Promise.race([firstTimeout, lost(client)])).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + } finally { + client?.destroy(); + server.closeAllConnections(); + server.close(); + } + }); + + // Between two requests the keep-alive timeout takes the place of the socket's timer, and the + // next request arms the first one again. With the same period the two share one timer. + test.concurrent.each([ + ["server.timeout", 1.5], + ["res.setTimeout(ms)", 1.5], + ["server.timeout", 1], + ])( + "%s lets each response on a kept-alive connection stream past it, then a keepAliveTimeout %s times as long closes the connection", + async (armedBy, factor) => { + const KEEP_ALIVE = TIMEOUT * factor; let lastWriteAt = 0; - const { promise: firstTimeout, resolve: onFirstTimeout } = Promise.withResolvers(); + let timedOutAt = 0; + let responses = 0; + let reference: ReturnType | undefined; + const { promise: settled, resolve: onSettled } = Promise.withResolvers(); const server = http.createServer((req, res) => { - if (armedBy === "res.setTimeout()") res.setTimeout(TIMEOUT); - if (armedBy === "req.setTimeout()") req.setTimeout(TIMEOUT); - if (armedBy === "socket.setTimeout()") req.socket.setTimeout(TIMEOUT); - req.socket.on("timeout", () => onFirstTimeout(performance.now() - lastWriteAt)); + if (armedBy === "res.setTimeout(ms)") res.setTimeout(TIMEOUT); res.writeHead(200, { "Content-Type": "text/plain" }); - // Stream for longer than the timeout, one chunk every TIMEOUT / 8. const startedAt = performance.now(); const interval = setInterval(() => { lastWriteAt = performance.now(); - if (lastWriteAt - startedAt < TIMEOUT * 1.5) { + if (lastWriteAt - startedAt < TIMEOUT * 1.25) { res.write("chunk\n"); return; } clearInterval(interval); res.end("done\n"); + // Long after the keep-alive timeout of the idle connection. + if (++responses === 2) reference = setTimeout(() => onSettled("the reference timer"), KEEP_ALIVE * 3); }, TIMEOUT / 8); res.on("close", () => clearInterval(interval)); }); if (armedBy === "server.timeout") server.timeout = TIMEOUT; - // The idle connection is closed one more period after the response. - server.keepAliveTimeout = TIMEOUT; + server.keepAliveTimeout = KEEP_ALIVE; server.keepAliveTimeoutBuffer = 0; + server.on("timeout", socket => { + timedOutAt = performance.now(); + // A machine that stalls for a whole period between two writes gets a 'timeout' that is due. The listener keeps the socket. + if (responses < 2 && timedOutAt - lastWriteAt >= TIMEOUT - SLACK) return; + onSettled(`'timeout' after ${responses} responses`); + socket.destroy(); + }); const port = await listen(server); let client: net.Socket | undefined; try { client = await send(port, GET); - // A server that ignores writes fires within TIMEOUT / 8 of one. A machine that stalls - // between two writes can only make this longer. - expect(await Promise.race([firstTimeout, lost(client)])).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + let received = ""; + let requests = 1; + client.on("data", chunk => { + received += chunk; + // The end of the first response: the connection is idle now. + if (requests === 1 && received.endsWith("done\n\r\n0\r\n\r\n")) { + requests++; + client!.write(GET); + } + }); + expect(await Promise.race([settled, lost(client)])).toBe("'timeout' after 2 responses"); + expect(timedOutAt - lastWriteAt).toBeGreaterThanOrEqual(KEEP_ALIVE - SLACK); } finally { + clearTimeout(reference); client?.destroy(); server.closeAllConnections(); server.close(); @@ -660,35 +738,52 @@ describe("node:http response writes restart the socket inactivity timeout", () = }, ); - const activities: Record void> = { - "res.write()": res => res.write("chunk\n"), - "res.end()": res => res.end("done\n"), - "res.flushHeaders()": res => res.flushHeaders(), - "res.writeProcessing()": res => res.writeProcessing(), - "res.writeContinue()": res => res.writeContinue(), + // One case for each way the server puts bytes on the connection, and for each call that puts + // none there in Node. `before` runs when the request arrives, `act` half a period later. + type Act = (res: http.ServerResponse) => void; + const activities: Record = { + "res.write()": { counts: true, act: res => res.write("chunk\n") }, + "res.write('')": { counts: true, before: res => res.write("chunk\n"), act: res => res.write("") }, + "res.end(chunk)": { counts: true, act: res => res.end("done\n") }, + "res.end() that sends the last chunk": { counts: true, before: res => res.write("chunk\n"), act: res => res.end() }, + "res.flushHeaders()": { counts: true, act: res => res.flushHeaders() }, + "res.writeContinue()": { counts: true, act: res => res.writeContinue() }, + "res.writeProcessing()": { counts: true, act: res => res.writeProcessing() }, + "res.writeEarlyHints()": { + counts: true, + act: res => res.writeEarlyHints({ link: "; rel=preload; as=style" }), + }, + "res.writeHead()": { counts: false, act: res => res.writeHead(200) }, + "res.end() behind a complete body": { + counts: false, + before: res => res.writeHead(200, { "Content-Length": 1 }).write("x"), + act: res => res.end(), + }, }; - test.concurrent.each(Object.keys(activities))("the inactivity period restarts at %s", async name => { - let activityAt = 0; + test.concurrent.each(Object.keys(activities))("the inactivity period after %s", async name => { + const { counts, before, act } = activities[name]; + let actedAt = 0; let reference: ReturnType | undefined; let referenceFired = false; const { promise: timedOut, resolve: onTimedOut } = Promise.withResolvers(); const server = http.createServer((req, res) => { + before?.(res); setTimeout(() => { - // After the exact deadline (the activity plus TIMEOUT) and before the deadline of - // a timer that only starts over when it fires (the activity plus 1.5 * TIMEOUT). - reference = setTimeout(() => (referenceFired = true), TIMEOUT * 1.25); - activityAt = performance.now(); - activities[name](res); + // The socket's timer is due one period after the last activity: after `act` when it + // counts, after the request when it does not. The reference lies between the two. + reference = setTimeout(() => (referenceFired = true), TIMEOUT * (counts ? 1.25 : 0.75)); + actedAt = performance.now(); + act(res); }, TIMEOUT / 2); }); server.timeout = TIMEOUT; // No keep-alive timeout: server.timeout stays armed after res.end(). server.keepAliveTimeout = 0; server.on("timeout", socket => { - // Only on a machine that stalls past the timeout before the activity runs. The - // activity then starts the timer again, and the next 'timeout' is the one measured. - if (activityAt === 0) return; + // Only on a machine that stalls past the timeout before `act` runs. A counted one + // then starts the timer again, and the next 'timeout' is the one measured. + if (actedAt === 0) return; onTimedOut(performance.now()); socket.destroy(); }); @@ -696,8 +791,8 @@ describe("node:http response writes restart the socket inactivity timeout", () = let client: net.Socket | undefined; try { client = await send(port, GET); - const quietFor = (await Promise.race([timedOut, lost(client)])) - activityAt; - expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + const quietFor = (await Promise.race([timedOut, lost(client)])) - actedAt; + if (counts) expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); expect(referenceFired).toBe(false); } finally { clearTimeout(reference); @@ -710,7 +805,9 @@ describe("node:http response writes restart the socket inactivity timeout", () = test.concurrent("a write after a 'timeout' that kept the socket open starts the timer again", async () => { const events: string[] = []; let writeAt = 0; - const { promise: timedOutAgain, resolve: onTimedOutAgain } = Promise.withResolvers(); + let timedOutAgainAt = 0; + let reference: ReturnType | undefined; + const { promise: settled, resolve: onSettled } = Promise.withResolvers(); let response: http.ServerResponse | undefined; const server = http.createServer((req, res) => { response = res; @@ -719,56 +816,96 @@ describe("node:http response writes restart the socket inactivity timeout", () = server.timeout = TIMEOUT; server.on("timeout", () => { events.push("timeout"); - if (writeAt !== 0) return onTimedOutAgain(performance.now()); + if (writeAt !== 0) { + timedOutAgainAt = performance.now(); + return onSettled(); + } // A listener keeps the socket open. Nothing reads or writes until this write. setTimeout(() => { events.push("write"); writeAt = performance.now(); response!.write("late\n"); + // Long after the second 'timeout'. A server that ignores the write never fires again. + reference = setTimeout(() => { + events.push("the reference timer"); + onSettled(); + }, TIMEOUT * 3); }, TIMEOUT / 4); }); const port = await listen(server); let client: net.Socket | undefined; try { client = await send(port, GET); - const quietFor = (await Promise.race([timedOutAgain, lost(client)])) - writeAt; + await Promise.race([settled, lost(client)]); expect(events).toEqual(["timeout", "write", "timeout"]); - expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + expect(timedOutAgainAt - writeAt).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { + clearTimeout(reference); client?.destroy(); server.closeAllConnections(); server.close(); } }); - // Writes that are no activity in Node: write_() drops a write to a response without a body - // and validates before it sends anything, and a write to a peer that stopped reading only - // backs up. A server that ignores every write passes these too. They guard against counting - // such a write, which keeps the connection of a handler that writes forever open. - const noActivity: Record void }> = - { - "a write that a HEAD response discards": { - request: "HEAD / HTTP/1.1\r\nHost: a\r\n\r\n", - setup: res => res.writeHead(200, { "Content-Type": "text/event-stream" }), - }, - "a write that throws": { - request: GET, - // The implicit header is invalid, so every write throws before it sends anything. - setup: res => (res.statusCode = 1000), - }, - "a write to a peer that stopped reading": { - request: GET, - stalled: true, - // More than the kernel buffers take: this write and every later one back up. - setup: res => res.write(Buffer.alloc(16 * 1024 * 1024, "a")), - }, - }; + // With the same period the keep-alive timeout uses the timer of server.timeout, which has fired here. + test.concurrent("keepAliveTimeout closes the idle connection after a 'timeout' that kept the socket", async () => { + let endedAt = 0; + let timedOutAgainAt = 0; + let reference: ReturnType | undefined; + const { promise: settled, resolve: onSettled } = Promise.withResolvers(); + let response: http.ServerResponse | undefined; + const server = http.createServer((req, res) => { + response = res; + res.writeHead(200, { "Content-Length": 1 }).write("x"); + }); + server.timeout = TIMEOUT; + server.keepAliveTimeout = TIMEOUT; + server.keepAliveTimeoutBuffer = 0; + server.on("timeout", socket => { + if (endedAt !== 0) { + timedOutAgainAt = performance.now(); + onSettled("'timeout' again"); + return socket.destroy(); + } + // A listener keeps the socket. The body is complete, so this end() sends nothing that could start the timer. + endedAt = performance.now(); + response!.end(); + // Long after the keep-alive timeout. A server that trusts the timer that fired never closes the connection. + reference = setTimeout(() => onSettled("the reference timer"), TIMEOUT * 3); + }); + const port = await listen(server); + let client: net.Socket | undefined; + try { + client = await send(port, GET); + expect(await Promise.race([settled, lost(client)])).toBe("'timeout' again"); + expect(timedOutAgainAt - endedAt).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + } finally { + clearTimeout(reference); + client?.destroy(); + server.closeAllConnections(); + server.close(); + } + }); + + // Writes that put nothing on the connection in Node: write_() drops a chunk for a response + // without a body, and it validates before it sends anything. A server that ignores every + // write passes these too. They guard against counting such a write, which keeps the + // connection of a handler that writes forever open. + const discarded: Record = { + "a HEAD response": { + request: "HEAD / HTTP/1.1\r\nHost: a\r\n\r\n", + setup: res => res.writeHead(200, { "Content-Type": "text/event-stream" }), + }, + "a 204 response": { request: GET, setup: res => res.writeHead(204) }, + // The implicit header is invalid, so every write throws before it sends anything. + "a response whose head is invalid": { request: GET, setup: res => (res.statusCode = 1000) }, + }; - test.concurrent.each(Object.keys(noActivity))("%s does not restart the timer", async name => { + test.concurrent.each(Object.keys(discarded))("writes to %s do not restart the timer", async name => { let reference: ReturnType | undefined; const { promise: settled, resolve: onSettled } = Promise.withResolvers(); const server = http.createServer((req, res) => { - noActivity[name].setup(res); + discarded[name].setup(res); const interval = setInterval(() => { try { res.write("data: ping\n\n"); @@ -786,7 +923,7 @@ describe("node:http response writes restart the socket inactivity timeout", () = const port = await listen(server); let client: net.Socket | undefined; try { - client = await send(port, noActivity[name].request, noActivity[name].stalled); + client = await send(port, discarded[name].request); expect(await Promise.race([settled, lost(client)])).toBe("'timeout'"); } finally { clearTimeout(reference); @@ -796,49 +933,173 @@ describe("node:http response writes restart the socket inactivity timeout", () = } }); - test.concurrent("a request body chunk after a write moved the deadline gets a full period", async () => { - // The write at 0.6 * TIMEOUT moves the deadline from TIMEOUT to 1.6 * TIMEOUT, and the - // body chunk arrives between the two. This guards how the deadline moves, not the bug. - // A server that ignores writes passes too: the 'timeout' at TIMEOUT is swallowed below - // and the chunk restarts the spent timer. It fails when the expiry puts a new, shorter - // timer in the slot, because the chunk then refreshes only 0.6 * TIMEOUT. - let chunkSentAt = 0; - let chunkReceived = false; + test.concurrent("a peer that stops reading is timed out at the second expiry", async () => { + let pendingSince = 0; + let reference: ReturnType | undefined; + let referenceFired = false; const { promise: timedOut, resolve: onTimedOut } = Promise.withResolvers(); - const { promise: wrote, resolve: onWrote } = Promise.withResolvers(); const server = http.createServer((req, res) => { - req.on("data", chunk => { - if (chunk.toString("latin1") === "late") chunkReceived = true; - }); - setTimeout(() => { - res.write("chunk\n"); - onWrote(); - }, TIMEOUT * 0.6); + // More than the kernel buffers take, so this write stays pending and every later one queues behind it. + res.write(Buffer.alloc(16 * 1024 * 1024, "a")); + pendingSince = performance.now(); + const interval = setInterval(() => res.write("data: ping\n\n"), TIMEOUT / 8); + res.on("close", () => clearInterval(interval)); + // Long after the second expiry. A server that counts the queued writes never times out. + reference = setTimeout(() => { + referenceFired = true; + onTimedOut(performance.now()); + }, TIMEOUT * 6); }); server.timeout = TIMEOUT; server.on("timeout", socket => { - // Before the chunk: a server that ignores writes, or a machine that stalls until the - // moved deadline has passed. The chunk then starts the timer again. - if (!chunkReceived) return; onTimedOut(performance.now()); socket.destroy(); }); const port = await listen(server); let client: net.Socket | undefined; try { - client = await send(port, "POST / HTTP/1.1\r\nHost: a\r\nContent-Length: 100\r\n\r\nearly"); - const connectionLost = lost(client); - await Promise.race([wrote, connectionLost]); - setTimeout(() => { - chunkSentAt = performance.now(); - client!.write("late"); - }, TIMEOUT * 0.7); - const quietFor = (await Promise.race([timedOut, connectionLost])) - chunkSentAt; - expect(quietFor).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + client = await send(port, GET, true); + // Node holds the first 'timeout' back, because the write was pending and the kernel had taken a part of it. + const quietFor = (await Promise.race([timedOut, lost(client)])) - pendingSince; + expect(quietFor).toBeGreaterThanOrEqual(2 * TIMEOUT - SLACK); + expect(referenceFired).toBe(false); } finally { + clearTimeout(reference); client?.destroy(); server.closeAllConnections(); server.close(); } }); }); + +// The progress of a write that is pending is activity too: Node looks at the write queue when the +// timer fires. setSocketOptions() does nothing on Windows, where the kernel can take a whole body at once. +describe.skipIf(isWindows)("node:http: a write that drains slowly holds the socket inactivity timeout back", () => { + const TIMEOUT = 400; + // Small kernel buffers at both ends keep most of the body with the server, whatever the limits of the machine are. + const KERNEL_BUFFER = 64 * 1024; + const BODY = Buffer.alloc(2 * 1024 * 1024, "a"); + // A server that holds 'timeout' back once, and does not see the bytes leave, fires at the second + // expiry. For longer than that the reader takes 16 KB every 20 ms, less than half of the body in + // all. Then it takes the rest. + const SLOW_FOR = TIMEOUT * 2.75; + const STEP = 16 * 1024; + function readSlowly(client: net.Socket) { + let budget = 0; + let received = 0; + let head = -1; + let startedAt = 0; + const { promise, resolve, reject } = Promise.withResolvers(); + client.on("data", chunk => { + startedAt ||= performance.now(); + if (head === -1) { + const end = chunk.indexOf("\r\n\r\n"); + if (end !== -1) head = received + end + 4; + } + received += chunk.length; + budget -= chunk.length; + if (budget <= 0) client.pause(); + }); + const pump = setInterval(() => { + // A chunk that was larger than the step is paid off before the next one. + if (startedAt === 0 || performance.now() - startedAt < SLOW_FOR) budget = Math.min(budget + STEP, STEP); + else budget = Infinity; + if (budget > 0) client.resume(); + }, 20); + client.on("error", reject); + // The server closes the connection behind the body. + client.on("close", () => { + clearInterval(pump); + resolve(received - head); + }); + return promise; + } + // `pendingFor` is how long the server had bytes of the write left. Less than two periods proves nothing. + const drainedWithoutTimeout = { body: BODY.length, timeouts: 0, pendingForTwoPeriods: true }; + + // `done` runs when the server has no byte of the body left. + type Respond = (res: http.ServerResponse, done: () => void) => void; + const responses: Record = { + "res.end()": { respond: (res, done) => res.end(BODY, done) }, + "res.end() over https": { secure: true, respond: (res, done) => res.end(BODY, done) }, + "res.write()": { + respond: (res, done) => { + const drained = () => { + done(); + res.end(); + }; + if (res.write(BODY)) drained(); + else res.once("drain", drained); + }, + }, + }; + + test.concurrent.each(Object.keys(responses))("no 'timeout' while a slow reader drains one %s", async name => { + const { secure, respond } = responses[name]; + let timeouts = 0; + let pendingFor = 0; + const handler = (req: http.IncomingMessage, res: http.ServerResponse) => { + setSocketOptions(req.socket, 1, KERNEL_BUFFER); + res.writeHead(200, { "Content-Length": BODY.length, "Connection": "close" }); + const wroteAt = performance.now(); + respond(res, () => (pendingFor = performance.now() - wroteAt)); + }; + const server = secure ? https.createServer(tlsOptions, handler) : http.createServer(handler); + server.timeout = TIMEOUT; + server.on("timeout", socket => { + timeouts++; + socket.destroy(); + }); + const port = await listen(server); + const client = secure + ? tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false }) + : net.connect(port, "127.0.0.1"); + try { + const body = readSlowly(client); + await once(client, secure ? "secureConnect" : "connect"); + setSocketOptions(client, 2, KERNEL_BUFFER); + client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); + expect({ body: await body, timeouts, pendingForTwoPeriods: pendingFor > 2 * TIMEOUT }).toEqual( + drainedWithoutTimeout, + ); + } finally { + client.destroy(); + server.closeAllConnections(); + server.close(); + } + }); + + test.concurrent("no 'timeout' while a slow reader drains one socket.write() on a CONNECT tunnel", async () => { + let timeouts = 0; + let pendingFor = 0; + const server = http.createServer(); + server.on("connect", (req, socket) => { + setSocketOptions(socket, 1, KERNEL_BUFFER); + // Node takes its 'timeout' listener off the socket of a tunnel, and the timer stays armed. + // Behind the last byte the connection is idle until the reader has caught up. + socket.on("timeout", () => pendingFor !== 0 || timeouts++); + socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + const wroteAt = performance.now(); + socket.write(BODY, () => { + pendingFor = performance.now() - wroteAt; + socket.end(); + }); + }); + server.timeout = TIMEOUT; + const port = await listen(server); + const client = net.connect(port, "127.0.0.1"); + try { + const body = readSlowly(client); + await once(client, "connect"); + setSocketOptions(client, 2, KERNEL_BUFFER); + client.write("CONNECT a:443 HTTP/1.1\r\nHost: a:443\r\n\r\n"); + expect({ body: await body, timeouts, pendingForTwoPeriods: pendingFor > 2 * TIMEOUT }).toEqual( + drainedWithoutTimeout, + ); + } finally { + client.destroy(); + server.closeAllConnections(); + server.close(); + } + }); +}); From 941a2fea91d464cecb4b4722f6af4b82aaf36890 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 8 Nov 2026 14:30:15 +0000 Subject: [PATCH 11/15] node:http: store what a frozen server socket prints now, fix a lint error in the second text - A server socket that a program has frozen throws in onResponseFinishHandleSocket, which writes kKeepAliveTimeoutSet before it arms the keep-alive timer. The stored output of test/aot/parallel/node-http/objects-closed-proxies-without-traps.ts has that in 17 rows. - _onTimeout() reads bufferedAmount with `?? 0` in both texts of the module, in place of a non-null assertion behind an optional chain. - The test of a peer that stops reading sets a 64 KB send buffer, and readSlowly() finds a blank line that lies across two chunks. --- src/js/ahead-of-time/node/_http_server.ts | 4 +-- src/js/node/_http_server.ts | 2 +- .../objects-closed-proxies-without-traps.ts | 34 +++++++++---------- .../http/node-http-server-timeouts.test.ts | 15 +++++--- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/js/ahead-of-time/node/_http_server.ts b/src/js/ahead-of-time/node/_http_server.ts index 3375d331ad9a..ce05c75c164f 100644 --- a/src/js/ahead-of-time/node/_http_server.ts +++ b/src/js/ahead-of-time/node/_http_server.ts @@ -1,5 +1,5 @@ // What the compiler reads in place of src/js/node/_http_server.ts, which `bun run` evaluates. The two do the same. -// Written against that file with the SHA-256 b193d24210dd78d099109bea822f2a2db1ea10994a6953ec80c11fbae3e365c8. +// Written against that file with the SHA-256 c8bac99def344958c19aa7f6e5441df451518f0275c5d1cd93f986c3cc652556. // When that file changes, bring the change here and write the new hash: test/internal/second-texts-of-modules.test.ts. // Hardcoded module "node:_http_server" @@ -2742,7 +2742,7 @@ function getNodeHTTPServerSocket(): NodeHTTPServerSocketConstructor { _onTimeout() { const handle = this[kHandle]; // A write in progress holds 'timeout' back once: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 - if ((this.writableLength > 0 || handle?.response?.bufferedAmount! > 0) && handle?.holdTimeoutBackOnce()) { + if ((this.writableLength > 0 || (handle?.response?.bufferedAmount ?? 0) > 0) && handle?.holdTimeoutBackOnce()) { this._unrefTimer(); return; } diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index f68d6342511d..680f91f38a7d 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1967,7 +1967,7 @@ function getNodeHTTPServerSocket() { _onTimeout() { const handle = this[kHandle]; // A write in progress holds 'timeout' back once: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 - if ((this.writableLength > 0 || handle?.response?.bufferedAmount > 0) && handle?.holdTimeoutBackOnce()) { + if ((this.writableLength > 0 || (handle?.response?.bufferedAmount ?? 0) > 0) && handle?.holdTimeoutBackOnce()) { this._unrefTimer(); return; } diff --git a/test/aot/parallel/node-http/objects-closed-proxies-without-traps.ts b/test/aot/parallel/node-http/objects-closed-proxies-without-traps.ts index fcee76a518c8..1c45219d248b 100644 --- a/test/aot/parallel/node-http/objects-closed-proxies-without-traps.ts +++ b/test/aot/parallel/node-http/objects-closed-proxies-without-traps.ts @@ -580,43 +580,43 @@ traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy, then used as that traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy of a Proxy, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy with every trap, then used itself traps while closing: 126 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy with every trap, then used as that traps while closing: 126 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy with every trap of a Proxy with every trap, then used itself traps while closing: 565 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a revoked Proxy, then used itself closing threw TypeError traps while closing: 0 of them for a key that is not listed: 0 @@ -636,61 +636,61 @@ traps while closing: 62 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps deleteProperty, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps get, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps getOwnPropertyDescriptor, then used itself traps while closing: 62 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps getPrototypeOf, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps has, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps isExtensible, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps ownKeys, then used itself traps while closing: 1 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps preventExtensions, then used itself traps while closing: 1 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: freeze a Proxy that traps set, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable as that closed sealed frozen 62 keys, 62 found, 0 writable, 0 configurable defined anew true added false removed true absent removed true absent found false --- nobody caught TypeError in | nobody caught TypeError in setTimeout | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim +-- nobody caught TypeError in | nobody caught TypeError in onResponseFinishHandleSocket | response close | request end | request close | answered 200 1 answer | socket error TypeError in _destroy | socket close | nobody caught TypeError in releaseServerParserShim == a server's socket, after the answer: seal itself, then used itself traps while closing: 0 of them for a key that is not listed: 0 itself closed sealed 62 keys, 62 found, 62 writable, 0 configurable diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index a2c7a406b595..7d1c7249b2dd 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -20,6 +20,9 @@ async function listen(server: http.Server) { return (server.address() as net.AddressInfo).port; } +// For setSocketOptions(). A small kernel buffer keeps most of a large write with the server, whatever the limits of the machine are. +const KERNEL_BUFFER = 64 * 1024; + describe("node:http server timeout enforcement", () => { test("headersTimeout closes a connection that never completes its request headers", async () => { const server = http.createServer({ connectionsCheckingInterval: 50 }, (req, res) => { @@ -939,7 +942,8 @@ describe("node:http server writes restart the socket inactivity timeout", () => let referenceFired = false; const { promise: timedOut, resolve: onTimedOut } = Promise.withResolvers(); const server = http.createServer((req, res) => { - // More than the kernel buffers take, so this write stays pending and every later one queues behind it. + // The kernel takes little of this write, so it stays pending and every later one queues behind it. + setSocketOptions(req.socket, 1, KERNEL_BUFFER); res.write(Buffer.alloc(16 * 1024 * 1024, "a")); pendingSince = performance.now(); const interval = setInterval(() => res.write("data: ping\n\n"), TIMEOUT / 8); @@ -976,8 +980,6 @@ describe("node:http server writes restart the socket inactivity timeout", () => // timer fires. setSocketOptions() does nothing on Windows, where the kernel can take a whole body at once. describe.skipIf(isWindows)("node:http: a write that drains slowly holds the socket inactivity timeout back", () => { const TIMEOUT = 400; - // Small kernel buffers at both ends keep most of the body with the server, whatever the limits of the machine are. - const KERNEL_BUFFER = 64 * 1024; const BODY = Buffer.alloc(2 * 1024 * 1024, "a"); // A server that holds 'timeout' back once, and does not see the bytes leave, fires at the second // expiry. For longer than that the reader takes 16 KB every 20 ms, less than half of the body in @@ -988,13 +990,16 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock let budget = 0; let received = 0; let head = -1; + let beforeBody = ""; let startedAt = 0; const { promise, resolve, reject } = Promise.withResolvers(); client.on("data", chunk => { startedAt ||= performance.now(); if (head === -1) { - const end = chunk.indexOf("\r\n\r\n"); - if (end !== -1) head = received + end + 4; + // The blank line that ends the head can lie across two chunks. + beforeBody += chunk.toString("latin1"); + const end = beforeBody.indexOf("\r\n\r\n"); + if (end !== -1) head = end + 4; } received += chunk.length; budget -= chunk.length; From b41aca61cbc83c511f272e84e965731275c2c08b Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 9 Nov 2026 04:30:49 +0000 Subject: [PATCH 12/15] node:http: look at the progress of a pending write when the socket inactivity timer fires A write() call that finds bytes pending sends them before its own (AsyncSocket::write). Nothing counted those bytes, so a handler that writes without waiting for 'drain' to a slow reader got 'timeout' at the second expiry while its bytes left. AsyncSocket now records in AsyncSocketData::backlogMoved that bytes of its buffer reached the kernel, in flush() and in write(). HttpResponse::spillBodyTail records the part of a zero-copy tail that it sends directly. socket._onTimeout() asks the native connection, which reads and clears the record: bytes that left since the last look hold 'timeout' back, and so does the first look at a pending write. For TLS the look also counts the ciphertext that the TLS layer still holds. _onTimeout() emits nothing for a connection that has closed natively and whose 'close' is not out yet, and it makes no native call under the keep-alive timeout. flush_headers() reads the state of the response only for a head that write_head() rendered while a timeout was armed. The lock of the pipelined responses moves into padding of JSNodeHTTPServerSocket, so the object is 208 bytes again, as on main. --- packages/bun-uws/src/AsyncSocket.h | 2 + packages/bun-uws/src/AsyncSocketData.h | 6 + packages/bun-uws/src/HttpResponse.h | 5 + src/js/ahead-of-time/node/_http_server.ts | 18 +- src/js/node/_http_server.ts | 13 +- src/js/private.d.ts | 4 +- .../bindings/node/JSNodeHTTPServerSocket.cpp | 41 +++ .../bindings/node/JSNodeHTTPServerSocket.h | 22 +- .../node/JSNodeHTTPServerSocketPrototype.cpp | 4 +- src/runtime/server/NodeHTTPResponse.rs | 14 +- .../http/node-http-server-timeouts.test.ts | 293 +++++++++++++++++- 11 files changed, 386 insertions(+), 36 deletions(-) diff --git a/packages/bun-uws/src/AsyncSocket.h b/packages/bun-uws/src/AsyncSocket.h index 8aede4bbd93d..0d05a96a91b2 100644 --- a/packages/bun-uws/src/AsyncSocket.h +++ b/packages/bun-uws/src/AsyncSocket.h @@ -274,6 +274,7 @@ struct AsyncSocket { /* Attempt to write data to the socket */ int written = us_socket_write((us_socket_t *) this, asyncSocketData->buffer.data(), max_flush_len); + asyncSocketData->backlogMoved |= written > 0; total_written += written; /* Check if we couldn't write the entire buffer */ @@ -354,6 +355,7 @@ struct AsyncSocket { /* Write off as much as we can */ int written = us_socket_write((us_socket_t *) this, asyncSocketData->buffer.data(), max_flush_len); + asyncSocketData->backlogMoved |= written > 0; /* On failure return, otherwise continue down the function */ if ((unsigned int) written < buffer_len) { /* Update buffering (todo: we can do better here if we keep track of what happens to this guy later on) */ diff --git a/packages/bun-uws/src/AsyncSocketData.h b/packages/bun-uws/src/AsyncSocketData.h index 9df9166ff664..e360196c307f 100644 --- a/packages/bun-uws/src/AsyncSocketData.h +++ b/packages/bun-uws/src/AsyncSocketData.h @@ -179,8 +179,14 @@ struct AsyncSocketData { /* Whether it has fired the filter with -3 (HttpResponse::setNodeHttpTunnelIdle). * +3 undoes it, and an idle socket fires -4 in place of -2. */ bool filteredIdleTunnel = false; + /* Bytes of a write that was pending have reached the kernel: the sends of `buffer` in + * AsyncSocket::flush() and write() set it. node:http reads and clears it when a socket + * inactivity timeout fires. */ + bool backlogMoved = false; }; +static_assert(sizeof(AsyncSocketData) == 48 && sizeof(AsyncSocketData) == 48, "the flags fit in the padding behind peerCertVerifyErrorCode"); + } #endif // UWS_ASYNCSOCKETDATA_H diff --git a/packages/bun-uws/src/HttpResponse.h b/packages/bun-uws/src/HttpResponse.h index 2364dc821b60..2fcf74598504 100644 --- a/packages/bun-uws/src/HttpResponse.h +++ b/packages/bun-uws/src/HttpResponse.h @@ -921,7 +921,12 @@ struct HttpResponse : public AsyncSocket { bool chunked = !(httpResponseData->state & (HttpResponseData::HTTP_WROTE_CONTENT_LENGTH_HEADER | HttpResponseData::HTTP_ANCIENT_REQUEST | HttpResponseData::HTTP_CLOSE_DELIMITED)); if (!data.empty()) { + /* With nothing buffered the tail is sent directly, which AsyncSocket::write() does not record. */ + size_t bufferedBefore = Super::getBufferedAmount(); Super::write(data.data(), data.length()); + if (Super::getBufferedAmount() < bufferedBefore + data.length()) { + httpResponseData->backlogMoved = true; + } } if (chunked) { Super::write("\r\n", 2); diff --git a/src/js/ahead-of-time/node/_http_server.ts b/src/js/ahead-of-time/node/_http_server.ts index ce05c75c164f..b3f0b075599d 100644 --- a/src/js/ahead-of-time/node/_http_server.ts +++ b/src/js/ahead-of-time/node/_http_server.ts @@ -1,5 +1,5 @@ // What the compiler reads in place of src/js/node/_http_server.ts, which `bun run` evaluates. The two do the same. -// Written against that file with the SHA-256 c8bac99def344958c19aa7f6e5441df451518f0275c5d1cd93f986c3cc652556. +// Written against that file with the SHA-256 154a159503904ab6d703e37d5bdaa37f01a37c82c3f28a9dc35b073ecf9701ab. // When that file changes, bring the change here and write the new hash: test/internal/second-texts-of-modules.test.ts. // Hardcoded module "node:_http_server" @@ -2741,10 +2741,15 @@ function getNodeHTTPServerSocket(): NodeHTTPServerSocketConstructor { _onTimeout() { const handle = this[kHandle]; - // A write in progress holds 'timeout' back once: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 - if ((this.writableLength > 0 || (handle?.response?.bufferedAmount ?? 0) > 0) && handle?.holdTimeoutBackOnce()) { - this._unrefTimer(); - return; + // Not under the keep-alive timeout: nothing writes there, and the native connection does not have that timer. + if (handle && !this[kKeepAliveTimeoutSet]) { + // The connection has closed, and 'close' comes next. + if (handle.closed) return; + // Node looks at the write queue here: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 + if (handle.holdTimeoutBackOnce(this.writableLength > 0 || (handle.response?.bufferedAmount ?? 0) > 0)) { + this._unrefTimer(); + return; + } } this.emit("timeout"); } @@ -4977,7 +4982,8 @@ const finishExchange = function emitResponseFinished(exchange: Exchange): void { return finishExchangeInSteps(exchange, flags); } const idleTimeout = keepAliveTimeout + keepAliveTimeoutBuffer; - if (socket.$armedTimeout !== idleTimeout && socket.setTimeout !== serverSocketSetTimeout) { + // finishConnection() calls setTimeout() in these two cases. + if ((socket.$armedTimeout !== idleTimeout || socket.timeout !== 0) && socket.setTimeout !== serverSocketSetTimeout) { ("unlikely"); return finishExchangeInSteps(exchange, flags); } diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index 680f91f38a7d..9bb44d9d9073 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1966,10 +1966,15 @@ function getNodeHTTPServerSocket() { _onTimeout() { const handle = this[kHandle]; - // A write in progress holds 'timeout' back once: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 - if ((this.writableLength > 0 || (handle?.response?.bufferedAmount ?? 0) > 0) && handle?.holdTimeoutBackOnce()) { - this._unrefTimer(); - return; + // Not under the keep-alive timeout: nothing writes there, and the native connection does not have that timer. + if (handle && !this[kKeepAliveTimeoutSet]) { + // The connection has closed, and 'close' comes next. + if (handle.closed) return; + // Node looks at the write queue here: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 + if (handle.holdTimeoutBackOnce(this.writableLength > 0 || (handle.response?.bufferedAmount ?? 0) > 0)) { + this._unrefTimer(); + return; + } } this.emit("timeout"); } diff --git a/src/js/private.d.ts b/src/js/private.d.ts index a093accc89ab..3c40ad93c67a 100644 --- a/src/js/private.d.ts +++ b/src/js/private.d.ts @@ -2481,8 +2481,8 @@ declare namespace $Node { closeIfIdle(): boolean; /** Hands over the `Timeout` of an armed socket inactivity timeout, or takes it back. While the connection holds it, the writes of the server and their progress refresh it. */ setInactivityTimer(timer: Timeout | undefined): void; - /** For a timer that fired while a write is pending. True once, then false until a write or its progress refreshes the timer. Always false while the connection holds no timer, as under the keep-alive timeout. */ - holdTimeoutBackOnce(): boolean; + /** For a timer that fired. True when bytes of a pending write left since the last call or refresh, and for the first look at a write that is `pending`. Always false while the connection holds no timer, as under the keep-alive timeout. */ + holdTimeoutBackOnce(pending: boolean): boolean; } interface Timeout { diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp b/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp index 4f84bab548ee..6cd5c88b0729 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp @@ -478,6 +478,21 @@ static void setTracksActivity(us_socket_t* socket, bool tracks) { auto* httpResponseData = reinterpret_cast*>(us_socket_ext(socket)); httpResponseData->setFlag(uWS::HttpResponseData::HTTP_NODE_TRACKS_ACTIVITY, tracks); + // What left before the timer was handed over is no progress under it. + httpResponseData->backlogMoved = false; +} + +/* Whether bytes of a pending write left since the last call (AsyncSocketData::backlogMoved). */ +template +static bool takeBacklogMoved(us_socket_t* socket) +{ + auto* asyncSocketData = reinterpret_cast*>(us_socket_ext(socket)); + return std::exchange(asyncSocketData->backlogMoved, false); +} + +static bool takeBacklogMoved(us_socket_t* socket, bool ssl) +{ + return ssl ? takeBacklogMoved(socket) : takeBacklogMoved(socket); } void JSNodeHTTPServerSocket::setInactivityTimer(JSC::VM& vm, JSC::JSObject* timer) @@ -501,7 +516,33 @@ void JSNodeHTTPServerSocket::touchInactivityTimer() if (auto* timer = m_inactivityTimer.get()) { Bun__Timer__refreshTimeout(globalObject(), JSValue::encode(timer)); timeoutHeldBack = false; + // onClose() clears `socket` before the task that runs detach(). + if (socket && !us_socket_is_closed(socket)) { + takeBacklogMoved(socket, is_ssl); + } + } +} + +bool JSNodeHTTPServerSocket::holdTimeoutBackOnce(bool pending) +{ + if (!m_inactivityTimer || !socket || upgraded || us_socket_is_closed(socket)) { + return false; + } + bool moved = takeBacklogMoved(socket, is_ssl); + if (is_ssl) { + // Ciphertext that the TLS layer still holds is a pending write too. us_socket_write() reports none of its sends. + const unsigned spill = us_socket_ssl_spill_pending(socket); + pending |= spill > 0; + moved |= spill != lastTLSSpill; + lastTLSSpill = spill; + } + if (!pending && !moved) { + return false; } + // One look uses up both: a period without progress ends in 'timeout' at the second expiry. + const bool held = moved || !timeoutHeldBack; + timeoutHeldBack = true; + return held; } bool JSNodeHTTPServerSocket::isAuthorized() const diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocket.h b/src/jsc/bindings/node/JSNodeHTTPServerSocket.h index f10ee5467ce3..c2ab4236104c 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocket.h +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocket.h @@ -65,11 +65,15 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { unsigned heldWriteAwaitsDrain : 1 = 0; /* Set by onClose() for the peerEnded / closeError getters: the peer's FIN, the error of a failed read. */ unsigned peer_ended : 1 = 0; - /* holdTimeoutBackOnce() said yes, and nothing has refreshed m_inactivityTimer since. */ + /* holdTimeoutBackOnce() has looked at the write that is pending, and nothing has refreshed m_inactivityTimer since. */ unsigned timeoutHeldBack : 1 = 0; int closeReadError = 0; /* Whether the connection closed before the kernel had taken everything, also after an upgrade to a WebSocket. */ std::optional closedWithUnsentData; + /* For m_pipelinedResponses. Here it takes padding, and the object is as large as without m_inactivityTimer. */ + mutable WTF::Lock m_pipelinedResponsesLock; + /* us_socket_ssl_spill_pending() at the last holdTimeoutBackOnce(). */ + unsigned lastTLSSpill = 0; /* Tunnel bytes that onData() queued for JS in tasks that have not run yet. */ size_t queuedTunnelBytes = 0; const char* peerCertVerifyErrorCode = nullptr; @@ -109,15 +113,8 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { /* node:http: takes the Timeout of an armed socket inactivity timeout (never the keep-alive one), or null. Server writes and the progress of a pending write refresh it, like net.Socket in Node. */ void setInactivityTimer(JSC::VM& vm, JSC::JSObject* timer); void touchInactivityTimer(); - /* For a timer that fired with a write still pending, where Node looks at writeQueueSize. True once, then false until something refreshes the timer. */ - bool holdTimeoutBackOnce() - { - if (!m_inactivityTimer || timeoutHeldBack) { - return false; - } - timeoutHeldBack = true; - return true; - } + /* For a timer that fired, where Node looks at writeQueueSize. True when bytes of a pending write left since the last call or refresh, and for the first look at a write that is `pending`. */ + bool holdTimeoutBackOnce(bool pending); /* node:http server compat - HTTP/1.1 pipelining. Responses for requests * that were parsed while an earlier response on this connection was still @@ -197,10 +194,9 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { mutable JSC::WriteBarrier m_duplex; mutable JSC::WriteBarrier m_inactivityTimer; - /* Queued pipelined responses (see appendPipelinedResponse). The lock keeps - * mutation on the JS thread coherent with the concurrent GC marker walking + /* Queued pipelined responses (see appendPipelinedResponse). m_pipelinedResponsesLock + * keeps mutation on the JS thread coherent with the concurrent GC marker walking * the vector in visitChildren. */ - mutable WTF::Lock m_pipelinedResponsesLock; mutable WTF::Vector, 2> m_pipelinedResponses; DECLARE_INFO; diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocketPrototype.cpp b/src/jsc/bindings/node/JSNodeHTTPServerSocketPrototype.cpp index d0e052ef298d..0fb99e0fdc93 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocketPrototype.cpp +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocketPrototype.cpp @@ -96,7 +96,7 @@ static const JSC::HashTableValue JSNodeHTTPServerSocketPrototypeTableValues[] = { "setResponseTrailers"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketSetResponseTrailers, 1 } }, { "isRequestTimedOut"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketIsRequestTimedOut, 2 } }, { "setInactivityTimer"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketSetInactivityTimer, 1 } }, - { "holdTimeoutBackOnce"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketHoldTimeoutBackOnce, 0 } }, + { "holdTimeoutBackOnce"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketHoldTimeoutBackOnce, 1 } }, { "startPipelinedResponse"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketStartPipelinedResponse, 3 } }, { "stopParsing"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketStopParsing, 0 } }, { "closeWhenDrained"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketCloseWhenDrained, 0 } }, @@ -236,7 +236,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketSetInactivityTimer, (JSC: JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketHoldTimeoutBackOnce, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = dynamicDowncast(callFrame->thisValue()); - return JSValue::encode(JSC::jsBoolean(thisObject && thisObject->holdTimeoutBackOnce())); + return JSValue::encode(JSC::jsBoolean(thisObject && thisObject->holdTimeoutBackOnce(callFrame->argument(0).toBoolean(globalObject)))); } // node:http HTTP/1.1 pipelining: make a queued pipelined response the diff --git a/src/runtime/server/NodeHTTPResponse.rs b/src/runtime/server/NodeHTTPResponse.rs index b14f4abf820f..2a181af26d6d 100644 --- a/src/runtime/server/NodeHTTPResponse.rs +++ b/src/runtime/server/NodeHTTPResponse.rs @@ -132,6 +132,8 @@ bitflags! { const TUNNELED = 1 << 8; /// Its dispatch threw while it was queued (pipelining): advanceResponsePipeline decides at its turn. const DISPATCH_THREW_WHILE_QUEUED = 1 << 9; + /// write_head() ran while the connection tracked activity (`State::tracks_node_activity`): the flush_headers() that sends that head is a write. + const HEAD_UNDER_INACTIVITY_TIMER = 1 << 10; } } @@ -1032,6 +1034,9 @@ impl NodeHTTPResponse { } } + if state.tracks_node_activity() { + self.update_flags(|f| f.insert(Flags::HEAD_UNDER_INACTIVITY_TIMER)); + } Ok(()) } @@ -2561,9 +2566,12 @@ impl NodeHTTPResponse { if !flags.contains(Flags::UPGRADED) && !self.is_socket_closed_or_closing() { if let Some(raw_response) = self.writer() { // Only the call that sends the head: write() with a callback calls this behind every chunk. - let state = raw_response.state(); - if !state.is_http_write_called() { - self.note_write_dispatched(raw_response, state); + if flags.contains(Flags::HEAD_UNDER_INACTIVITY_TIMER) { + self.update_flags(|f| f.remove(Flags::HEAD_UNDER_INACTIVITY_TIMER)); + let state = raw_response.state(); + if !state.is_http_write_called() { + self.note_write_dispatched(raw_response, state); + } } // Don't flush immediately; queue a microtask to uncork the socket. raw_response.flush_headers(false); diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index 7d1c7249b2dd..4c8337d21e90 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -2,6 +2,7 @@ import { setSocketOptions } from "bun:internal-for-testing"; import { describe, expect, test } from "bun:test"; import { isWindows, tls as tlsOptions } from "harness"; import { once } from "node:events"; +import { readSync } from "node:fs"; import http from "node:http"; import https from "node:https"; import net from "node:net"; @@ -465,6 +466,60 @@ describe("node:http server timeout enforcement", () => { server.close(); } }); + + // The reset of the peer and the request of another connection reach the server together. The handler of + // that request keeps the event loop past the deadline of the timer, so the timer is due when the connection + // has closed and its 'close' is not out yet. Node emits no 'timeout' there. + test("no 'timeout' on a connection that its peer has reset", async () => { + const TIMEOUT = 400; + let deadline = 0; + let timeoutsBehindTheClose = 0; + let reset: net.Socket | undefined; + let other: net.Socket | undefined; + const { promise: closed, resolve: onClosed } = Promise.withResolvers(); + const server = http.createServer((req, res) => { + if (req.url === "/busy") { + while (performance.now() < deadline + 50) {} + res.end(); + return; + } + const socket = req.socket; + // The native connection says when it has closed. + const handle = (socket as any)[ + Object.getOwnPropertySymbols(socket).find(symbol => symbol.description === "handle")! + ]; + socket.setTimeout(TIMEOUT); + deadline = performance.now() + TIMEOUT; + socket.on("timeout", () => handle.closed && timeoutsBehindTheClose++); + socket.on("close", onClosed); + for (const stream of [socket, req, res]) stream.on("error", () => {}); + setTimeout(() => { + // Both are with the kernel before the event loop asks it again. + reset!.resetAndDestroy(); + other!.write("GET /busy HTTP/1.1\r\nHost: a\r\n\r\n"); + }, TIMEOUT - 150); + }); + // Keeps a socket that times out, so its 'close' comes from the reset alone. + server.on("timeout", () => {}); + const port = await listen(server); + try { + other = net.connect(port, "127.0.0.1"); + other.on("error", () => {}); + other.resume(); + await once(other, "connect"); + reset = net.connect(port, "127.0.0.1"); + reset.on("error", () => {}); + await once(reset, "connect"); + reset.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); + await closed; + expect(timeoutsBehindTheClose).toBe(0); + } finally { + reset?.destroy(); + other?.destroy(); + server.closeAllConnections(); + server.close(); + } + }); }); // server.setTimeout (applied per connection), req.setTimeout, res.setTimeout @@ -977,7 +1032,7 @@ describe("node:http server writes restart the socket inactivity timeout", () => }); // The progress of a write that is pending is activity too: Node looks at the write queue when the -// timer fires. setSocketOptions() does nothing on Windows, where the kernel can take a whole body at once. +// timer fires. Not on Windows, where the kernel takes a whole body at once and no write stays pending. describe.skipIf(isWindows)("node:http: a write that drains slowly holds the socket inactivity timeout back", () => { const TIMEOUT = 400; const BODY = Buffer.alloc(2 * 1024 * 1024, "a"); @@ -986,7 +1041,7 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock // all. Then it takes the rest. const SLOW_FOR = TIMEOUT * 2.75; const STEP = 16 * 1024; - function readSlowly(client: net.Socket) { + function readSlowly(client: net.Socket, step = STEP, slowFor = SLOW_FOR) { let budget = 0; let received = 0; let head = -1; @@ -1007,7 +1062,7 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock }); const pump = setInterval(() => { // A chunk that was larger than the step is paid off before the next one. - if (startedAt === 0 || performance.now() - startedAt < SLOW_FOR) budget = Math.min(budget + STEP, STEP); + if (startedAt === 0 || performance.now() - startedAt < slowFor) budget = Math.min(budget + step, step); else budget = Infinity; if (budget > 0) client.resume(); }, 20); @@ -1024,7 +1079,24 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock // `done` runs when the server has no byte of the body left. type Respond = (res: http.ServerResponse, done: () => void) => void; - const responses: Record = { + // Most of the body in one write that stays pending, then 64 more writes, one per tick, that do not + // wait for 'drain': each call sends bytes of the writes before it. The send buffer is four times the + // receive buffer of the reader, so what the reader takes between two writes never frees the third + // at which the kernel reports a writable socket. No bytes leave the server outside a write() call. + const keepWriting = + (size: number): Respond => + (res, done) => { + let offset = BODY.length - 64 * size; + res.write(BODY.subarray(0, offset)); + const interval = setInterval(() => { + res.write(BODY.subarray(offset, (offset += size))); + if (offset < BODY.length) return; + clearInterval(interval); + res.end(done); + }, 20); + res.on("close", () => clearInterval(interval)); + }; + const responses: Record = { "res.end()": { respond: (res, done) => res.end(BODY, done) }, "res.end() over https": { secure: true, respond: (res, done) => res.end(BODY, done) }, "res.write()": { @@ -1037,14 +1109,28 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock else res.once("drain", drained); }, }, + "res.write() and more res.write() calls that do not wait for 'drain'": { + sendBuffer: 4 * KERNEL_BUFFER, + respond: keepWriting(4 * 1024), + }, + // Over 16 KB the server keeps the rest of a write by reference, not in the buffer of the connection. + "res.write() and more res.write() calls of over 16 KB that do not wait for 'drain'": { + sendBuffer: 4 * KERNEL_BUFFER, + respond: keepWriting(20 * 1024), + }, + "res.write() and more res.write() calls over https that do not wait for 'drain'": { + secure: true, + sendBuffer: 4 * KERNEL_BUFFER, + respond: keepWriting(4 * 1024), + }, }; test.concurrent.each(Object.keys(responses))("no 'timeout' while a slow reader drains one %s", async name => { - const { secure, respond } = responses[name]; + const { secure, sendBuffer = KERNEL_BUFFER, respond } = responses[name]; let timeouts = 0; let pendingFor = 0; const handler = (req: http.IncomingMessage, res: http.ServerResponse) => { - setSocketOptions(req.socket, 1, KERNEL_BUFFER); + setSocketOptions(req.socket, 1, sendBuffer); res.writeHead(200, { "Content-Length": BODY.length, "Connection": "close" }); const wroteAt = performance.now(); respond(res, () => (pendingFor = performance.now() - wroteAt)); @@ -1107,4 +1193,199 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock server.close(); } }); + + // The TLS layer takes a batch of records from the server and holds the ciphertext that the kernel does not take + // yet. The reader takes 2 KB every 20 ms to the end, and the buffers are small, so a batch leaves for longer + // than two periods. In that time no plaintext leaves the server, and behind the last batch none is left. + // Not concurrent: while another connection of the process holds a batch, the TLS layer sends record by record. + test("no 'timeout' while a slow reader drains what the TLS layer holds of one res.end()", async () => { + const body = BODY.subarray(0, 224 * 1024); + let timeouts = 0; + let pendingFor = 0; + const server = https.createServer(tlsOptions, (req, res) => { + setSocketOptions(req.socket, 1, KERNEL_BUFFER / 4); + res.writeHead(200, { "Content-Length": body.length, "Connection": "close" }); + const wroteAt = performance.now(); + res.end(body, () => (pendingFor = performance.now() - wroteAt)); + }); + server.timeout = TIMEOUT; + server.on("timeout", socket => { + timeouts++; + socket.destroy(); + }); + const port = await listen(server); + const client = tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false }); + try { + const received = readSlowly(client, 2 * 1024, Infinity); + await once(client, "secureConnect"); + setSocketOptions(client, 2, KERNEL_BUFFER / 4); + client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); + expect({ body: await received, timeouts, pendingForTwoPeriods: pendingFor > 2 * TIMEOUT }).toEqual({ + body: body.length, + timeouts: 0, + pendingForTwoPeriods: true, + }); + } finally { + client.destroy(); + server.closeAllConnections(); + server.close(); + } + }); + + // The timer clock counts whole milliseconds. A server that does not count the bytes fires TIMEOUT / 2 early or more. + const SLACK = 50; + + // What the kernel holds for a paused client, taken from its descriptor at once. Behind it the send buffer of the server has room again. + function takeFromKernel(client: net.Socket, into: Buffer) { + const fd: number = (client as any)._handle.fd; + let taken = 0; + for (;;) { + try { + const length = readSync(fd, into); + if (length === 0) return taken; + taken += length; + } catch (error: any) { + if (error.code !== "EAGAIN") throw error; + return taken; + } + } + } + + // A write() call sends the bytes that are pending before its own. The call that sends the last of them is + // activity, although nothing is pending when the timer fires next. The client is paused and the handler takes + // its bytes from the kernel itself, so no byte leaves the server outside a write() call. + test.concurrent.each([ + ["many chunks of 8 KB", 8 * 1024], + // Over 16 KB the server keeps the rest of a write by reference, not in the buffer of the connection. + ["one chunk of 1 MB", 1024 * 1024], + ])("a write() that sends the last pending bytes of %s restarts the period", async (_, size) => { + let client: net.Socket | undefined; + let emptying: ReturnType | undefined; + let emptiedAt = 0; + const { promise: timedOut, resolve: onTimedOut, reject: onFailed } = Promise.withResolvers(); + const server = http.createServer((req, res) => { + const socket = req.socket as net.Socket & { _onTimeout(): void }; + setSocketOptions(socket, 1, KERNEL_BUFFER); + res.writeHead(200); + for (let written = 0; written < 1024 * 1024; written += size) res.write(Buffer.alloc(size, "a")); + // The native response says what it still holds of the writes. + const handle = (res as any)[Object.getOwnPropertySymbols(res).find(symbol => symbol.description === "handle")!]; + const empty = () => { + try { + if (handle.bufferedAmount === 0) throw new Error("the kernel took the whole megabyte"); + const into = Buffer.alloc(64 * 1024); + const giveUpAt = performance.now() + 3000; + while (handle.bufferedAmount > 0) { + if (takeFromKernel(client!, into) > 0) res.write("ping"); + else if (performance.now() > giveUpAt) throw new Error("the pending bytes do not leave"); + } + // Room for the last ping, which leaves when this callback returns. + takeFromKernel(client!, into); + emptiedAt = performance.now(); + } catch (error) { + onFailed(error); + } + }; + // The first expiry finds the writes pending. Three quarters of a period behind it, when the kernel takes no more by itself, the handler sends the rest. + const onTimeout = socket._onTimeout; + let expiries = 0; + socket._onTimeout = function () { + if (++expiries === 1) emptying = setTimeout(empty, TIMEOUT * 0.75); + return onTimeout.call(this); + }; + }); + server.timeout = TIMEOUT; + server.on("timeout", socket => { + onTimedOut(performance.now()); + socket.destroy(); + }); + const port = await listen(server); + try { + client = net.connect(port, "127.0.0.1"); + client.pause(); + client.on("error", onFailed); + await once(client, "connect"); + setSocketOptions(client, 2, KERNEL_BUFFER); + client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); + const timedOutAt = await timedOut; + // A server that does not count those bytes fires at the first expiry, or a quarter of a period behind them. + expect(emptiedAt).not.toBe(0); + expect(timedOutAt - emptiedAt).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + } finally { + clearTimeout(emptying); + client?.destroy(); + server.closeAllConnections(); + server.close(); + } + }); + + // Node records the size of the write queue at every expiry, so an expiry that finds progress also uses up the + // one that a pending write gets without progress. Here bytes leave until half a period behind the first + // expiry. Then the reader stops and the handler stops. The second expiry behind that is 'timeout', not the third. + test.concurrent("a peer that stops reading is timed out at the second expiry behind its last byte", async () => { + let stop = () => {}; + let stoppedAt = 0; + let expiriesBehindStop = 0; + const { promise: timedOut, resolve: onTimedOut, reject: onFailed } = Promise.withResolvers(); + const server = http.createServer((req, res) => { + const socket = req.socket as net.Socket & { _onTimeout(): void }; + setSocketOptions(socket, 1, 4 * KERNEL_BUFFER); + res.writeHead(200); + res.write(BODY); + // Each call sends bytes of the body, which is pending. + const interval = setInterval(() => res.write("data: ping\n\n"), 20); + res.on("close", () => clearInterval(interval)); + // Every expiry of the timer comes through here. + const onTimeout = socket._onTimeout; + let expiries = 0; + socket._onTimeout = function () { + if (stoppedAt !== 0) expiriesBehindStop++; + else if (++expiries === 1) { + setTimeout(() => { + stop(); + clearInterval(interval); + stoppedAt = performance.now(); + }, TIMEOUT / 2); + } + return onTimeout.call(this); + }; + }); + server.timeout = TIMEOUT; + server.on("timeout", socket => { + onTimedOut(performance.now()); + socket.destroy(); + }); + const port = await listen(server); + const client = net.connect(port, "127.0.0.1"); + let pump: ReturnType | undefined; + try { + let budget = 0; + client.on("data", chunk => { + budget -= chunk.length; + if (budget <= 0) client.pause(); + }); + pump = setInterval(() => { + budget = Math.min(budget + STEP, STEP); + if (budget > 0) client.resume(); + }, 20); + stop = () => { + clearInterval(pump); + client.pause(); + }; + client.on("error", onFailed); + await once(client, "connect"); + setSocketOptions(client, 2, KERNEL_BUFFER); + client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); + const timedOutAt = await timedOut; + // A server that does not see the bytes leave fires before the reader stops, or half a period behind that. + expect(stoppedAt).not.toBe(0); + expect(timedOutAt - stoppedAt).toBeGreaterThanOrEqual(TIMEOUT - SLACK); + expect(expiriesBehindStop).toBeLessThanOrEqual(2); + } finally { + clearInterval(pump); + client.destroy(); + server.closeAllConnections(); + server.close(); + } + }); }); From f31f4ac121cf4b3837d84678126da57fedf55806 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 9 Nov 2026 13:39:41 +0000 Subject: [PATCH 13/15] node:http: say in one line what the lock of the pipelined responses is for --- src/jsc/bindings/node/JSNodeHTTPServerSocket.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocket.h b/src/jsc/bindings/node/JSNodeHTTPServerSocket.h index c2ab4236104c..a74301c2f4f4 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocket.h +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocket.h @@ -194,9 +194,7 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { mutable JSC::WriteBarrier m_duplex; mutable JSC::WriteBarrier m_inactivityTimer; - /* Queued pipelined responses (see appendPipelinedResponse). m_pipelinedResponsesLock - * keeps mutation on the JS thread coherent with the concurrent GC marker walking - * the vector in visitChildren. */ + /* Queued pipelined responses (see appendPipelinedResponse). The concurrent GC marker walks them in visitChildren, so every change holds m_pipelinedResponsesLock. */ mutable WTF::Vector, 2> m_pipelinedResponses; DECLARE_INFO; From ed93ca2ef39a75b08149f64c9e39657305e39cf8 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 9 Nov 2026 14:57:45 +0000 Subject: [PATCH 14/15] node:http: count what a write() sent of a pending write when the socket is writable again - HttpContext::onWritable refreshes the socket inactivity timer when bytes of a pending write left since the last refresh, in its own flush or in a write() before it. An idle connection then gets 'timeout' one period behind those bytes, and no longer at the second expiry behind the first write. - A refresh of the timer and a hand-over take the size of the TLS spill again, so ciphertext that left before them is no progress at the next expiry. - _onTimeout() returns for a connection that has closed under the keep-alive timeout too. - Tests: the test of the TLS spill runs over a Unix socket behind a relay that takes the ciphertext at an exact pace. The tests of a reset prove that the timer came due behind the close. Each new test waits for its server to close. --- packages/bun-uws/src/AsyncSocketData.h | 4 +- packages/bun-uws/src/HttpContext.h | 16 +- src/js/ahead-of-time/node/_http_server.ts | 11 +- src/js/node/_http_server.ts | 9 +- .../bindings/node/JSNodeHTTPServerSocket.cpp | 14 +- .../bindings/node/JSNodeHTTPServerSocket.h | 3 +- .../http/node-http-server-timeouts.test.ts | 376 ++++++++++++------ 7 files changed, 292 insertions(+), 141 deletions(-) diff --git a/packages/bun-uws/src/AsyncSocketData.h b/packages/bun-uws/src/AsyncSocketData.h index e360196c307f..26bb073c7860 100644 --- a/packages/bun-uws/src/AsyncSocketData.h +++ b/packages/bun-uws/src/AsyncSocketData.h @@ -180,8 +180,8 @@ struct AsyncSocketData { * +3 undoes it, and an idle socket fires -4 in place of -2. */ bool filteredIdleTunnel = false; /* Bytes of a write that was pending have reached the kernel: the sends of `buffer` in - * AsyncSocket::flush() and write() set it. node:http reads and clears it when a socket - * inactivity timeout fires. */ + * AsyncSocket::flush() and write() set it. node:http reads and clears it at a writable + * event and when a socket inactivity timeout fires. */ bool backlogMoved = false; }; diff --git a/packages/bun-uws/src/HttpContext.h b/packages/bun-uws/src/HttpContext.h index 488469a58000..9081f24ee590 100644 --- a/packages/bun-uws/src/HttpContext.h +++ b/packages/bun-uws/src/HttpContext.h @@ -897,15 +897,15 @@ struct HttpContext { /* Attempt to drain the socket buffer before triggering onWritable callback */ size_t bufferedAmount = asyncSocket->getBufferedAmount(); - if (bufferedAmount > 0) { - /* Try to flush pending data from the socket's buffer to the network */ - size_t flushed = asyncSocket->flush(); - if constexpr (IsNodeHttp) { - /* node:http compat: bytes of a pending write left, which is activity for an armed socket inactivity timeout. */ - if (flushed > 0 && (httpResponseData->state & HttpResponseData::HTTP_NODE_TRACKS_ACTIVITY)) [[unlikely]] { - Bun__NodeHTTP__touchInactivityTimer(SSL, s); - } + /* Try to flush pending data from the socket's buffer to the network */ + size_t flushed = bufferedAmount > 0 ? asyncSocket->flush() : 0; + if constexpr (IsNodeHttp) { + /* node:http compat: bytes of a pending write left, in this flush or in a write() before it. That is activity for an armed socket inactivity timeout. */ + if ((httpResponseData->state & HttpResponseData::HTTP_NODE_TRACKS_ACTIVITY) && httpResponseData->backlogMoved) [[unlikely]] { + Bun__NodeHTTP__touchInactivityTimer(SSL, s); } + } + if (bufferedAmount > 0) { /* Check if there's still data waiting to be sent after flush attempt */ if (asyncSocket->getBufferedAmount() > 0) { /* onEnd deferred close for these bytes; a writable event that diff --git a/src/js/ahead-of-time/node/_http_server.ts b/src/js/ahead-of-time/node/_http_server.ts index b3f0b075599d..5b7632391814 100644 --- a/src/js/ahead-of-time/node/_http_server.ts +++ b/src/js/ahead-of-time/node/_http_server.ts @@ -1,5 +1,5 @@ // What the compiler reads in place of src/js/node/_http_server.ts, which `bun run` evaluates. The two do the same. -// Written against that file with the SHA-256 154a159503904ab6d703e37d5bdaa37f01a37c82c3f28a9dc35b073ecf9701ab. +// Written against that file with the SHA-256 afe1fa727e188edb03f07d531760d32176b6cf4ddc3b2febb8751ce16f71a6d5. // When that file changes, bring the change here and write the new hash: test/internal/second-texts-of-modules.test.ts. // Hardcoded module "node:_http_server" @@ -2741,12 +2741,15 @@ function getNodeHTTPServerSocket(): NodeHTTPServerSocketConstructor { _onTimeout() { const handle = this[kHandle]; - // Not under the keep-alive timeout: nothing writes there, and the native connection does not have that timer. - if (handle && !this[kKeepAliveTimeoutSet]) { + if (handle) { // The connection has closed, and 'close' comes next. if (handle.closed) return; // Node looks at the write queue here: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 - if (handle.holdTimeoutBackOnce(this.writableLength > 0 || (handle.response?.bufferedAmount ?? 0) > 0)) { + // Not under the keep-alive timeout: nothing writes there, and the native connection does not have that timer. + if ( + !this[kKeepAliveTimeoutSet] && + handle.holdTimeoutBackOnce(this.writableLength > 0 || (handle.response?.bufferedAmount ?? 0) > 0) + ) { this._unrefTimer(); return; } diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index 9bb44d9d9073..4936e2541942 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1966,12 +1966,15 @@ function getNodeHTTPServerSocket() { _onTimeout() { const handle = this[kHandle]; - // Not under the keep-alive timeout: nothing writes there, and the native connection does not have that timer. - if (handle && !this[kKeepAliveTimeoutSet]) { + if (handle) { // The connection has closed, and 'close' comes next. if (handle.closed) return; // Node looks at the write queue here: https://github.com/nodejs/node/blob/v26.3.0/lib/net.js#L596-L611 - if (handle.holdTimeoutBackOnce(this.writableLength > 0 || (handle.response?.bufferedAmount ?? 0) > 0)) { + // Not under the keep-alive timeout: nothing writes there, and the native connection does not have that timer. + if ( + !this[kKeepAliveTimeoutSet] && + handle.holdTimeoutBackOnce(this.writableLength > 0 || (handle.response?.bufferedAmount ?? 0) > 0) + ) { this._unrefTimer(); return; } diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp b/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp index 6cd5c88b0729..a55e3770263d 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp @@ -478,8 +478,6 @@ static void setTracksActivity(us_socket_t* socket, bool tracks) { auto* httpResponseData = reinterpret_cast*>(us_socket_ext(socket)); httpResponseData->setFlag(uWS::HttpResponseData::HTTP_NODE_TRACKS_ACTIVITY, tracks); - // What left before the timer was handed over is no progress under it. - httpResponseData->backlogMoved = false; } /* Whether bytes of a pending write left since the last call (AsyncSocketData::backlogMoved). */ @@ -495,6 +493,15 @@ static bool takeBacklogMoved(us_socket_t* socket, bool ssl) return ssl ? takeBacklogMoved(socket) : takeBacklogMoved(socket); } +/* What has left of a pending write until now is no progress for the next holdTimeoutBackOnce(). Only for an open socket. */ +void JSNodeHTTPServerSocket::forgetWriteProgress() +{ + takeBacklogMoved(socket, is_ssl); + if (is_ssl) { + lastTLSSpill = us_socket_ssl_spill_pending(socket); + } +} + void JSNodeHTTPServerSocket::setInactivityTimer(JSC::VM& vm, JSC::JSObject* timer) { timeoutHeldBack = false; @@ -509,6 +516,7 @@ void JSNodeHTTPServerSocket::setInactivityTimer(JSC::VM& vm, JSC::JSObject* time } else { setTracksActivity(socket, !!timer); } + forgetWriteProgress(); } void JSNodeHTTPServerSocket::touchInactivityTimer() @@ -518,7 +526,7 @@ void JSNodeHTTPServerSocket::touchInactivityTimer() timeoutHeldBack = false; // onClose() clears `socket` before the task that runs detach(). if (socket && !us_socket_is_closed(socket)) { - takeBacklogMoved(socket, is_ssl); + forgetWriteProgress(); } } } diff --git a/src/jsc/bindings/node/JSNodeHTTPServerSocket.h b/src/jsc/bindings/node/JSNodeHTTPServerSocket.h index a74301c2f4f4..4da899742227 100644 --- a/src/jsc/bindings/node/JSNodeHTTPServerSocket.h +++ b/src/jsc/bindings/node/JSNodeHTTPServerSocket.h @@ -72,7 +72,7 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { std::optional closedWithUnsentData; /* For m_pipelinedResponses. Here it takes padding, and the object is as large as without m_inactivityTimer. */ mutable WTF::Lock m_pipelinedResponsesLock; - /* us_socket_ssl_spill_pending() at the last holdTimeoutBackOnce(). */ + /* us_socket_ssl_spill_pending() at the last refresh of m_inactivityTimer or holdTimeoutBackOnce(), whichever came last. */ unsigned lastTLSSpill = 0; /* Tunnel bytes that onData() queued for JS in tasks that have not run yet. */ size_t queuedTunnelBytes = 0; @@ -113,6 +113,7 @@ class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { /* node:http: takes the Timeout of an armed socket inactivity timeout (never the keep-alive one), or null. Server writes and the progress of a pending write refresh it, like net.Socket in Node. */ void setInactivityTimer(JSC::VM& vm, JSC::JSObject* timer); void touchInactivityTimer(); + void forgetWriteProgress(); /* For a timer that fired, where Node looks at writeQueueSize. True when bytes of a pending write left since the last call or refresh, and for the first look at a write that is `pending`. */ bool holdTimeoutBackOnce(bool pending); diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index 4c8337d21e90..6b2f02c9d252 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -1,11 +1,12 @@ import { setSocketOptions } from "bun:internal-for-testing"; import { describe, expect, test } from "bun:test"; -import { isWindows, tls as tlsOptions } from "harness"; +import { isWindows, tempDir, tls as tlsOptions } from "harness"; import { once } from "node:events"; import { readSync } from "node:fs"; import http from "node:http"; import https from "node:https"; import net from "node:net"; +import { join } from "node:path"; import { pipeline, Writable } from "node:stream"; import tls from "node:tls"; @@ -21,6 +22,13 @@ async function listen(server: http.Server) { return (server.address() as net.AddressInfo).port; } +// Closes the server and waits for its connections. A connection frees its response on a later turn +// of the event loop than the one that closes it, and the last test of the file must not end before that. +async function close(server: http.Server) { + server.closeAllConnections(); + await new Promise(resolve => server.close(resolve)); +} + // For setSocketOptions(). A small kernel buffer keeps most of a large write with the server, whatever the limits of the machine are. const KERNEL_BUFFER = 64 * 1024; @@ -470,56 +478,84 @@ describe("node:http server timeout enforcement", () => { // The reset of the peer and the request of another connection reach the server together. The handler of // that request keeps the event loop past the deadline of the timer, so the timer is due when the connection // has closed and its 'close' is not out yet. Node emits no 'timeout' there. - test("no 'timeout' on a connection that its peer has reset", async () => { - const TIMEOUT = 400; - let deadline = 0; - let timeoutsBehindTheClose = 0; - let reset: net.Socket | undefined; - let other: net.Socket | undefined; - const { promise: closed, resolve: onClosed } = Promise.withResolvers(); - const server = http.createServer((req, res) => { - if (req.url === "/busy") { - while (performance.now() < deadline + 50) {} - res.end(); - return; + test.each(["socket.setTimeout()", "keepAliveTimeout"])( + "no 'timeout' from %s on a connection that its peer has reset", + async armedBy => { + const TIMEOUT = 400; + let deadline = 0; + let expiriesBehindTheClose = 0; + let timeoutsBehindTheClose = 0; + let reset: net.Socket | undefined; + let other: net.Socket | undefined; + const { promise: closed, resolve: onClosed } = Promise.withResolvers(); + const server = http.createServer((req, res) => { + if (req.url === "/busy") { + // No event to wait for: the event loop must not turn before the timer is due. + while (performance.now() < deadline + 50) {} + res.end(); + return; + } + const socket = req.socket as net.Socket & { _onTimeout(): void }; + // The native connection says when it has closed. + const handle = (socket as any)[ + Object.getOwnPropertySymbols(socket).find(symbol => symbol.description === "handle")! + ]; + // Every expiry of the timer comes through here. + const onTimeout = socket._onTimeout; + socket._onTimeout = function () { + if (handle.closed) expiriesBehindTheClose++; + return onTimeout.call(this); + }; + socket.on("timeout", () => handle.closed && timeoutsBehindTheClose++); + socket.on("close", onClosed); + for (const stream of [socket, req, res]) stream.on("error", () => {}); + const armed = () => { + deadline = performance.now() + TIMEOUT; + // On the next turn, when the server has left the read of this connection. + setImmediate(() => { + reset!.resetAndDestroy(); + other!.write("GET /busy HTTP/1.1\r\nHost: a\r\n\r\n"); + // Both are at the sockets of the server before the event loop asks the kernel again. The loopback of macOS delivers on another thread. + Bun.sleepSync(10); + }); + }; + if (armedBy === "keepAliveTimeout") { + // The keep-alive timeout starts behind the response. + res.on("finish", armed); + res.end(); + } else { + socket.setTimeout(TIMEOUT); + armed(); + } + }); + server.keepAliveTimeout = TIMEOUT; + server.keepAliveTimeoutBuffer = 0; + // Keeps a socket that times out, so its 'close' comes from the reset alone. + server.on("timeout", () => {}); + const port = await listen(server); + try { + other = net.connect(port, "127.0.0.1"); + other.on("error", () => {}); + other.resume(); + await once(other, "connect"); + reset = net.connect(port, "127.0.0.1"); + reset.on("error", () => {}); + reset.resume(); + await once(reset, "connect"); + reset.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); + await closed; + // The timer came due behind the close, once. Without that the absence of a 'timeout' proves nothing. + expect({ expiriesBehindTheClose, timeoutsBehindTheClose }).toEqual({ + expiriesBehindTheClose: 1, + timeoutsBehindTheClose: 0, + }); + } finally { + reset?.destroy(); + other?.destroy(); + await close(server); } - const socket = req.socket; - // The native connection says when it has closed. - const handle = (socket as any)[ - Object.getOwnPropertySymbols(socket).find(symbol => symbol.description === "handle")! - ]; - socket.setTimeout(TIMEOUT); - deadline = performance.now() + TIMEOUT; - socket.on("timeout", () => handle.closed && timeoutsBehindTheClose++); - socket.on("close", onClosed); - for (const stream of [socket, req, res]) stream.on("error", () => {}); - setTimeout(() => { - // Both are with the kernel before the event loop asks it again. - reset!.resetAndDestroy(); - other!.write("GET /busy HTTP/1.1\r\nHost: a\r\n\r\n"); - }, TIMEOUT - 150); - }); - // Keeps a socket that times out, so its 'close' comes from the reset alone. - server.on("timeout", () => {}); - const port = await listen(server); - try { - other = net.connect(port, "127.0.0.1"); - other.on("error", () => {}); - other.resume(); - await once(other, "connect"); - reset = net.connect(port, "127.0.0.1"); - reset.on("error", () => {}); - await once(reset, "connect"); - reset.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); - await closed; - expect(timeoutsBehindTheClose).toBe(0); - } finally { - reset?.destroy(); - other?.destroy(); - server.closeAllConnections(); - server.close(); - } - }); + }, + ); }); // server.setTimeout (applied per connection), req.setTimeout, res.setTimeout @@ -724,8 +760,7 @@ describe("node:http server writes restart the socket inactivity timeout", () => expect(await Promise.race([firstTimeout, lost(client)])).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { client?.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); @@ -790,8 +825,7 @@ describe("node:http server writes restart the socket inactivity timeout", () => } finally { clearTimeout(reference); client?.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }, ); @@ -855,8 +889,7 @@ describe("node:http server writes restart the socket inactivity timeout", () => } finally { clearTimeout(reference); client?.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); @@ -900,8 +933,7 @@ describe("node:http server writes restart the socket inactivity timeout", () => } finally { clearTimeout(reference); client?.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); @@ -940,8 +972,7 @@ describe("node:http server writes restart the socket inactivity timeout", () => } finally { clearTimeout(reference); client?.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); @@ -986,8 +1017,7 @@ describe("node:http server writes restart the socket inactivity timeout", () => } finally { clearTimeout(reference); client?.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); @@ -1025,8 +1055,7 @@ describe("node:http server writes restart the socket inactivity timeout", () => } finally { clearTimeout(reference); client?.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); }); @@ -1041,7 +1070,7 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock // all. Then it takes the rest. const SLOW_FOR = TIMEOUT * 2.75; const STEP = 16 * 1024; - function readSlowly(client: net.Socket, step = STEP, slowFor = SLOW_FOR) { + function readSlowly(client: net.Socket) { let budget = 0; let received = 0; let head = -1; @@ -1062,7 +1091,7 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock }); const pump = setInterval(() => { // A chunk that was larger than the step is paid off before the next one. - if (startedAt === 0 || performance.now() - startedAt < slowFor) budget = Math.min(budget + step, step); + if (startedAt === 0 || performance.now() - startedAt < SLOW_FOR) budget = Math.min(budget + STEP, STEP); else budget = Infinity; if (budget > 0) client.resume(); }, 20); @@ -1155,8 +1184,7 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock ); } finally { client.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); @@ -1189,52 +1217,141 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock ); } finally { client.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); - // The TLS layer takes a batch of records from the server and holds the ciphertext that the kernel does not take - // yet. The reader takes 2 KB every 20 ms to the end, and the buffers are small, so a batch leaves for longer - // than two periods. In that time no plaintext leaves the server, and behind the last batch none is left. - // Not concurrent: while another connection of the process holds a batch, the TLS layer sends record by record. - test("no 'timeout' while a slow reader drains what the TLS layer holds of one res.end()", async () => { - const body = BODY.subarray(0, 224 * 1024); - let timeouts = 0; - let pendingFor = 0; + // The timer clock counts whole milliseconds. A server that does not count the bytes fires TIMEOUT / 2 early or more. + const SLACK = 50; + + // A TCP relay in front of a server on a Unix socket. Every 20 ms it takes up to allowance() bytes of the server + // from the kernel and hands them to its client, so it is the relay that says how fast the bytes leave the server. + async function relay(path: string, allowance: () => number) { + const listener = net.createServer(client => { + const server = net.connect({ path }); + // The event loop never reads it. + server.pause(); + server.on("error", () => client.destroy()); + client.on("error", () => server.destroy()); + client.on("data", chunk => server.write(chunk)); + const into = Buffer.alloc(64 * 1024); + const pump = setInterval(() => { + const fd: number | undefined = (server as any)._handle?.fd; + if (fd === undefined || fd < 0) return; + try { + for (let left = allowance(); left > 0;) { + const length = readSync(fd, into, 0, Math.min(left, into.length), null); + // The server has closed the connection. + if (length === 0) return client.end(); + client.write(Buffer.from(into.subarray(0, length))); + left -= length; + } + } catch (error: any) { + if (error.code !== "EAGAIN") client.destroy(error); + } + }, 20); + client.on("close", () => { + clearInterval(pump); + server.destroy(); + }); + }); + listener.listen(0, "127.0.0.1"); + await once(listener, "listening"); + return listener; + } + + // One write of 128 KB is one batch of TLS records. The TLS layer takes all of it and holds the ciphertext that + // the kernel does not take, so the server has no byte of the write left. The kernel holds 8 KB of a Unix socket + // with this send buffer, where a TCP connection starts with a receive window of half the batch. The relay takes + // 2 KB every 20 ms for longer than two periods, then the rest. Behind that a last write restarts the timer, and + // what the TLS layer held before it must not hold 'timeout' back again. + test("the ciphertext that the TLS layer holds of a write counts while it leaves, and not after", async () => { + // A short name: the path of a Unix socket has about 100 bytes. + using dir = tempDir("http-tls", {}); + const path = join(String(dir), "s.sock"); + const body = BODY.subarray(0, 128 * 1024); + let slowSince = 0; + let expiriesWhileLeaving = 0; + let timeoutsWhileLeaving = 0; + let lastWriteAt = 0; + let timedOutAt = 0; + let referenceFired = false; + let reference: ReturnType | undefined; + let writeLast = () => {}; + const { promise: settled, resolve: onSettled } = Promise.withResolvers(); const server = https.createServer(tlsOptions, (req, res) => { - setSocketOptions(req.socket, 1, KERNEL_BUFFER / 4); - res.writeHead(200, { "Content-Length": body.length, "Connection": "close" }); - const wroteAt = performance.now(); - res.end(body, () => (pendingFor = performance.now() - wroteAt)); + const socket = req.socket as net.Socket & { _onTimeout(): void }; + setSocketOptions(socket, 1, 4 * 1024); + // Every expiry of the timer comes through here. + const onTimeout = socket._onTimeout; + socket._onTimeout = function () { + if (lastWriteAt === 0) expiriesWhileLeaving++; + return onTimeout.call(this); + }; + res.writeHead(200, { "Content-Length": body.length + 1 }); + slowSince = performance.now(); + res.write(body); + writeLast = () => { + lastWriteAt = performance.now(); + res.write("x"); + // Between the first expiry behind this write and the second one. + reference = setTimeout(() => { + referenceFired = true; + onSettled(); + }, TIMEOUT * 1.5); + }; }); server.timeout = TIMEOUT; server.on("timeout", socket => { - timeouts++; + if (lastWriteAt === 0) timeoutsWhileLeaving++; + else timedOutAt = performance.now(); socket.destroy(); + onSettled(); + }); + server.listen(path); + await once(server, "listening"); + const slowFor = TIMEOUT * 2.25; + const inFront = await relay(path, () => + slowSince !== 0 && performance.now() - slowSince < slowFor ? 2 * 1024 : Infinity, + ); + const client = tls.connect({ + port: (inFront.address() as net.AddressInfo).port, + host: "127.0.0.1", + rejectUnauthorized: false, }); - const port = await listen(server); - const client = tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false }); try { - const received = readSlowly(client, 2 * 1024, Infinity); + let received = 0; + let head = -1; + let beforeBody = ""; + client.on("error", () => {}); + client.on("data", chunk => { + if (head === -1) { + // The blank line that ends the head can lie across two chunks. + beforeBody += chunk.toString("latin1"); + const end = beforeBody.indexOf("\r\n\r\n"); + if (end !== -1) head = end + 4; + } + received += chunk.length; + // The whole first write is here, so the TLS layer holds nothing. + if (head !== -1 && received - head === body.length) writeLast(); + }); await once(client, "secureConnect"); - setSocketOptions(client, 2, KERNEL_BUFFER / 4); client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); - expect({ body: await received, timeouts, pendingForTwoPeriods: pendingFor > 2 * TIMEOUT }).toEqual({ - body: body.length, - timeouts: 0, - pendingForTwoPeriods: true, + await settled; + expect({ timeoutsWhileLeaving, twoExpiriesWhileLeaving: expiriesWhileLeaving >= 2, referenceFired }).toEqual({ + timeoutsWhileLeaving: 0, + twoExpiriesWhileLeaving: true, + referenceFired: false, }); + expect(timedOutAt - lastWriteAt).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { + clearTimeout(reference); client.destroy(); - server.closeAllConnections(); - server.close(); + inFront.close(); + await close(server); } }); - // The timer clock counts whole milliseconds. A server that does not count the bytes fires TIMEOUT / 2 early or more. - const SLACK = 50; - // What the kernel holds for a paused client, taken from its descriptor at once. Behind it the send buffer of the server has room again. function takeFromKernel(client: net.Socket, into: Buffer) { const fd: number = (client as any)._handle.fd; @@ -1251,9 +1368,30 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock } } + // Makes the server send what it holds of its writes from inside write() calls alone. The client is paused, this + // takes its bytes from the kernel, and each call then sends what the kernel has room for. + function sendPendingBytesInWrites(res: http.ServerResponse, client: net.Socket) { + // The native response says what it still holds of the writes. + const handle = (res as any)[Object.getOwnPropertySymbols(res).find(symbol => symbol.description === "handle")!]; + if (handle.bufferedAmount === 0) throw new Error("the kernel took the whole megabyte"); + const into = Buffer.alloc(64 * 1024); + const giveUpAt = performance.now() + 3000; + while (handle.bufferedAmount > 0) { + if (takeFromKernel(client, into) === 0) { + // The last call sent nothing: on macOS the kernel opens the window of the client a moment behind the read. + if (performance.now() > giveUpAt) throw new Error("the pending bytes do not leave"); + Bun.sleepSync(1); + } + res.write("ping"); + } + // Room for the last ping, which leaves when the caller returns. + takeFromKernel(client, into); + } + // A write() call sends the bytes that are pending before its own. The call that sends the last of them is - // activity, although nothing is pending when the timer fires next. The client is paused and the handler takes - // its bytes from the kernel itself, so no byte leaves the server outside a write() call. + // activity, although nothing is pending when the timer fires next, and the kernel then reports the socket + // writable once more. The server counts the bytes there, as Node does when a pending write completes: 'timeout' + // comes one period behind them. No byte leaves the server outside a write() call. test.concurrent.each([ ["many chunks of 8 KB", 8 * 1024], // Over 16 KB the server keeps the rest of a write by reference, not in the buffer of the connection. @@ -1261,43 +1399,42 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock ])("a write() that sends the last pending bytes of %s restarts the period", async (_, size) => { let client: net.Socket | undefined; let emptying: ReturnType | undefined; + let reference: ReturnType | undefined; + let referenceFired = false; let emptiedAt = 0; - const { promise: timedOut, resolve: onTimedOut, reject: onFailed } = Promise.withResolvers(); + let timedOutAt = 0; + const { promise: settled, resolve: onSettled, reject: onFailed } = Promise.withResolvers(); const server = http.createServer((req, res) => { const socket = req.socket as net.Socket & { _onTimeout(): void }; setSocketOptions(socket, 1, KERNEL_BUFFER); res.writeHead(200); for (let written = 0; written < 1024 * 1024; written += size) res.write(Buffer.alloc(size, "a")); - // The native response says what it still holds of the writes. - const handle = (res as any)[Object.getOwnPropertySymbols(res).find(symbol => symbol.description === "handle")!]; const empty = () => { try { - if (handle.bufferedAmount === 0) throw new Error("the kernel took the whole megabyte"); - const into = Buffer.alloc(64 * 1024); - const giveUpAt = performance.now() + 3000; - while (handle.bufferedAmount > 0) { - if (takeFromKernel(client!, into) > 0) res.write("ping"); - else if (performance.now() > giveUpAt) throw new Error("the pending bytes do not leave"); - } - // Room for the last ping, which leaves when this callback returns. - takeFromKernel(client!, into); + sendPendingBytesInWrites(res, client!); emptiedAt = performance.now(); + // A server that counts these bytes only when the timer fires is due at the second expiry from here, behind this. + reference = setTimeout(() => { + referenceFired = true; + onSettled(); + }, TIMEOUT * 1.5); } catch (error) { onFailed(error); } }; - // The first expiry finds the writes pending. Three quarters of a period behind it, when the kernel takes no more by itself, the handler sends the rest. + // The first expiry finds the writes pending. A quarter of a period behind it, when the kernel takes no more by itself, the handler sends the rest. const onTimeout = socket._onTimeout; let expiries = 0; socket._onTimeout = function () { - if (++expiries === 1) emptying = setTimeout(empty, TIMEOUT * 0.75); + if (++expiries === 1) emptying = setTimeout(empty, TIMEOUT / 4); return onTimeout.call(this); }; }); server.timeout = TIMEOUT; server.on("timeout", socket => { - onTimedOut(performance.now()); + timedOutAt = performance.now(); socket.destroy(); + onSettled(); }); const port = await listen(server); try { @@ -1307,15 +1444,15 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock await once(client, "connect"); setSocketOptions(client, 2, KERNEL_BUFFER); client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); - const timedOutAt = await timedOut; - // A server that does not count those bytes fires at the first expiry, or a quarter of a period behind them. - expect(emptiedAt).not.toBe(0); + await settled; + expect({ emptied: emptiedAt !== 0, referenceFired }).toEqual({ emptied: true, referenceFired: false }); + // A server that does not count those bytes fires three quarters of a period behind them. expect(timedOutAt - emptiedAt).toBeGreaterThanOrEqual(TIMEOUT - SLACK); } finally { clearTimeout(emptying); + clearTimeout(reference); client?.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); @@ -1384,8 +1521,7 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock } finally { clearInterval(pump); client.destroy(); - server.closeAllConnections(); - server.close(); + await close(server); } }); }); From a79f34f8251942d869b52b3b0118b804969718a3 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 9 Nov 2026 16:42:30 +0000 Subject: [PATCH 15/15] node:http: let the bytes leave at an even pace in the test of a peer that stops The test read through a client that pauses itself by a budget, and it stopped half a period behind the first expiry of the timer. On macOS no byte left the server between that expiry and the stop in one CI run, so 'timeout' came half a period behind the stop. The test now takes 16 KB of the paused client from the kernel every 20 ms, and it stops one and a half periods behind the write. --- .../http/node-http-server-timeouts.test.ts | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/test/js/node/http/node-http-server-timeouts.test.ts b/test/js/node/http/node-http-server-timeouts.test.ts index 6b2f02c9d252..85ebcddf5ebe 100644 --- a/test/js/node/http/node-http-server-timeouts.test.ts +++ b/test/js/node/http/node-http-server-timeouts.test.ts @@ -1352,13 +1352,13 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock } }); - // What the kernel holds for a paused client, taken from its descriptor at once. Behind it the send buffer of the server has room again. - function takeFromKernel(client: net.Socket, into: Buffer) { + // What the kernel holds for a paused client, taken from its descriptor at once: all of it, or `limit` bytes. Behind it the send buffer of the server has room again. + function takeFromKernel(client: net.Socket, into: Buffer, limit = Infinity) { const fd: number = (client as any)._handle.fd; let taken = 0; - for (;;) { + while (taken < limit) { try { - const length = readSync(fd, into); + const length = readSync(fd, into, 0, Math.min(into.length, limit - taken), null); if (length === 0) return taken; taken += length; } catch (error: any) { @@ -1366,6 +1366,7 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock return taken; } } + return taken; } // Makes the server send what it holds of its writes from inside write() calls alone. The client is paused, this @@ -1457,10 +1458,13 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock }); // Node records the size of the write queue at every expiry, so an expiry that finds progress also uses up the - // one that a pending write gets without progress. Here bytes leave until half a period behind the first - // expiry. Then the reader stops and the handler stops. The second expiry behind that is 'timeout', not the third. + // one that a pending write gets without progress. Here the client is paused, and the test takes 16 KB of its + // bytes from the kernel every 20 ms, so bytes leave the server at an even pace. One and a half periods behind + // the write the test stops, and the handler with it. The second expiry behind that is 'timeout', not the third. test.concurrent("a peer that stops reading is timed out at the second expiry behind its last byte", async () => { - let stop = () => {}; + let client: net.Socket | undefined; + let pump: ReturnType | undefined; + let stopping: ReturnType | undefined; let stoppedAt = 0; let expiriesBehindStop = 0; const { promise: timedOut, resolve: onTimedOut, reject: onFailed } = Promise.withResolvers(); @@ -1472,18 +1476,23 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock // Each call sends bytes of the body, which is pending. const interval = setInterval(() => res.write("data: ping\n\n"), 20); res.on("close", () => clearInterval(interval)); + const step = Buffer.alloc(STEP); + pump = setInterval(() => { + try { + takeFromKernel(client!, step, STEP); + } catch (error) { + onFailed(error); + } + }, 20); + stopping = setTimeout(() => { + clearInterval(pump); + clearInterval(interval); + stoppedAt = performance.now(); + }, TIMEOUT * 1.5); // Every expiry of the timer comes through here. const onTimeout = socket._onTimeout; - let expiries = 0; socket._onTimeout = function () { if (stoppedAt !== 0) expiriesBehindStop++; - else if (++expiries === 1) { - setTimeout(() => { - stop(); - clearInterval(interval); - stoppedAt = performance.now(); - }, TIMEOUT / 2); - } return onTimeout.call(this); }; }); @@ -1493,34 +1502,22 @@ describe.skipIf(isWindows)("node:http: a write that drains slowly holds the sock socket.destroy(); }); const port = await listen(server); - const client = net.connect(port, "127.0.0.1"); - let pump: ReturnType | undefined; try { - let budget = 0; - client.on("data", chunk => { - budget -= chunk.length; - if (budget <= 0) client.pause(); - }); - pump = setInterval(() => { - budget = Math.min(budget + STEP, STEP); - if (budget > 0) client.resume(); - }, 20); - stop = () => { - clearInterval(pump); - client.pause(); - }; + client = net.connect(port, "127.0.0.1"); + client.pause(); client.on("error", onFailed); await once(client, "connect"); setSocketOptions(client, 2, KERNEL_BUFFER); client.write("GET / HTTP/1.1\r\nHost: a\r\n\r\n"); const timedOutAt = await timedOut; - // A server that does not see the bytes leave fires before the reader stops, or half a period behind that. + // A server that does not see the bytes leave fires before the test stops, or half a period behind that. expect(stoppedAt).not.toBe(0); expect(timedOutAt - stoppedAt).toBeGreaterThanOrEqual(TIMEOUT - SLACK); expect(expiriesBehindStop).toBeLessThanOrEqual(2); } finally { clearInterval(pump); - client.destroy(); + clearTimeout(stopping); + client?.destroy(); await close(server); } });