From d63bf0df8a3e9012fafddde15c93487c1c399e4f Mon Sep 17 00:00:00 2001 From: b84330808 Date: Wed, 21 Oct 2026 01:41:00 +0900 Subject: [PATCH] fix(eio): do not access the uWS request after an async middleware With the uWebSockets.js server, the HttpRequest object must not be used once the route handler has returned. When a middleware (for example express-session with an existing session) or the allowRequest() option completes asynchronously, the handshake failed with: > uWS.HttpRequest must not be accessed after await or route handler return - for WebSocket upgrades, the Sec-WebSocket-* headers are now read synchronously, and the response is written within a cork() callback - for HTTP long-polling, the method is now read from the prepared request.method attribute instead of req.getMethod() Related: https://github.com/socketio/socket.io/issues/5330 Co-Authored-By: Claude Opus 5.5 --- .../engine.io/lib/transports-uws/polling.ts | 4 +- packages/engine.io/lib/userver.ts | 35 +++++++------ packages/engine.io/test/middlewares.js | 49 +++++++++++++++++++ 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/packages/engine.io/lib/transports-uws/polling.ts b/packages/engine.io/lib/transports-uws/polling.ts index ba49015c6d..0e272f38b3 100644 --- a/packages/engine.io/lib/transports-uws/polling.ts +++ b/packages/engine.io/lib/transports-uws/polling.ts @@ -53,9 +53,9 @@ export class Polling extends Transport { // remove the reference to the ServerResponse object (as the first request of the session is kept in memory by default) req.res = null; - if (req.getMethod() === "get") { + if (req.method === "GET") { this.onPollRequest(req, res); - } else if (req.getMethod() === "post") { + } else if (req.method === "POST") { this.onDataRequest(req, res); } else { res.writeStatus("500 Internal Server Error"); diff --git a/packages/engine.io/lib/userver.ts b/packages/engine.io/lib/userver.ts index 76ecc99110..001755b51a 100644 --- a/packages/engine.io/lib/userver.ts +++ b/packages/engine.io/lib/userver.ts @@ -181,6 +181,11 @@ export class uServer extends BaseServer { req.res = res; + // the uWS.HttpRequest object cannot be accessed once the handler has returned (e.g. with an asynchronous middleware or allowRequest() function) + const secWebSocketKey = req.getHeader("sec-websocket-key"); + const secWebSocketProtocol = req.getHeader("sec-websocket-protocol"); + const secWebSocketExtensions = req.getHeader("sec-websocket-extensions"); + const callback = async (errorCode, errorContext) => { if (errorCode !== undefined) { this.emit("connection_error", { @@ -237,22 +242,24 @@ export class uServer extends BaseServer { this.emit("headers", additionalHeaders, req); - // calling writeStatus() triggers the flushing of any header added in a middleware - req.res.writeStatus("101 Switching Protocols"); + res.cork(() => { + // calling writeStatus() triggers the flushing of any header added in a middleware + req.res.writeStatus("101 Switching Protocols"); - Object.keys(additionalHeaders).forEach((key) => { - req.res.writeHeader(key, additionalHeaders[key]); - }); + Object.keys(additionalHeaders).forEach((key) => { + req.res.writeHeader(key, additionalHeaders[key]); + }); - res.upgrade( - { - transport, - }, - req.getHeader("sec-websocket-key"), - req.getHeader("sec-websocket-protocol"), - req.getHeader("sec-websocket-extensions"), - context, - ); + res.upgrade( + { + transport, + }, + secWebSocketKey, + secWebSocketProtocol, + secWebSocketExtensions, + context, + ); + }); }; this._applyMiddlewares(req, res, (err) => { diff --git a/packages/engine.io/test/middlewares.js b/packages/engine.io/test/middlewares.js index 60e1cc6b22..69f76fac75 100644 --- a/packages/engine.io/test/middlewares.js +++ b/packages/engine.io/test/middlewares.js @@ -56,6 +56,55 @@ describe("middlewares", () => { }); }); + it("should apply an asynchronous middleware (polling)", (done) => { + const engine = listen((port) => { + engine.use((req, res, next) => { + res.setHeader("foo", "bar"); + setTimeout(next, 10); + }); + + request + .get(`http://localhost:${port}/engine.io/`) + .query({ EIO: 4, transport: "polling" }) + .end((err, res) => { + expect(err).to.be(null); + expect(res.status).to.eql(200); + expect(res.headers["foo"]).to.eql("bar"); + + if (engine.httpServer) { + engine.httpServer.close(); + } + done(); + }); + }); + }); + + it("should apply an asynchronous middleware (websocket)", (done) => { + const engine = listen((port) => { + engine.use((req, res, next) => { + res.setHeader("foo", "bar"); + setTimeout(next, 10); + }); + + const socket = new WebSocket( + `ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`, + ); + + socket.on("upgrade", (res) => { + expect(res.headers["foo"]).to.eql("bar"); + + if (engine.httpServer) { + engine.httpServer.close(); + } + done(); + }); + + socket.on("open", () => { + socket.close(); + }); + }); + }); + it("should apply all middlewares in order", (done) => { const engine = listen((port) => { let count = 0;