Extract and modernize the webserver's request checking code
The `handler` method contained this code in two inline functions, triggered via callbacks, which made the `handler` method big and harder to read. Moreover, this code relied on variables from the outer scope, which made it harder to reason about because the inputs and outputs weren't easily visible. This commit fixes the problems by extracting the request checking code into a dedicated private method, and modernizing it to use e.g. `const`/ `let` instead of `var` and using template strings. The logic is now self-contained in a single method that can be read from top to bottom without callbacks and with comments annotating each check/section.
This commit is contained in:
parent
f1a225889b
commit
6ef813af01
@ -82,8 +82,7 @@ class WebServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#handler(req, res) {
|
async #handler(req, res) {
|
||||||
var self = this;
|
|
||||||
var url = req.url.replaceAll("//", "/");
|
var url = req.url.replaceAll("//", "/");
|
||||||
var urlParts = /([^?]*)((?:\?(.*))?)/.exec(url);
|
var urlParts = /([^?]*)((?:\?(.*))?)/.exec(url);
|
||||||
try {
|
try {
|
||||||
@ -103,8 +102,6 @@ class WebServer {
|
|||||||
res.end("Bad request", "utf8");
|
res.end("Bad request", "utf8");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var queryPart = urlParts[3];
|
|
||||||
var verbose = this.verbose;
|
|
||||||
|
|
||||||
var methodHooks = this.hooks[req.method];
|
var methodHooks = this.hooks[req.method];
|
||||||
if (!methodHooks) {
|
if (!methodHooks) {
|
||||||
@ -120,71 +117,70 @@ class WebServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pathPart === "/favicon.ico") {
|
if (pathPart === "/favicon.ico") {
|
||||||
fs.realpath(
|
pathPart = "test/resources/favicon.ico";
|
||||||
path.join(this.root, "test/resources/favicon.ico"),
|
}
|
||||||
checkFile
|
await this.#checkRequest(req, res, url, urlParts, pathPart);
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var disableRangeRequests = this.disableRangeRequests;
|
async #checkRequest(request, response, url, urlParts, pathPart) {
|
||||||
|
// Check if the file/folder exists.
|
||||||
var filePath;
|
let filePath;
|
||||||
fs.realpath(path.join(this.root, pathPart), checkFile);
|
try {
|
||||||
|
filePath = await fsPromises.realpath(path.join(this.root, pathPart));
|
||||||
function checkFile(err, file) {
|
} catch {
|
||||||
if (err) {
|
response.writeHead(404);
|
||||||
res.writeHead(404);
|
response.end();
|
||||||
res.end();
|
if (this.verbose) {
|
||||||
if (verbose) {
|
console.error(`${url}: not found`);
|
||||||
console.error(url + ": not found");
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
filePath = file;
|
|
||||||
fs.stat(filePath, statFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileSize;
|
// Get the properties of the file/folder.
|
||||||
|
let stats;
|
||||||
function statFile(err, stats) {
|
try {
|
||||||
if (err) {
|
stats = await fsPromises.stat(filePath);
|
||||||
res.writeHead(500);
|
} catch {
|
||||||
res.end();
|
response.writeHead(500);
|
||||||
|
response.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const fileSize = stats.size;
|
||||||
|
const isDir = stats.isDirectory();
|
||||||
|
|
||||||
fileSize = stats.size;
|
// If a folder is requested, serve the directory listing.
|
||||||
var isDir = stats.isDirectory();
|
|
||||||
if (isDir && !/\/$/.test(pathPart)) {
|
if (isDir && !/\/$/.test(pathPart)) {
|
||||||
res.setHeader("Location", pathPart + "/" + urlParts[2]);
|
response.setHeader("Location", `${pathPart}/${urlParts[2]}`);
|
||||||
res.writeHead(301);
|
response.writeHead(301);
|
||||||
res.end("Redirected", "utf8");
|
response.end("Redirected", "utf8");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
self.#serveDirectoryIndex(res, pathPart, queryPart, filePath);
|
const queryPart = urlParts[3];
|
||||||
|
await this.#serveDirectoryIndex(response, pathPart, queryPart, filePath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var range = req.headers.range;
|
// If a file is requested with range requests, serve it accordingly.
|
||||||
if (range && !disableRangeRequests) {
|
const { range } = request.headers;
|
||||||
var rangesMatches = /^bytes=(\d+)-(\d+)?/.exec(range);
|
if (range && !this.disableRangeRequests) {
|
||||||
|
const rangesMatches = /^bytes=(\d+)-(\d+)?/.exec(range);
|
||||||
if (!rangesMatches) {
|
if (!rangesMatches) {
|
||||||
res.writeHead(501);
|
response.writeHead(501);
|
||||||
res.end("Bad range", "utf8");
|
response.end("Bad range", "utf8");
|
||||||
if (verbose) {
|
if (this.verbose) {
|
||||||
console.error(url + ': bad range: "' + range + '"');
|
console.error(`${url}: bad range: ${range}`);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var start = +rangesMatches[1];
|
|
||||||
var end = +rangesMatches[2];
|
const start = +rangesMatches[1];
|
||||||
if (verbose) {
|
const end = +rangesMatches[2];
|
||||||
console.log(url + ": range " + start + " - " + end);
|
if (this.verbose) {
|
||||||
|
console.log(`${url}: range ${start}-${end}`);
|
||||||
}
|
}
|
||||||
self.#serveFileRange(
|
this.#serveFileRange(
|
||||||
res,
|
response,
|
||||||
filePath,
|
filePath,
|
||||||
fileSize,
|
fileSize,
|
||||||
start,
|
start,
|
||||||
@ -192,11 +188,12 @@ class WebServer {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (verbose) {
|
|
||||||
|
// Otherwise, serve the file normally.
|
||||||
|
if (this.verbose) {
|
||||||
console.log(url);
|
console.log(url);
|
||||||
}
|
}
|
||||||
self.#serveFile(res, filePath, fileSize);
|
this.#serveFile(response, filePath, fileSize);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async #serveDirectoryIndex(response, pathPart, queryPart, directory) {
|
async #serveDirectoryIndex(response, pathPart, queryPart, directory) {
|
||||||
|
Loading…
Reference in New Issue
Block a user