Skip to content

Commit 9395782

Browse files
authored
fix: include error handling for Express middlewares (#674)
Following 24786e7. Reference: https://expressjs.com/en/guide/error-handling.html
1 parent 911d0e3 commit 9395782

File tree

3 files changed

+177
-104
lines changed

3 files changed

+177
-104
lines changed

lib/server.ts

+40-27
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export interface ServerOptions {
137137
type Middleware = (
138138
req: IncomingMessage,
139139
res: ServerResponse,
140-
next: () => void
140+
next: (err?: any) => void
141141
) => void;
142142

143143
export abstract class BaseServer extends EventEmitter {
@@ -335,7 +335,7 @@ export abstract class BaseServer extends EventEmitter {
335335
protected _applyMiddlewares(
336336
req: IncomingMessage,
337337
res: ServerResponse,
338-
callback: () => void
338+
callback: (err?: any) => void
339339
) {
340340
if (this.middlewares.length === 0) {
341341
debug("no middleware to apply, skipping");
@@ -344,7 +344,11 @@ export abstract class BaseServer extends EventEmitter {
344344

345345
const apply = (i) => {
346346
debug("applying middleware n°%d", i + 1);
347-
this.middlewares[i](req, res, () => {
347+
this.middlewares[i](req, res, (err?: any) => {
348+
if (err) {
349+
return callback(err);
350+
}
351+
348352
if (i + 1 < this.middlewares.length) {
349353
apply(i + 1);
350354
} else {
@@ -655,8 +659,12 @@ export class Server extends BaseServer {
655659
}
656660
};
657661

658-
this._applyMiddlewares(req, res, () => {
659-
this.verify(req, false, callback);
662+
this._applyMiddlewares(req, res, (err) => {
663+
if (err) {
664+
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
665+
} else {
666+
this.verify(req, false, callback);
667+
}
660668
});
661669
}
662670

@@ -673,32 +681,37 @@ export class Server extends BaseServer {
673681
this.prepare(req);
674682

675683
const res = new WebSocketResponse(req, socket);
684+
const callback = (errorCode, errorContext) => {
685+
if (errorCode) {
686+
this.emit("connection_error", {
687+
req,
688+
code: errorCode,
689+
message: Server.errorMessages[errorCode],
690+
context: errorContext,
691+
});
692+
abortUpgrade(socket, errorCode, errorContext);
693+
return;
694+
}
676695

677-
this._applyMiddlewares(req, res as unknown as ServerResponse, () => {
678-
this.verify(req, true, (errorCode, errorContext) => {
679-
if (errorCode) {
680-
this.emit("connection_error", {
681-
req,
682-
code: errorCode,
683-
message: Server.errorMessages[errorCode],
684-
context: errorContext,
685-
});
686-
abortUpgrade(socket, errorCode, errorContext);
687-
return;
688-
}
689-
690-
const head = Buffer.from(upgradeHead);
691-
upgradeHead = null;
696+
const head = Buffer.from(upgradeHead);
697+
upgradeHead = null;
692698

693-
// some middlewares (like express-session) wait for the writeHead() call to flush their headers
694-
// see https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L220-L244
695-
res.writeHead();
699+
// some middlewares (like express-session) wait for the writeHead() call to flush their headers
700+
// see https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L220-L244
701+
res.writeHead();
696702

697-
// delegate to ws
698-
this.ws.handleUpgrade(req, socket, head, (websocket) => {
699-
this.onWebSocket(req, socket, websocket);
700-
});
703+
// delegate to ws
704+
this.ws.handleUpgrade(req, socket, head, (websocket) => {
705+
this.onWebSocket(req, socket, websocket);
701706
});
707+
};
708+
709+
this._applyMiddlewares(req, res as unknown as ServerResponse, (err) => {
710+
if (err) {
711+
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
712+
} else {
713+
this.verify(req, true, callback);
714+
}
702715
});
703716
}
704717

lib/userver.ts

+93-77
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,24 @@ export class uServer extends BaseServer {
9292
});
9393
}
9494

95-
override _applyMiddlewares(req: any, res: any, callback: () => void): void {
95+
override _applyMiddlewares(
96+
req: any,
97+
res: any,
98+
callback: (err?: any) => void
99+
): void {
96100
if (this.middlewares.length === 0) {
97101
return callback();
98102
}
99103

100104
// needed to buffer headers until the status is computed
101105
req.res = new ResponseWrapper(res);
102106

103-
super._applyMiddlewares(req, req.res, () => {
107+
super._applyMiddlewares(req, req.res, (err) => {
104108
// some middlewares (like express-session) wait for the writeHead() call to flush their headers
105109
// see https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L220-L244
106110
req.res.writeHead();
107111

108-
callback();
112+
callback(err);
109113
});
110114
}
111115

@@ -118,28 +122,34 @@ export class uServer extends BaseServer {
118122

119123
req.res = res;
120124

121-
this._applyMiddlewares(req, res, () => {
122-
this.verify(req, false, (errorCode, errorContext) => {
123-
if (errorCode !== undefined) {
124-
this.emit("connection_error", {
125-
req,
126-
code: errorCode,
127-
message: Server.errorMessages[errorCode],
128-
context: errorContext,
129-
});
130-
this.abortRequest(req.res, errorCode, errorContext);
131-
return;
132-
}
125+
const callback = (errorCode, errorContext) => {
126+
if (errorCode !== undefined) {
127+
this.emit("connection_error", {
128+
req,
129+
code: errorCode,
130+
message: Server.errorMessages[errorCode],
131+
context: errorContext,
132+
});
133+
this.abortRequest(req.res, errorCode, errorContext);
134+
return;
135+
}
136+
137+
if (req._query.sid) {
138+
debug("setting new request for existing client");
139+
this.clients[req._query.sid].transport.onRequest(req);
140+
} else {
141+
const closeConnection = (errorCode, errorContext) =>
142+
this.abortRequest(res, errorCode, errorContext);
143+
this.handshake(req._query.transport, req, closeConnection);
144+
}
145+
};
133146

134-
if (req._query.sid) {
135-
debug("setting new request for existing client");
136-
this.clients[req._query.sid].transport.onRequest(req);
137-
} else {
138-
const closeConnection = (errorCode, errorContext) =>
139-
this.abortRequest(res, errorCode, errorContext);
140-
this.handshake(req._query.transport, req, closeConnection);
141-
}
142-
});
147+
this._applyMiddlewares(req, res, (err) => {
148+
if (err) {
149+
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
150+
} else {
151+
this.verify(req, false, callback);
152+
}
143153
});
144154
}
145155

@@ -154,63 +164,69 @@ export class uServer extends BaseServer {
154164

155165
req.res = res;
156166

157-
this._applyMiddlewares(req, res, () => {
158-
this.verify(req, true, async (errorCode, errorContext) => {
159-
if (errorCode) {
160-
this.emit("connection_error", {
161-
req,
162-
code: errorCode,
163-
message: Server.errorMessages[errorCode],
164-
context: errorContext,
165-
});
166-
this.abortRequest(res, errorCode, errorContext);
167+
const callback = async (errorCode, errorContext) => {
168+
if (errorCode) {
169+
this.emit("connection_error", {
170+
req,
171+
code: errorCode,
172+
message: Server.errorMessages[errorCode],
173+
context: errorContext,
174+
});
175+
this.abortRequest(res, errorCode, errorContext);
176+
return;
177+
}
178+
179+
const id = req._query.sid;
180+
let transport;
181+
182+
if (id) {
183+
const client = this.clients[id];
184+
if (!client) {
185+
debug("upgrade attempt for closed client");
186+
res.close();
187+
} else if (client.upgrading) {
188+
debug("transport has already been trying to upgrade");
189+
res.close();
190+
} else if (client.upgraded) {
191+
debug("transport had already been upgraded");
192+
res.close();
193+
} else {
194+
debug("upgrading existing transport");
195+
transport = this.createTransport(req._query.transport, req);
196+
client.maybeUpgrade(transport);
197+
}
198+
} else {
199+
transport = await this.handshake(
200+
req._query.transport,
201+
req,
202+
(errorCode, errorContext) =>
203+
this.abortRequest(res, errorCode, errorContext)
204+
);
205+
if (!transport) {
167206
return;
168207
}
208+
}
169209

170-
const id = req._query.sid;
171-
let transport;
172-
173-
if (id) {
174-
const client = this.clients[id];
175-
if (!client) {
176-
debug("upgrade attempt for closed client");
177-
res.close();
178-
} else if (client.upgrading) {
179-
debug("transport has already been trying to upgrade");
180-
res.close();
181-
} else if (client.upgraded) {
182-
debug("transport had already been upgraded");
183-
res.close();
184-
} else {
185-
debug("upgrading existing transport");
186-
transport = this.createTransport(req._query.transport, req);
187-
client.maybeUpgrade(transport);
188-
}
189-
} else {
190-
transport = await this.handshake(
191-
req._query.transport,
192-
req,
193-
(errorCode, errorContext) =>
194-
this.abortRequest(res, errorCode, errorContext)
195-
);
196-
if (!transport) {
197-
return;
198-
}
199-
}
210+
// calling writeStatus() triggers the flushing of any header added in a middleware
211+
req.res.writeStatus("101 Switching Protocols");
200212

201-
// calling writeStatus() triggers the flushing of any header added in a middleware
202-
req.res.writeStatus("101 Switching Protocols");
203-
204-
res.upgrade(
205-
{
206-
transport,
207-
},
208-
req.getHeader("sec-websocket-key"),
209-
req.getHeader("sec-websocket-protocol"),
210-
req.getHeader("sec-websocket-extensions"),
211-
context
212-
);
213-
});
213+
res.upgrade(
214+
{
215+
transport,
216+
},
217+
req.getHeader("sec-websocket-key"),
218+
req.getHeader("sec-websocket-protocol"),
219+
req.getHeader("sec-websocket-extensions"),
220+
context
221+
);
222+
};
223+
224+
this._applyMiddlewares(req, res, (err) => {
225+
if (err) {
226+
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
227+
} else {
228+
this.verify(req, true, callback);
229+
}
214230
});
215231
}
216232

test/middlewares.js

+44
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,48 @@ describe("middlewares", () => {
247247
});
248248
});
249249
});
250+
251+
it("should fail on errors (polling)", (done) => {
252+
const engine = listen((port) => {
253+
engine.use((req, res, next) => {
254+
next(new Error("will always fail"));
255+
});
256+
257+
request
258+
.get(`http://localhost:${port}/engine.io/`)
259+
.query({ EIO: 4, transport: "polling" })
260+
.end((err, res) => {
261+
expect(err).to.be.an(Error);
262+
expect(res.status).to.eql(400);
263+
264+
if (engine.httpServer) {
265+
engine.httpServer.close();
266+
}
267+
done();
268+
});
269+
});
270+
271+
it("should fail on errors (websocket)", (done) => {
272+
const engine = listen((port) => {
273+
engine.use((req, res, next) => {
274+
next(new Error("will always fail"));
275+
});
276+
277+
engine.on("connection", () => {
278+
done(new Error("should not connect"));
279+
});
280+
281+
const socket = new WebSocket(
282+
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
283+
);
284+
285+
socket.addEventListener("error", () => {
286+
if (engine.httpServer) {
287+
engine.httpServer.close();
288+
}
289+
done();
290+
});
291+
});
292+
});
293+
});
250294
});

0 commit comments

Comments
 (0)