Skip to content

Commit ae9d251

Browse files
committed
Failed middleware should return HTTP 500, fixes #150
1 parent ad3beb9 commit ae9d251

2 files changed

Lines changed: 85 additions & 6 deletions

File tree

‎server.js‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -443,16 +443,30 @@ export default class DevServer {
443443
// Runs very first in the middleware chain
444444
middlewares.push(this.devServerMiddleware);
445445

446+
let handleError = (e) => {
447+
this.logger.error(`Server error: ${e.message}`);
448+
if(!res.bodyUsed) {
449+
if(!res.headersSent) {
450+
res.statusCode = 500;
451+
}
452+
res.end("");
453+
}
454+
};
455+
446456
let bound = [];
447457
let next;
448458

449459
for(let ware of middlewares) {
450-
let fn;
451-
if(next) {
452-
fn = ware.bind(this, req, res, next);
453-
} else {
454-
fn = ware.bind(this, req, res);
455-
}
460+
let args = next ? [req, res, next] : [req, res];
461+
// Middleware call `next()` without awaiting it, so each one catches its own errors
462+
let fn = () => {
463+
try {
464+
let result = ware.call(this, ...args);
465+
return typeof result?.catch === "function" ? result.catch(handleError) : result;
466+
} catch(e) {
467+
handleError(e);
468+
}
469+
};
456470
bound.push(fn);
457471
next = fn;
458472
}

‎test/testServerThread.js‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,68 @@ test("Encoded traversal to a sibling directory is refused in both modes", async
493493
t.false(threaded.body.includes("SECRET"));
494494
t.false(single.body.includes("SECRET"));
495495
});
496+
497+
async function requestThenSample(server, path) {
498+
let failed = await sendRequest(server, path);
499+
let after = await sendRequest(server, "/sample");
500+
return { failed, after };
501+
}
502+
503+
test("A throwing middleware responds with 500 in both modes", async (t) => {
504+
let options = {
505+
middleware: [
506+
function(req, res, next) {
507+
if(req.url === "/boom") {
508+
throw new Error("middleware exploded");
509+
}
510+
next();
511+
},
512+
],
513+
};
514+
515+
let results = await inBothModes(options, (server) => requestThenSample(server, "/boom"));
516+
517+
for(let { failed, after } of Object.values(results)) {
518+
t.is(failed.statusCode, 500);
519+
t.is(after.statusCode, 200);
520+
}
521+
});
522+
523+
test("A rejection after an un-awaited next() responds with 500 in both modes", async (t) => {
524+
let options = {
525+
middleware: [
526+
async function(req, res, next) {
527+
next();
528+
},
529+
async function(req, res, next) {
530+
await new Promise((resolve) => setTimeout(resolve, 10));
531+
if(req.url === "/boom") {
532+
throw new Error("async middleware exploded");
533+
}
534+
next();
535+
},
536+
],
537+
};
538+
539+
let results = await inBothModes(options, (server) => requestThenSample(server, "/boom"));
540+
541+
for(let { failed, after } of Object.values(results)) {
542+
t.is(failed.statusCode, 500);
543+
t.is(after.statusCode, 200);
544+
}
545+
});
546+
547+
test("An invalid onRequest return value responds with 500 in both modes", async (t) => {
548+
let options = {
549+
onRequest: {
550+
"/bad": () => 42,
551+
},
552+
};
553+
554+
let results = await inBothModes(options, (server) => requestThenSample(server, "/bad"));
555+
556+
for(let { failed, after } of Object.values(results)) {
557+
t.is(failed.statusCode, 500);
558+
t.is(after.statusCode, 200);
559+
}
560+
});

0 commit comments

Comments
 (0)