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