A hop count cannot tell the socket's peer from a client that sends its
own X-Forwarded-For, so Fastify 5.12.1 and later trust no proxy for one,
and leave numbers out of the type of its trustProxy option. The Node
adapter now does the same, instead of trusting that many hops as Express
does. Addresses, subnets, true and functions are unchanged.
The Node adapter parses query strings with fast-querystring into flat
keys, as Express and Fastify do by default, but offered no way to parse
nested objects and arrays, which Express gets from its "query parser"
setting and Fastify from its querystringParser option. As with Fastify,
new NodeAdapter({ querystringParser }) takes a function the query string
is passed to (without the "?", and "" when the URL has none), such as
str => qs.parse(str). Without it, requests run the same code as before.
Behind a reverse proxy, req.ip was always the proxy's address and
req.protocol "http" behind one that terminates TLS, so the throttler
counted every client as one, express-session would not set secure
cookies, and passport built http:// callback URLs. As Express' "trust
proxy" setting and Fastify's option of the same name, new NodeAdapter({
trustProxy }) takes true, a hop count, addresses or subnets, or a
function. For a request from a trusted proxy, req.ip, req.ips,
req.protocol, req.secure and req.hostname are read from the
X-Forwarded-* headers the way Express reads them, and
req.app.get('trust proxy') returns the setting. Unlike Fastify, which no
longer trusts a hop count, the adapter trusts it as Express does.
Without the option, the requests are NodeRequests as before: the getters
that read the headers belong to a subclass the option creates, so the
default requests run the same code. req.ips is new (empty there), and
req.protocol is typed as a string, as with Express.
The Node twins of the disconnect specs now wait for the producer teardown
as the Express and Fastify ones do since #18260, which fixes the same
off-by-one tick under load. getUrl() is checked for an https server
(#18252), and the specs of #18276 for middleware on routes that differ
only in their version run on the Node adapter too; both pass as they are.
As platform-express (#18268) and platform-fastify pin them. multer 2.5.0
fixes GHSA-q7qp-rjg7-q26m: a request aborted while an asynchronous
fileFilter is pending could crash the process.
The multer interceptors are copies of platform-express' (the file upload
parity spec checks it). Two fixes landed there since: a multipart request
with an unsupported content type is answered with a 415 (#18264), and
isRejectedByPipes() is no longer a named import from @nestjs/core/internal,
which made the package fail to load with a core older than 12.2 (#18251).
Ports body-parser's tests for bytes that arrive after a compressed frame,
which zlib ends the decompression stream on since Node.js 11: the request
still finishes, a late decoder error is swallowed instead of ending the
process, and a body rejected for its charset never creates a decoder. The
parser already keeps an error listener on the decoder once it has read the
body; without it, these tests report the decoder's error as uncaught.
An SSE client whose stream is backed up is dropped once more event data
waits for it than the route's "maxPendingDataLength" allows. The limit
was only checked when the producer emitted an event. A producer that
emits many events at once, synchronously (e.g. replaying past events
before live ones), emits them all before the first write can back the
stream up, and none after, so they waited in memory for a client that
never read them, past the limit.
The limit is now also checked after each write, once the stream is
backed up. A route that sends more than its limit at once, to a client
that reads slower than the events are written, now drops that client
as well, as the limit describes. Such a route can raise or lift it.
* fix(ws): import ws statically so bundlers include it
ws is a dependency of @nestjs/platform-ws, but the adapter loaded it
with createRequire(import.meta.url)('ws') in its constructor, which
esbuild and rolldown don't follow: a bundled app left ws out and exited
with "ws package is missing". A static import is bundled by all of them.
* fix(microservices): load transport packages where bundlers can see them
ClientMqtt, ClientRMQ, ClientNats, ServerGrpc and ServerRMQ loaded their
packages with createRequire() in the constructor, which esbuild and
rolldown don't follow. They now load them with a literal import() when
the client connects or the server listens, as ServerKafka and
ServerRedis already do. createClient() of ClientMqtt and ClientRMQ still
loads the package when called before connect().
ClientGrpcProxy keeps its synchronous load, since getService() is
synchronous; bundled applications have to mark its packages external.
* docs(common): note which bundlers resolve synchronous package loaders
webpack 5 follows createRequire(import.meta.url)('pkg'), esbuild and
rolldown don't. Point to a literal import() loader or a static import.
A packet's declared length was never compared against "maxBufferSize",
so a packet that could never be accepted was still buffered until the
buffer filled up. It is now rejected as soon as its length is read, and
the connection is dropped, since everything that follows is the rest of
that packet.
The idle timer for a partially received packet is refreshed on every
read, so a peer that sends a few bytes now and then kept a partial
packet buffered for as long as it liked. The new "messageReceiveTimeout"
option (default: 300000ms, like "requestTimeout" in node:http) bounds
the time from a packet's first character to its last, and is not
extended by further reads. Time spent with reading suspended due to
backpressure is not counted. Set it to 0 to disable it.
Also corrects the comment on the default "maxBufferSize": it is counted
in characters, which take 1 or 2 bytes each, not 4.
The disconnect specs read the tick count right after the client destroyed its socket, but the server only sees the disconnect once the socket close reaches it. Under load the producer's 20 ms interval could tick once more in between, so the count was off by one (7 of 12 runs failed under full CPU load).
The producer now records its teardown, and the specs take the count once it has happened. They still fail when the producer is never stopped.