From 0e6deb8d9df2804324e033317c0b0fb1e087f0c0 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Fri, 19 Jun 2026 14:38:01 +0000 Subject: [PATCH] http: add 'sendingHeaders' event for response header manipulation Signed-off-by: Sebastian Beltran --- doc/api/http.md | 62 ++++++++++ lib/_http_server.js | 26 +++- test/parallel/test-http-sending-headers.js | 133 +++++++++++++++++++++ 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-http-sending-headers.js diff --git a/doc/api/http.md b/doc/api/http.md index 76950b95f8c5de..02602061703c1c 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -2086,6 +2086,61 @@ emitted when the last segment of the response headers and body have been handed off to the operating system for transmission over the network. It does not imply that the client has received anything yet. +### Event: `'sendingHeaders'` + + + +Emitted synchronously, exactly once, immediately before the status line and +response headers are serialized and sent to the client. The listener is called +with `this` bound to the response. + +This is the moment at which the response is about to become committed: when the +event fires, [`response.headersSent`][] is still `false`, so a listener may make +final changes to the outgoing message. From inside the listener it is valid to: + +* read headers with [`response.getHeader()`][] / [`response.getHeaders()`][]; +* add, replace, or remove headers with [`response.setHeader()`][], + [`outgoingMessage.appendHeader()`][], and [`response.removeHeader()`][]; +* change [`response.statusCode`][] and [`response.statusMessage`][]. + +The event is emitted regardless of how the headers are flushed: an explicit call +to [`response.writeHead()`][], implicit headers triggered by the first +[`response.write()`][] or [`response.end()`][], or [`response.flushHeaders()`][]. +Because it is a single emission point, multiple independent listeners (for +example logging, session, and content-negotiation middleware) can each register +without coordinating with one another. Listeners run in registration order. + +The listener runs synchronously and must not be an `async` function: work +deferred past an `await` runs after the headers are already sent, so only +changes made synchronously take effect. + +```js +const server = http.createServer((req, res) => { + res.on('sendingHeaders', function() { + // Set a header at the last possible moment, based on final state. + this.setHeader('X-Response-Time', `${Date.now() - req.startTime}ms`); + if (!this.getHeader('Content-Type')) { + this.setHeader('Content-Type', 'text/plain'); + } + }); + + res.end('hello'); +}); +``` + +A header passed inline to `response.writeHead()` is visible to the listener and +can be modified there, including the array form: + +```js +res.on('sendingHeaders', function() { + // 'X-Inline' was passed to writeHead() below; it can still be removed here. + this.removeHeader('X-Inline'); +}); +res.writeHead(200, ['X-Inline', 'value', 'Content-Type', 'text/plain']); +``` + ### `response.addTrailers(headers)`