minimalistic-server 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.mjs +113 -13
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
2
|
import * as querystring from "node:querystring";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
|
+
import { start } from 'node:repl';
|
|
5
|
+
import { type } from 'node:os';
|
|
4
6
|
|
|
5
7
|
const servers = new Map();
|
|
6
8
|
const keepAliveTimeout = 20000;
|
|
@@ -1194,6 +1196,7 @@ export class FileResponse extends Response {
|
|
|
1194
1196
|
#maxChunkSize = 4 * 1024 * 1024;
|
|
1195
1197
|
#fragmentRequestMap = new WeakMap();
|
|
1196
1198
|
#makeNotFoundResponse = null;
|
|
1199
|
+
#urlPathForDirectory = null;
|
|
1197
1200
|
|
|
1198
1201
|
constructor(filePath, code = 200, contentType = null, cookies = null) {
|
|
1199
1202
|
super();
|
|
@@ -1255,6 +1258,11 @@ export class FileResponse extends Response {
|
|
|
1255
1258
|
this.#dataPromise = null;
|
|
1256
1259
|
}
|
|
1257
1260
|
|
|
1261
|
+
setUrlPathForDirectory(urlPathForDirectory) {
|
|
1262
|
+
this.#urlPathForDirectory = urlPathForDirectory;
|
|
1263
|
+
this.#dataPromise = null;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1258
1266
|
async #getFragmentRequest(headers) {
|
|
1259
1267
|
let result = this.#fragmentRequestMap.get(headers);
|
|
1260
1268
|
|
|
@@ -1314,6 +1322,45 @@ export class FileResponse extends Response {
|
|
|
1314
1322
|
return result ?? null;
|
|
1315
1323
|
}
|
|
1316
1324
|
|
|
1325
|
+
async * #getDirectoryStream() {
|
|
1326
|
+
const data = await this.#retreiveData();
|
|
1327
|
+
|
|
1328
|
+
if (typeof data.body !== 'function') {
|
|
1329
|
+
yield data.body;
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const urlPath = this.#urlPathForDirectory;
|
|
1334
|
+
const parentUrlPath = urlPath.split('/').filter((x, i, arr) => x && i !== arr.length - 1).join('/');
|
|
1335
|
+
const filePath = this.#filePath;
|
|
1336
|
+
|
|
1337
|
+
yield `<!DOCTYPE html>
|
|
1338
|
+
<html lang="en">
|
|
1339
|
+
<head>
|
|
1340
|
+
<title>${escapeHtml(urlPath)}</title>
|
|
1341
|
+
</head>
|
|
1342
|
+
<body>
|
|
1343
|
+
${urlPath ? `<a href="/${parentUrlPath}">Up</a><hr>` : ''}
|
|
1344
|
+
`;
|
|
1345
|
+
|
|
1346
|
+
const files = await fs.readdir(filePath);
|
|
1347
|
+
let counter = 0;
|
|
1348
|
+
|
|
1349
|
+
for (const file of files) {
|
|
1350
|
+
if (counter === 100) {
|
|
1351
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
++counter;
|
|
1355
|
+
|
|
1356
|
+
yield `<a href="/${urlPath}/${encodeURIComponent(file)}">${escapeHtml(file)}</a><br>`;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
yield `</body>`
|
|
1361
|
+
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1317
1364
|
async * #getBodyStream(fragmentRequest) {
|
|
1318
1365
|
const data = await this.#retreiveData();
|
|
1319
1366
|
|
|
@@ -1347,14 +1394,23 @@ export class FileResponse extends Response {
|
|
|
1347
1394
|
|
|
1348
1395
|
try {
|
|
1349
1396
|
filehandle = await fs.open(this.#filePath, 'r');
|
|
1350
|
-
const
|
|
1351
|
-
const
|
|
1397
|
+
const stat = await filehandle.stat();
|
|
1398
|
+
const size = stat.size;
|
|
1399
|
+
const readAsDirectory = stat.isDirectory() && this.#urlPathForDirectory !== null;
|
|
1400
|
+
const body = size > this.#maxChunkSize ? (fragmentRequest => this.#getBodyStream(fragmentRequest)) : (readAsDirectory ? (() => this.#getDirectoryStream()) : await filehandle.readFile());
|
|
1401
|
+
|
|
1402
|
+
if (readAsDirectory) {
|
|
1403
|
+
this.addCustomHeaders({ 'Cache-Control': 'no-store, no-cache, must-revalidate' });
|
|
1404
|
+
}
|
|
1352
1405
|
|
|
1353
1406
|
resolve({
|
|
1354
1407
|
code: this.#code,
|
|
1355
1408
|
headers: this.getMergedWithOtherHeaders({
|
|
1356
|
-
'Content-Type': this.#contentType ??
|
|
1357
|
-
|
|
1409
|
+
'Content-Type': this.#contentType ??
|
|
1410
|
+
(readAsDirectory ? 'text/html; charset=utf-8' : null) ??
|
|
1411
|
+
mimeTypes[this.#filePath.split('.').at(-1).toLowerCase()] ??
|
|
1412
|
+
'application/octet-stream',
|
|
1413
|
+
'Content-Length': readAsDirectory ? null : size,
|
|
1358
1414
|
}
|
|
1359
1415
|
),
|
|
1360
1416
|
body,
|
|
@@ -1494,21 +1550,19 @@ export class RedirectResponse extends Response {
|
|
|
1494
1550
|
}
|
|
1495
1551
|
}
|
|
1496
1552
|
|
|
1497
|
-
export function serve(routes, port = 80,
|
|
1553
|
+
export function serve(routes, port = 80, staticFileDirectoryOrDirectories = null, handleNotFoundError = null, handleServerError = null) {
|
|
1498
1554
|
port = +port;
|
|
1499
1555
|
routes = normalizeRoutes(routes, handleServerError);
|
|
1556
|
+
staticFileDirectoryOrDirectories = normalizeStaticFileDirectories(staticFileDirectoryOrDirectories);
|
|
1500
1557
|
safePrint(routes);
|
|
1501
|
-
|
|
1502
|
-
if (staticFileDirectory !== null && staticFileDirectory !== undefined) {
|
|
1503
|
-
staticFileDirectory = `${staticFileDirectory}`.split('/').filter(x => x).join('/');
|
|
1504
|
-
}
|
|
1558
|
+
safePrint(staticFileDirectoryOrDirectories);
|
|
1505
1559
|
|
|
1506
1560
|
try {
|
|
1507
1561
|
unserve(port);
|
|
1508
1562
|
|
|
1509
1563
|
const server = http.createServer(async (req, res) => {
|
|
1510
1564
|
try {
|
|
1511
|
-
const [code, headers, body] = await handleRequest(req, routes,
|
|
1565
|
+
const [code, headers, body] = await handleRequest(req, routes, staticFileDirectoryOrDirectories, handleNotFoundError);
|
|
1512
1566
|
res.writeHead(code, headers);
|
|
1513
1567
|
|
|
1514
1568
|
if (typeof body === 'function') {
|
|
@@ -1573,6 +1627,39 @@ export function unserve(port = 80) {
|
|
|
1573
1627
|
servers.delete(port);
|
|
1574
1628
|
}
|
|
1575
1629
|
|
|
1630
|
+
function normalizeStaticFileDirectories(staticFileDirectoryOrDirectories) {
|
|
1631
|
+
if (staticFileDirectoryOrDirectories !== null && staticFileDirectoryOrDirectories !== undefined) {
|
|
1632
|
+
if (!Array.isArray(staticFileDirectoryOrDirectories)) {
|
|
1633
|
+
staticFileDirectoryOrDirectories = [staticFileDirectoryOrDirectories];
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
staticFileDirectoryOrDirectories = staticFileDirectoryOrDirectories.filter(x => x !== null && typeof x === 'object' || typeof x === 'string').map(x => {
|
|
1637
|
+
let serverFilePath = '', urlPath = '', showWholeDirectory = false;;
|
|
1638
|
+
|
|
1639
|
+
if (typeof x === 'string') {
|
|
1640
|
+
serverFilePath = urlPath = x;
|
|
1641
|
+
showWholeDirectory = false;
|
|
1642
|
+
} else {
|
|
1643
|
+
({ serverPath: serverFilePath, urlPath, showWholeDirectory } = x);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
urlPath = `${urlPath}`.split('/').filter(x => x).join('/');
|
|
1647
|
+
serverFilePath = `${serverFilePath}`.split('/').filter(x => x).join('/');
|
|
1648
|
+
showWholeDirectory = !!showWholeDirectory;
|
|
1649
|
+
|
|
1650
|
+
return {
|
|
1651
|
+
urlPath,
|
|
1652
|
+
serverFilePath,
|
|
1653
|
+
showWholeDirectory,
|
|
1654
|
+
};
|
|
1655
|
+
});
|
|
1656
|
+
} else {
|
|
1657
|
+
staticFileDirectoryOrDirectories = [];
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
return staticFileDirectoryOrDirectories;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1576
1663
|
function normalizeRoutes(routes, handleServerError) {
|
|
1577
1664
|
const flatten = {};
|
|
1578
1665
|
|
|
@@ -1698,7 +1785,7 @@ export function clearStaticCache(path = null) {
|
|
|
1698
1785
|
}
|
|
1699
1786
|
}
|
|
1700
1787
|
|
|
1701
|
-
async function handleRequest(req, routes,
|
|
1788
|
+
async function handleRequest(req, routes, staticFileDirectories, handleNotFoundError) {
|
|
1702
1789
|
let response = new JsonResponse({
|
|
1703
1790
|
message: 'Invalid data'
|
|
1704
1791
|
}, 400);
|
|
@@ -1716,9 +1803,11 @@ async function handleRequest(req, routes, staticFileDirectory, handleNotFoundErr
|
|
|
1716
1803
|
let routeHandler = routes;
|
|
1717
1804
|
let handleOptions = false;
|
|
1718
1805
|
|
|
1719
|
-
|
|
1806
|
+
const staticFileOrDirectory = method === 'GET' ? staticFileDirectories.find(x => x.urlPath === path || path.startsWith(x.urlPath + '/')) : null;
|
|
1807
|
+
|
|
1808
|
+
if (staticFileOrDirectory) {
|
|
1720
1809
|
routeHandler = () => {
|
|
1721
|
-
const filePath = decodeURI(path);
|
|
1810
|
+
const filePath = decodeURI(path).replace(staticFileOrDirectory.urlPath, staticFileOrDirectory.serverFilePath);
|
|
1722
1811
|
|
|
1723
1812
|
let resp = staticCache.get(filePath);
|
|
1724
1813
|
|
|
@@ -1732,7 +1821,18 @@ async function handleRequest(req, routes, staticFileDirectory, handleNotFoundErr
|
|
|
1732
1821
|
resp.setNotFoundErrorCustomResponseHandler(() => handleNotFoundError(request));
|
|
1733
1822
|
}
|
|
1734
1823
|
|
|
1824
|
+
if (staticFileOrDirectory.showWholeDirectory) {
|
|
1825
|
+
resp.setUrlPathForDirectory(path);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1735
1828
|
staticCache.set(filePath, resp);
|
|
1829
|
+
|
|
1830
|
+
if (staticCache.size > 500) {
|
|
1831
|
+
for (const k of staticCache.keys()) {
|
|
1832
|
+
staticCache.delete(k);
|
|
1833
|
+
break;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1736
1836
|
}
|
|
1737
1837
|
|
|
1738
1838
|
return resp;
|