Serve static files
This commit is contained in:
parent
b0fd916eb4
commit
d5a9668e17
6 changed files with 1156 additions and 964 deletions
1994
package-lock.json
generated
1994
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -5,7 +5,7 @@
|
||||||
"main": "src/index.mjs",
|
"main": "src/index.mjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"No tests have been implemented yet\"",
|
"test": "echo \"No tests have been implemented yet\"",
|
||||||
"build": "node src/static/esbuild.mjs"
|
"build": "node src/static/esbuild.mjs"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -21,15 +21,18 @@
|
||||||
"@ltd/j-toml": "^1.29.0",
|
"@ltd/j-toml": "^1.29.0",
|
||||||
"applause-cli": "^1.8.0",
|
"applause-cli": "^1.8.0",
|
||||||
"jpake": "^1.0.1",
|
"jpake": "^1.0.1",
|
||||||
|
"mime": "^3.0.0",
|
||||||
"nexline": "^1.2.2",
|
"nexline": "^1.2.2",
|
||||||
"p-queue": "^7.2.0",
|
"p-queue": "^7.2.0",
|
||||||
"p-reflect": "^3.0.0",
|
"p-reflect": "^3.0.0",
|
||||||
"p-retry": "^5.0.0",
|
"p-retry": "^5.0.0",
|
||||||
"powahroot": "^1.1.1",
|
"powahroot": "^1.1.1",
|
||||||
|
"pretty-ms": "^7.0.1",
|
||||||
"systeminformation": "^5.11.2",
|
"systeminformation": "^5.11.2",
|
||||||
"tweetnacl": "^1.0.3"
|
"tweetnacl": "^1.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/mime": "^2.0.3",
|
||||||
"esbuild": "^0.14.23",
|
"esbuild": "^0.14.23",
|
||||||
"fork-awesome": "^1.2.0"
|
"fork-awesome": "^1.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,31 @@
|
||||||
|
|
||||||
import ServerRouter from 'powahroot/Server.mjs';
|
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_table from './routes/table.mjs';
|
||||||
|
import route_static from './routes/static.mjs';
|
||||||
|
|
||||||
export default function(sys) {
|
export default function(sys) {
|
||||||
const router = new ServerRouter();
|
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));
|
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;
|
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