-
Notifications
You must be signed in to change notification settings - Fork 0
/
staticFileHandler.js
121 lines (105 loc) · 3.78 KB
/
staticFileHandler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
const
{ returnErrorPage } = require("./errorPageGenerator"),
StatusCodeMessages = require("http").STATUS_CODES,
fileSystem = require("fs"),
{ pathToFileURL, URL, fileURLToPath } = require('url'),
serverConfig = require("./config").general,
mimeTypes = require("mimetype");
let isStaticPathSet = false,
isStaticServerEnabled = true,
staticFileSearchPath = pathToFileURL("./").pathname;
const config = {
get shouldServe() { return isStaticPathSet && isStaticServerEnabled; },
set shouldServe(newVal) { isStaticServerEnabled = !!newVal; },
get searchPath() { return staticFileSearchPath; },
set searchPath(path) {
if (serverConfig.hasServerStarted) return;
//Do not change directory after launching the server.
const isString = typeof path === "string",
isURL = typeof path === "object" && path instanceof URL;
if (!(isString || isURL)) {
throw "Path must be an string or an URL pointing to the desired folder.";
}
if (isString) {
path = pathToFileURL(path[path.length - 1] === "/" ? path + "/" : path);
} else {
if (path.protocol !== "file:") throw "Only file URLs are supported.";
}
isStaticPathSet = true;
staticFileSearchPath = fileURLToPath(path);
}
};
/**
* @class FileHandler
* @param {string} filePath
*/
function FileHandler(filePath) {
const stats = fileSystem.statSync(filePath, { bigint: false, throwIfNoEntry: false }),
mimetype = mimeTypes.lookup(filePath, "utf-8");
this.getReadStream = () => fileSystem.createReadStream(filePath, { autoClose: true });
this.exists = !!stats;
this.mimetype = mimetype;
if (stats) {
this.mtime = stats.mtime.toUTCString();
this.length = stats.size;
}
}
/**
* @param {import('http').IncomingMessage} request
* @param {import('http').ServerResponse} response
* @param {string} sanitizedURL
*/
async function respondStatically({ method = "", headers = {} }, response, sanitizedURL) {
const
isMethodGET = method === "GET",
isMethodHEAD = method === "HEAD";
if (!(isMethodHEAD || isMethodGET)) {
response.writeHead(405, StatusCodeMessages[405]);
}
if (!isMethodGET) {
response.setHeader("Allow", "GET, HEAD");
}
if (!(isMethodHEAD || isMethodGET)) {
return response.end();
}
const fileHandler = new FileHandler(
config.searchPath +
sanitizedURL +
(sanitizedURL.endsWith("/") ? "index.html" : "")
);
if (!fileHandler.exists) {
response.writeHead(404, StatusCodeMessages[404]);
returnErrorPage(response, 404);
return;
}
if ("if-modified-since" in headers) {
const requestModifyTime = Date.parse(headers["if-modified-since"]),
fileModifyTime = Date.parse(fileHandler.mtime);
if (requestModifyTime >= fileModifyTime) {
response.writeHead(304, StatusCodeMessages[304]);
response.end();
return;
}
}
response.writeHead(200, StatusCodeMessages[200], {
"Content-Length": fileHandler.length,
"Content-Type": fileHandler.mimetype,
"Last-Modified": fileHandler.mtime,
"Cache-Control": "public, must-revalidate, max-age=86400"
});
if (isMethodHEAD) {
response.end();
return;
}
fileHandler.getReadStream().pipe(response);
}
module.exports.interface = respondStatically;
module.exports.config = config;
/**
* @typedef FileHandler
* @prop {Date=} mtime
* @prop {number=} length
* @prop {boolean} exists
* @prop {string} mimetype
* @prop {()=>import('fs').ReadStream} createReadStream
*/