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.
Files changed (2) hide show
  1. package/index.mjs +113 -13
  2. 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 size = (await filehandle.stat()).size;
1351
- const body = size > this.#maxChunkSize ? (fragmentRequest => this.#getBodyStream(fragmentRequest)) : await filehandle.readFile();
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 ?? mimeTypes[this.#filePath.split('.').at(-1).toLowerCase()] ?? 'application/octet-stream',
1357
- 'Content-Length': size,
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, staticFileDirectory = null, handleNotFoundError = null, handleServerError = null) {
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, staticFileDirectory, handleNotFoundError);
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, staticFileDirectory, handleNotFoundError) {
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
- if (staticFileDirectory && method === 'GET' && path.startsWith(staticFileDirectory)) {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minimalistic-server",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "engines" : {
5
5
  "npm" : ">=8.6.0",
6
6
  "node" : ">=22.0.0"