153 lines
3.9 KiB
TypeScript
153 lines
3.9 KiB
TypeScript
// Adapted from https://deno.land/std@0.106.0/http/file_server.ts
|
|
import {
|
|
listenAndServe,
|
|
posix,
|
|
readAll,
|
|
serveFile,
|
|
ServerRequest,
|
|
ServerResponse,
|
|
Sys,
|
|
} from "../deps.ts";
|
|
|
|
export function runDevServer(): void {
|
|
const target = posix.resolve("./web/");
|
|
|
|
const handler = async (req: ServerRequest) => {
|
|
let response: ServerResponse | undefined;
|
|
try {
|
|
const normalizedUrl = normalizeURL(req.url);
|
|
let fsPath = posix.join(target, normalizedUrl);
|
|
if (fsPath.indexOf(target) !== 0) {
|
|
fsPath = target;
|
|
}
|
|
|
|
const srcPath = fsPath.replace(/\/web\/(.*)\.js$/, "/$1.ts");
|
|
if (srcPath != fsPath) {
|
|
// TODO check only local
|
|
await bundle(srcPath, fsPath);
|
|
}
|
|
|
|
const fileInfo = await Deno.stat(fsPath);
|
|
if (fileInfo.isDirectory) {
|
|
response = await serveFile(req, posix.join(fsPath, "index.html"));
|
|
} else {
|
|
response = await serveFile(req, fsPath);
|
|
}
|
|
} catch (e) {
|
|
console.error(e.message);
|
|
response = await serveFallback(req, e);
|
|
} finally {
|
|
serverLog(req, response!);
|
|
try {
|
|
await req.respond(response!);
|
|
} catch (e) {
|
|
console.error(e.message);
|
|
}
|
|
}
|
|
};
|
|
|
|
const port = 4507;
|
|
const host = "0.0.0.0";
|
|
const addr = `${host}:${port}`;
|
|
|
|
listenAndServe(addr, handler);
|
|
console.log(`Dev server listening on http://${addr}/`);
|
|
}
|
|
|
|
export function normalizeURL(url: string): string {
|
|
let normalizedUrl = url;
|
|
try {
|
|
normalizedUrl = decodeURI(normalizedUrl);
|
|
} catch (e) {
|
|
if (!(e instanceof URIError)) {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
try {
|
|
//allowed per https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
|
|
const absoluteURI = new URL(normalizedUrl);
|
|
normalizedUrl = absoluteURI.pathname;
|
|
} catch (e) { //wasn't an absoluteURI
|
|
if (!(e instanceof TypeError)) {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
if (normalizedUrl[0] !== "/") {
|
|
throw new URIError("The request URI is malformed.");
|
|
}
|
|
|
|
normalizedUrl = posix.normalize(normalizedUrl);
|
|
const startOfParams = normalizedUrl.indexOf("?");
|
|
return startOfParams > -1
|
|
? normalizedUrl.slice(0, startOfParams)
|
|
: normalizedUrl;
|
|
}
|
|
|
|
const encoder = new TextEncoder();
|
|
function serveFallback(_req: ServerRequest, e: Error): Promise<ServerResponse> {
|
|
if (e instanceof URIError) {
|
|
return Promise.resolve({
|
|
status: 400,
|
|
body: encoder.encode("Bad Request"),
|
|
});
|
|
} else if (e instanceof Deno.errors.NotFound) {
|
|
return Promise.resolve({
|
|
status: 404,
|
|
body: encoder.encode("Not Found"),
|
|
});
|
|
} else {
|
|
return Promise.resolve({
|
|
status: 500,
|
|
body: encoder.encode("Internal server error"),
|
|
});
|
|
}
|
|
}
|
|
|
|
function serverLog(req: ServerRequest, res: ServerResponse): void {
|
|
const d = new Date().toISOString();
|
|
const dateFmt = `[${d.slice(0, 10)} ${d.slice(11, 19)}]`;
|
|
const s = `${dateFmt} "${req.method} ${req.url} ${req.proto}" ${res.status}`;
|
|
console.log(s);
|
|
}
|
|
|
|
async function run(...cmd: string[]): Promise<boolean> {
|
|
const process = Sys.run({ cmd });
|
|
const result = await process.status();
|
|
return result.success;
|
|
}
|
|
|
|
async function deno(cmd: string, ...args: string[]): Promise<boolean> {
|
|
const flags = await readConfigFlags(cmd);
|
|
return await run("deno", cmd, ...flags, ...args);
|
|
}
|
|
|
|
async function readConfigFlags(cmd: string): Promise<string[]> {
|
|
const path = `config/${cmd}.flags`;
|
|
try {
|
|
const f = await Sys.open(path);
|
|
const content = await readAll(f);
|
|
return new TextDecoder()
|
|
.decode(content)
|
|
.split(" ")
|
|
.filter((part) => part && part.startsWith("--"));
|
|
} catch {
|
|
}
|
|
return [];
|
|
}
|
|
|
|
async function bundle(
|
|
source: string,
|
|
destination: string,
|
|
): Promise<void> {
|
|
if (
|
|
await deno("bundle", "-q", source, destination) &&
|
|
await deno("fmt", "-q", destination)
|
|
) {
|
|
console.log(`Bundled : ${source} -> ${destination}`);
|
|
} else {
|
|
console.error(`Bundle failed for: ${source}`);
|
|
}
|
|
}
|