Browse Source

bring over initial version of static file server

master
Alan Faubert 3 years ago
parent
commit
12eed26d24
  1. 125
      sfs.js

125
sfs.js

@ -0,0 +1,125 @@
'use strict';
const fs = require('fs');
const http = require('http');
const mime = require('mime');
const readline = require('readline');
const util = require('util');
const readdir = util.promisify(fs.readdir);
const stat = util.promisify(fs.stat);
const sendError = (res, code, message, headers) => {
res.writeHead(code, {
'content-type': 'text/html; charset=utf-8',
...headers,
});
res.end(`<!doctype html>
<html>
<head>
<title>${code}: ${message}</title>
</head>
<body>
<h1>${code}: ${message}</h1>
</body>
</html>`);
};
const prefixes = ' KMGT';
const humanizeSize = size => {
let t = 0;
while (size >= 1024) {
size /= 1024;
t++;
}
return t ? `${size.toFixed(1)} ${prefixes[t]}B` : `${size} B`;
};
if (process.argv.length < 3 || process.argv.length > 4) {
console.error('Arguments: <port> [host]');
process.exit(1);
}
const port = +process.argv[2];
const host = process.argv[3] || 'localhost';
http
.createServer(async (req, res) => {
const path = '.' + decodeURIComponent(req.url);
if (!path.startsWith('./') || path.includes('/..') || path.includes('\\')) {
sendError(res, 403, 'Forbidden');
return;
}
let stats;
try {
stats = await stat(path);
} catch (e) {
sendError(res, 404, 'Not Found');
return;
}
if (stats.isFile()) {
let type = mime.getType(path) || 'application/octet-stream';
if (
type.startsWith('text/') ||
type === 'application/javascript' ||
type === 'application/json'
) {
type += '; charset=utf-8';
}
res.writeHead(200, {
'content-type': type,
'content-length': stats.size,
});
fs.createReadStream(path)
.on('error', () => sendError(res, 500, 'Internal Server Error'))
.pipe(res);
return;
}
if (stats.isDirectory()) {
if (!req.url.endsWith('/')) {
res.writeHead(302, { location: req.url + '/' });
res.end();
return;
}
const dir = (await Promise.all(
(await readdir(path)).map(async file => [
file,
await stat(path + file),
file.replace(/\d+/g, _ => '1'.repeat(_.length - 1) + '0' + _),
]),
)).sort(
(a, b) => +a[1].isFile() - +b[1].isFile() || a[2].localeCompare(b[2]),
);
if (path !== './') {
dir.unshift(['..', { isFile: () => false, isDirectory: () => true }]);
}
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
res.end(`<!doctype html>
<html>
<head>
<title>${req.headers.host + decodeURIComponent(req.url)}</title>
</head>
<body>
<h1>${req.headers.host + decodeURIComponent(req.url)}</h1>
<ul>${dir
.map(
([file, stats]) => `
<li><a href="${encodeURIComponent(file) +
(stats.isDirectory() ? '/' : '')}">${file.replace(/&/g, '&amp;')}</a>${
stats.isFile() ? ` (${humanizeSize(stats.size)})` : ''
}</li>`,
)
.join('')}
</ul>
</body>
</html>`);
return;
}
sendError(res, 500, 'Internal Server Error');
})
.listen(port, host);
console.log(`Serving on http://${host}:${port} - Press any key to stop`);
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
process.stdin.on('keypress', () => process.exit(0));
Loading…
Cancel
Save