Serve static files
This commit is contained in:
parent
b0fd916eb4
commit
d5a9668e17
6 changed files with 1156 additions and 964 deletions
74
package-lock.json
generated
74
package-lock.json
generated
|
@ -11,18 +11,21 @@
|
|||
"dependencies": {
|
||||
"@ltd/j-toml": "^1.29.0",
|
||||
"applause-cli": "^1.8.0",
|
||||
"fork-awesome": "^1.2.0",
|
||||
"jpake": "^1.0.1",
|
||||
"mime": "^3.0.0",
|
||||
"nexline": "^1.2.2",
|
||||
"p-queue": "^7.2.0",
|
||||
"p-reflect": "^3.0.0",
|
||||
"p-retry": "^5.0.0",
|
||||
"powahroot": "^1.1.1",
|
||||
"pretty-ms": "^7.0.1",
|
||||
"systeminformation": "^5.11.2",
|
||||
"tweetnacl": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.14.23"
|
||||
"@types/mime": "^2.0.3",
|
||||
"esbuild": "^0.14.23",
|
||||
"fork-awesome": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ltd/j-toml": {
|
||||
|
@ -35,6 +38,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.0.2.tgz",
|
||||
"integrity": "sha512-sCVniU+h3GcGqxOmng11BRvf9TfN9yIs8KKjB8C8d75W69cpTfZG80gau9yTx5SxF3gvHGbJhdESzzvnjtf3Og=="
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz",
|
||||
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/retry": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz",
|
||||
|
@ -418,6 +427,7 @@
|
|||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fork-awesome/-/fork-awesome-1.2.0.tgz",
|
||||
"integrity": "sha512-MNwTBnnudMIweHfDtTY8TeR5fxIAZ2w9o8ITn5XDySqdxa4k5AH8IuAMa89RVxDxgPNlosZxqkFKN5UmHXuYSw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.3"
|
||||
}
|
||||
|
@ -474,6 +484,17 @@
|
|||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nexline": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/nexline/-/nexline-1.2.2.tgz",
|
||||
|
@ -552,6 +573,14 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
|
||||
"integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/powahroot": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/powahroot/-/powahroot-1.1.1.tgz",
|
||||
|
@ -566,6 +595,20 @@
|
|||
"url": "https://liberapay.com/sbrl"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-ms": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
|
||||
"integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==",
|
||||
"dependencies": {
|
||||
"parse-ms": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/retry": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||
|
@ -629,6 +672,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.0.2.tgz",
|
||||
"integrity": "sha512-sCVniU+h3GcGqxOmng11BRvf9TfN9yIs8KKjB8C8d75W69cpTfZG80gau9yTx5SxF3gvHGbJhdESzzvnjtf3Og=="
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz",
|
||||
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/retry": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz",
|
||||
|
@ -827,7 +876,8 @@
|
|||
"fork-awesome": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fork-awesome/-/fork-awesome-1.2.0.tgz",
|
||||
"integrity": "sha512-MNwTBnnudMIweHfDtTY8TeR5fxIAZ2w9o8ITn5XDySqdxa4k5AH8IuAMa89RVxDxgPNlosZxqkFKN5UmHXuYSw=="
|
||||
"integrity": "sha512-MNwTBnnudMIweHfDtTY8TeR5fxIAZ2w9o8ITn5XDySqdxa4k5AH8IuAMa89RVxDxgPNlosZxqkFKN5UmHXuYSw==",
|
||||
"dev": true
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
|
@ -875,6 +925,11 @@
|
|||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="
|
||||
},
|
||||
"nexline": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/nexline/-/nexline-1.2.2.tgz",
|
||||
|
@ -926,6 +981,11 @@
|
|||
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.0.2.tgz",
|
||||
"integrity": "sha512-sEmji9Yaq+Tw+STwsGAE56hf7gMy9p0tQfJojIAamB7WHJYJKf1qlsg9jqBWG8q9VCxKPhZaP/AcXwEoBcYQhQ=="
|
||||
},
|
||||
"parse-ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
|
||||
"integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA=="
|
||||
},
|
||||
"powahroot": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/powahroot/-/powahroot-1.1.1.tgz",
|
||||
|
@ -936,6 +996,14 @@
|
|||
"nightink": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"pretty-ms": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
|
||||
"integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==",
|
||||
"requires": {
|
||||
"parse-ms": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"retry": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||
|
|
|
@ -21,15 +21,18 @@
|
|||
"@ltd/j-toml": "^1.29.0",
|
||||
"applause-cli": "^1.8.0",
|
||||
"jpake": "^1.0.1",
|
||||
"mime": "^3.0.0",
|
||||
"nexline": "^1.2.2",
|
||||
"p-queue": "^7.2.0",
|
||||
"p-reflect": "^3.0.0",
|
||||
"p-retry": "^5.0.0",
|
||||
"powahroot": "^1.1.1",
|
||||
"pretty-ms": "^7.0.1",
|
||||
"systeminformation": "^5.11.2",
|
||||
"tweetnacl": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime": "^2.0.3",
|
||||
"esbuild": "^0.14.23",
|
||||
"fork-awesome": "^1.2.0"
|
||||
}
|
||||
|
|
|
@ -2,12 +2,31 @@
|
|||
|
||||
import ServerRouter from 'powahroot/Server.mjs';
|
||||
|
||||
import middleware_log from './routes/middleware_log.mjs';
|
||||
import middleware_catch_errors from './routes/middleware_errors.mjs';
|
||||
import route_table from './routes/table.mjs';
|
||||
import route_static from './routes/static.mjs';
|
||||
|
||||
export default function(sys) {
|
||||
const router = new ServerRouter();
|
||||
|
||||
router.on_all(middleware_catch_errors.bind(this, sys.config.verbose));
|
||||
router.on_all(middleware_log);
|
||||
|
||||
|
||||
///
|
||||
// API
|
||||
///
|
||||
router.get(`/api/table/:table_name`, route_table.bind(this, sys));
|
||||
|
||||
///
|
||||
// Web interface
|
||||
///
|
||||
router.get(`/`, async (ctx, next) => {
|
||||
ctx.params.filepath = `index.html`;
|
||||
await route_static(ctx, next);
|
||||
});
|
||||
router.get(`/static/::filepath`, route_static);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
|
37
src/lib/agent/subsystems/http/routes/middleware_errors.mjs
Normal file
37
src/lib/agent/subsystems/http/routes/middleware_errors.mjs
Normal file
|
@ -0,0 +1,37 @@
|
|||
"use strict";
|
||||
|
||||
import pretty_ms from 'pretty-ms';
|
||||
|
||||
import log from '../../../../io/NamespacedLog.mjs'; const l = log("http:request");
|
||||
|
||||
/**
|
||||
* Handles errors thrown by handlers further down the chain.
|
||||
* @param {RequestContext} context The RequestContext object.
|
||||
* @param {Function} next The function to call to invoke the next middleware item
|
||||
*/
|
||||
async function middleware_catch_errors(verbose, context, next) {
|
||||
try {
|
||||
await next();
|
||||
} catch(error) {
|
||||
handle_error(verbose, error, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a given error thrown by a given RequestContext.
|
||||
* @param {Error} error The error that was thrown.
|
||||
* @param {RequestContext} context The RequestContext from which the error was thrown.
|
||||
*/
|
||||
function handle_error(verbose, error, context) {
|
||||
l.info(`[?ms] [${context.request.method} 503] ${context.request.connection.remoteAddress} -> ${context.request.url}`);
|
||||
console.error(error.stack); // TODO: colourise this?
|
||||
// TODO: Send a better error page - perhaps with an error id that's uploaded to some remote system, or the prettified stack trace & offending code encrypted with a key?
|
||||
|
||||
if(!verbose)
|
||||
context.send.plain(503, "Error caught", "Oops! An error ocurred. Please report this to bugs@starbeamrainbowlabs.com");
|
||||
else
|
||||
context.send.plain(503, `*** Server Error ***\n${error.stack}\n`);
|
||||
|
||||
}
|
||||
|
||||
export default middleware_catch_errors;
|
15
src/lib/agent/subsystems/http/routes/middleware_log.mjs
Normal file
15
src/lib/agent/subsystems/http/routes/middleware_log.mjs
Normal file
|
@ -0,0 +1,15 @@
|
|||
"use strict";
|
||||
|
||||
import pretty_ms from 'pretty-ms';
|
||||
|
||||
import log from '../../../../io/NamespacedLog.mjs'; const l = log("http:request");
|
||||
|
||||
async function middleware_log_request(ctx, next) {
|
||||
let start = new Date();
|
||||
|
||||
await next();
|
||||
|
||||
l.info(`[${pretty_ms(new Date() - start)}] [${ctx.request.method} ${ctx.response.statusCode}] ${ctx.request.connection.remoteAddress} -> ${ctx.request.url}`);
|
||||
}
|
||||
|
||||
export default middleware_log_request;
|
50
src/lib/agent/subsystems/http/routes/static.mjs
Normal file
50
src/lib/agent/subsystems/http/routes/static.mjs
Normal file
|
@ -0,0 +1,50 @@
|
|||
"use strict";
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import mime from 'mime';
|
||||
|
||||
const __dirname = import.meta.url.slice(7, import.meta.url.lastIndexOf("/"));
|
||||
|
||||
const root_dir = path.resolve(__dirname, "../../../../../static-dist");
|
||||
|
||||
function make_path_safe(path) {
|
||||
return path.replace(/[^a-zA-Z0-9\-\._\/]/g, "")
|
||||
.replace(/\.+/g, ".");
|
||||
}
|
||||
|
||||
async function route_files(context, _next) {
|
||||
let safe_url = make_path_safe(context.params.filepath);
|
||||
let file_path = path.join(root_dir, safe_url);
|
||||
try {
|
||||
let info = await fs.promises.stat(file_path);
|
||||
if(info && info.isFile()) {
|
||||
context.response.writeHead(200, {
|
||||
"content-type": mime.getType(file_path),
|
||||
"content-length": info.size
|
||||
});
|
||||
fs.createReadStream(file_path)
|
||||
.pipe(context.response);
|
||||
}
|
||||
else {
|
||||
send_404(context, safe_url);
|
||||
return;
|
||||
}
|
||||
} catch(error) {
|
||||
if(error.code == "ENOENT") {
|
||||
send_404(context, safe_url);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function send_404(context, url) {
|
||||
context.send.plain(404, `Error: ${url} couldn't be found.`);
|
||||
}
|
||||
|
||||
export { route_files };
|
||||
export default route_files;
|
Loading…
Reference in a new issue