2020-09-21 01:14:06 -04:00
|
|
|
import fs from "fs";
|
|
|
|
import { createServer, Server } from "http";
|
2020-10-22 20:01:57 -04:00
|
|
|
import { resolve as _resolve } from "path";
|
|
|
|
|
|
|
|
import { resolve } from "../path";
|
2020-09-21 01:14:06 -04:00
|
|
|
|
|
|
|
export function getMime(filename: string): string {
|
|
|
|
switch (filename.split('.').pop().toLowerCase()) {
|
|
|
|
case 'css': return 'text/css';
|
|
|
|
case 'doctree': return 'application/x-doctree';
|
|
|
|
case 'eot': return 'application/vnd.ms-fontobject';
|
|
|
|
case 'gif': return 'image/gif';
|
|
|
|
case 'html': return 'text/html';
|
2021-02-08 15:27:05 -05:00
|
|
|
case 'ico': return 'image/x-icon';
|
2020-09-21 01:14:06 -04:00
|
|
|
case 'js': return 'application/javascript';
|
|
|
|
case 'jpg': return 'image/jpeg';
|
|
|
|
case 'jpeg': return 'image/jpeg';
|
2020-10-22 20:01:57 -04:00
|
|
|
case 'json': return 'application/json';
|
2020-09-21 01:14:06 -04:00
|
|
|
case 'md': return 'text/markdown';
|
|
|
|
case 'pickle': return 'application/x-pickle';
|
|
|
|
case 'png': return 'image/png';
|
|
|
|
case 'svg': return 'image/svg+xml';
|
|
|
|
case 'ttf': return 'application/x-font-ttf';
|
|
|
|
case 'txt': return 'text/plain';
|
|
|
|
case 'woff': return 'application/font-woff';
|
|
|
|
}
|
|
|
|
console.log('NO MIME', filename);
|
|
|
|
|
|
|
|
return "application/octet-stream";
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Options = {
|
|
|
|
port?: number;
|
|
|
|
redirects?: Record<string, string>;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function start(root: string, options: Options): Server {
|
|
|
|
if (root == null) { throw new Error("root required"); }
|
|
|
|
if (options == null) { options = { }; }
|
|
|
|
if (options.port == null) { options.port = 8000; }
|
2020-10-22 20:01:57 -04:00
|
|
|
root = _resolve(root);
|
2020-09-21 01:14:06 -04:00
|
|
|
|
|
|
|
const server = createServer((req, resp) => {
|
2020-10-22 20:01:57 -04:00
|
|
|
const url = req.url.split("?")[0];
|
2020-09-21 01:14:06 -04:00
|
|
|
|
|
|
|
// Follow redirects in options
|
2020-10-22 20:01:57 -04:00
|
|
|
if (options.redirects && options.redirects[url]) {
|
|
|
|
resp.writeHead(301, { Location: options.redirects[url] });
|
2020-09-21 01:14:06 -04:00
|
|
|
resp.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-22 20:01:57 -04:00
|
|
|
let filename = _resolve(root, "." + url);
|
2020-09-21 01:14:06 -04:00
|
|
|
|
|
|
|
// Make sure we aren't crawling out of our sandbox
|
2020-10-22 20:01:57 -04:00
|
|
|
if (url[0] !== "/" || filename.substring(0, filename.length) !== filename) {
|
2020-09-21 01:14:06 -04:00
|
|
|
resp.writeHead(403);
|
|
|
|
resp.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const stat = fs.statSync(filename);
|
|
|
|
if (stat.isDirectory()) {
|
|
|
|
// Redirect bare directory to its path (i.e. "/foo" => "/foo/")
|
2020-10-22 20:01:57 -04:00
|
|
|
if (url[url.length - 1] !== "/") {
|
|
|
|
resp.writeHead(301, { Location: url + "/" });
|
2020-09-21 01:14:06 -04:00
|
|
|
resp.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
filename += "/index.html";
|
|
|
|
}
|
|
|
|
|
|
|
|
const content = fs.readFileSync(filename);
|
|
|
|
|
|
|
|
resp.writeHead(200, {
|
|
|
|
"Content-Length": content.length,
|
|
|
|
"Content-Type": getMime(filename)
|
|
|
|
});
|
|
|
|
resp.end(content);
|
|
|
|
return;
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
if (error.code === "ENOENT") {
|
|
|
|
resp.writeHead(404, { });
|
|
|
|
resp.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.writeHead(500, { });
|
|
|
|
resp.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(options.port, () => {
|
|
|
|
console.log(`Server running on: http://localhost:${ options.port }`);
|
|
|
|
});
|
|
|
|
|
|
|
|
return server;
|
|
|
|
}
|
|
|
|
|
2020-10-22 20:01:57 -04:00
|
|
|
start(resolve("docs"), {
|
2020-09-21 01:14:06 -04:00
|
|
|
redirects: {
|
|
|
|
"/": "/v5/"
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|