@e22m4u/js-http-static-router 0.0.1 → 0.1.0

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/README.md CHANGED
@@ -29,42 +29,48 @@ import http from 'http';
29
29
  import {HttpStaticRouter} from '@e22m4u/js-http-static-router';
30
30
 
31
31
  // создание экземпляра маршрутизатора
32
- const staticRouter = new HttpStaticRouter();
33
-
34
- // определение директории "../static"
35
- // доступной по адресу "/static"
36
- staticRouter.addRoute(
37
- '/static', // путь маршрута
38
- `${import.meta.dirname}/../static`, // файловый путь
39
- );
40
-
41
- // объявление файла "./static/file.txt"
42
- // доступным по адресу "/static"
43
- staticRouter.addRoute(
44
- '/file.txt',
45
- `${import.meta.dirname}/static/file.txt`,
46
- );
47
-
48
- // создание HTTP сервера и подключение обработчика
32
+ const staticRouter = new HttpStaticRouter({
33
+ // при использовании опции "rootDir", относительные пути
34
+ // в регистрируемых маршрутах будут разрешаться относительно
35
+ // указанного адреса файловой системы
36
+ rootDir: import.meta.dirname,
37
+ // в данном случае "rootDir" указывает
38
+ // на путь к директории текущего модуля
39
+ });
40
+ // доступ к import.meta.dirname возможен
41
+ // только для ESM начиная с Node.js 20.11.0
42
+
43
+ // экспозиция содержимого директории "/static"
44
+ // доступным по адресу "/assets/{file_name}"
45
+ staticRouter.defineRoute({
46
+ remotePath: '/assets', // путь маршрута
47
+ resourcePath: '../static', // файловый путь
48
+ });
49
+
50
+ // объявление файла "./index.html"
51
+ // доступным по адресу "/home"
52
+ staticRouter.defineRoute({
53
+ remotePath: '/home',
54
+ resourcePath: './static/index.html',
55
+ });
56
+
57
+ // создание HTTP сервера и определение
58
+ // функции для обработки запросов
49
59
  const server = new http.Server();
50
- server.on('request', (req, res) => {
51
- // если статический маршрут найден,
52
- // выполняется поиск и отдача файла
53
- const staticRoute = staticRouter.matchRoute(req);
54
- if (staticRoute) {
55
- return staticRouter.sendFileByRoute(staticRoute, req, res);
60
+ server.on('request', async (req, res) => {
61
+ const fileSent = await staticRouter.handleRequest(req, res);
62
+ if (!fileSent) {
63
+ res.writeHead(404, {'Content-Type': 'text/plain'});
64
+ res.write('404 Not Found');
65
+ res.end();
56
66
  }
57
- // в противном случае запрос обрабатывается
58
- // основной логикой приложения
59
- res.writeHead(200, {'Content-Type': 'text/plain'});
60
- res.end('Hello from App!');
61
67
  });
62
68
 
63
69
  server.listen(3000, () => {
64
70
  console.log('Server is running on http://localhost:3000');
65
71
  console.log('Try to open:');
66
- console.log('http://localhost:3000/static/');
67
- console.log('http://localhost:3000/file.txt');
72
+ console.log('http://localhost:3000/home');
73
+ console.log('http://localhost:3000/assets/file.txt');
68
74
  });
69
75
  ```
70
76
 
@@ -31,14 +31,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.js
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
- HttpStaticRouter: () => HttpStaticRouter
34
+ HttpStaticRouter: () => HttpStaticRouter,
35
+ StaticRoute: () => StaticRoute
35
36
  });
36
37
  module.exports = __toCommonJS(index_exports);
37
38
 
38
- // src/http-static-router.js
39
- var import_path = __toESM(require("path"), 1);
40
- var import_mime_types = __toESM(require("mime-types"), 1);
39
+ // src/static-route.js
41
40
  var import_fs = __toESM(require("fs"), 1);
41
+ var import_path = __toESM(require("path"), 1);
42
42
 
43
43
  // src/utils/escape-regexp.js
44
44
  function escapeRegexp(input) {
@@ -46,173 +46,363 @@ function escapeRegexp(input) {
46
46
  }
47
47
  __name(escapeRegexp, "escapeRegexp");
48
48
 
49
- // src/utils/normalize-path.js
50
- function normalizePath(value, noStartingSlash = false) {
51
- if (typeof value !== "string") {
52
- return "/";
49
+ // src/static-route.js
50
+ var import_js_format = require("@e22m4u/js-format");
51
+ var _StaticRoute = class _StaticRoute {
52
+ /**
53
+ * Remote path.
54
+ *
55
+ * @type {string}
56
+ */
57
+ remotePath;
58
+ /**
59
+ * Resource path.
60
+ *
61
+ * @type {string}
62
+ */
63
+ resourcePath;
64
+ /**
65
+ * RegExp.
66
+ *
67
+ * @type {RegExp}
68
+ */
69
+ regexp;
70
+ /**
71
+ * Is file.
72
+ *
73
+ * @type {boolean}
74
+ */
75
+ isFile;
76
+ /**
77
+ * Constructor.
78
+ *
79
+ * @param {string} routeDef
80
+ */
81
+ constructor(routeDef) {
82
+ if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef)) {
83
+ throw new import_js_format.InvalidArgumentError(
84
+ 'Parameter "routeDef" must be an Object, but %v was given.',
85
+ routeDef
86
+ );
87
+ }
88
+ if (typeof routeDef.remotePath !== "string") {
89
+ throw new import_js_format.InvalidArgumentError(
90
+ 'Option "remotePath" must be a String, but %v was given.',
91
+ routeDef.remotePath
92
+ );
93
+ }
94
+ if (!routeDef.remotePath.startsWith("/")) {
95
+ throw new import_js_format.InvalidArgumentError(
96
+ 'Option "remotePath" must starts with "/", but %v was given.',
97
+ routeDef.remotePath
98
+ );
99
+ }
100
+ if (typeof routeDef.resourcePath !== "string") {
101
+ throw new import_js_format.InvalidArgumentError(
102
+ 'Option "resourcePath" must be a String, but %v was given.',
103
+ routeDef.resourcePath
104
+ );
105
+ }
106
+ const resourcePath = import_path.default.resolve(routeDef.resourcePath);
107
+ let stats;
108
+ try {
109
+ stats = import_fs.default.statSync(resourcePath);
110
+ } catch (error) {
111
+ console.error(error);
112
+ throw new import_js_format.InvalidArgumentError(
113
+ "Resource path %v does not exist.",
114
+ resourcePath
115
+ );
116
+ }
117
+ const isFile = stats.isFile();
118
+ const escapedRemotePath = escapeRegexp(routeDef.remotePath);
119
+ const regexp = isFile ? new RegExp(`^${escapedRemotePath}$`) : new RegExp(`^${escapedRemotePath}(?:$|\\/)`);
120
+ this.remotePath = routeDef.remotePath;
121
+ this.resourcePath = resourcePath;
122
+ this.regexp = regexp;
123
+ this.isFile = isFile;
124
+ }
125
+ };
126
+ __name(_StaticRoute, "StaticRoute");
127
+ var StaticRoute = _StaticRoute;
128
+
129
+ // src/http-static-router.js
130
+ var import_path2 = __toESM(require("path"), 1);
131
+ var import_mime_types = __toESM(require("mime-types"), 1);
132
+ var import_http = require("http");
133
+ var import_fs2 = __toESM(require("fs"), 1);
134
+
135
+ // src/utils/get-pathname-from-url.js
136
+ var import_js_format2 = require("@e22m4u/js-format");
137
+ var HOST_RE = /^https?:\/\/[^/]+/;
138
+ var QUERY_STRING_RE = /\?.*$/;
139
+ function getPathnameFromUrl(url) {
140
+ if (typeof url !== "string") {
141
+ throw new import_js_format2.InvalidArgumentError(
142
+ 'Parameter "url" must be a String, but %v was given.',
143
+ url
144
+ );
53
145
  }
54
- const res = value.trim().replace(/\/+/g, "/").replace(/(^\/|\/$)/g, "");
55
- return noStartingSlash ? res : "/" + res;
146
+ return url.replace(HOST_RE, "").replace(QUERY_STRING_RE, "");
56
147
  }
57
- __name(normalizePath, "normalizePath");
148
+ __name(getPathnameFromUrl, "getPathnameFromUrl");
58
149
 
59
150
  // src/http-static-router.js
151
+ var import_js_format3 = require("@e22m4u/js-format");
60
152
  var import_js_service = require("@e22m4u/js-service");
61
- var import_js_format = require("@e22m4u/js-format");
62
153
  var _HttpStaticRouter = class _HttpStaticRouter extends import_js_service.DebuggableService {
63
154
  /**
64
155
  * Routes.
65
156
  *
66
157
  * @protected
158
+ * @type {StaticRoute[]}
67
159
  */
68
160
  _routes = [];
161
+ /**
162
+ * Options.
163
+ *
164
+ * @type {import('./http-static-router.js').HttpStaticRouterOptions}
165
+ * @protected
166
+ */
167
+ _options = {};
69
168
  /**
70
169
  * Constructor.
71
170
  *
72
- * @param {import('@e22m4u/js-service').ServiceContainer} container
171
+ * @param {import('@e22m4u/js-service').ServiceContainer|import('./http-static-router.js').HttpStaticRouterOptions} containerOrOptions
172
+ * @param {import('./http-static-router.js').HttpStaticRouterOptions} options
73
173
  */
74
- constructor(container) {
75
- super(container, {
174
+ constructor(containerOrOptions, options) {
175
+ const debugOptions = {
76
176
  noEnvironmentNamespace: true,
77
177
  namespace: "jsHttpStaticRouter"
78
- });
178
+ };
179
+ if ((0, import_js_service.isServiceContainer)(containerOrOptions)) {
180
+ super(containerOrOptions, debugOptions);
181
+ } else if (containerOrOptions !== void 0) {
182
+ if (!containerOrOptions || typeof containerOrOptions !== "object" || Array.isArray(containerOrOptions)) {
183
+ throw new import_js_format3.InvalidArgumentError(
184
+ 'Parameter "containerOrOptions" must be an Object or an instance of ServiceContainer, but %v was given.',
185
+ options
186
+ );
187
+ }
188
+ super(void 0, debugOptions);
189
+ if (options === void 0) {
190
+ options = containerOrOptions;
191
+ containerOrOptions = void 0;
192
+ }
193
+ } else {
194
+ super(void 0, debugOptions);
195
+ }
196
+ if (options !== void 0) {
197
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
198
+ throw new import_js_format3.InvalidArgumentError(
199
+ 'Parameter "options" must be an Object, but %v was given.',
200
+ options
201
+ );
202
+ }
203
+ if (options.rootDir !== void 0) {
204
+ if (typeof options.rootDir !== "string") {
205
+ throw new import_js_format3.InvalidArgumentError(
206
+ 'Option "rootDir" must be a String, but %v was given.',
207
+ options.rootDir
208
+ );
209
+ }
210
+ if (!import_path2.default.isAbsolute(options.rootDir)) {
211
+ throw new import_js_format3.InvalidArgumentError(
212
+ 'Option "rootDir" must be an absolute path, but %v was given.',
213
+ options.rootDir
214
+ );
215
+ }
216
+ }
217
+ this._options = { ...options };
218
+ }
219
+ Object.freeze(this._options);
79
220
  }
80
221
  /**
81
- * Add route.
222
+ * Define route.
82
223
  *
83
- * @param {string} remotePath
84
- * @param {string} resourcePath
85
- * @returns {object}
224
+ * @param {import('./static-route.js').StaticRouteDefinition} routeDef
225
+ * @returns {this}
86
226
  */
87
- addRoute(remotePath, resourcePath) {
88
- const debug = this.getDebuggerFor(this.addRoute);
89
- resourcePath = import_path.default.resolve(resourcePath);
90
- debug("Adding a new route.");
91
- debug("Resource path is %v.", resourcePath);
92
- debug("Remote path is %v.", remotePath);
93
- let stats;
94
- try {
95
- stats = import_fs.default.statSync(resourcePath);
96
- } catch (error) {
97
- console.error(error);
98
- throw new import_js_format.InvalidArgumentError(
99
- "Static resource path does not exist %v.",
100
- resourcePath
227
+ defineRoute(routeDef) {
228
+ if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef)) {
229
+ throw new import_js_format3.InvalidArgumentError(
230
+ 'Parameter "routeDef" must be an Object, but %v was given.',
231
+ routeDef
101
232
  );
102
233
  }
103
- const isFile = stats.isFile();
104
- debug("Resource type is %s.", isFile ? "File" : "Folder");
105
- const normalizedRemotePath = normalizePath(remotePath);
106
- const escapedRemotePath = escapeRegexp(normalizedRemotePath);
107
- const regexp = isFile ? new RegExp(`^${escapedRemotePath}$`) : new RegExp(`^${escapedRemotePath}(?:$|\\/)`);
108
- const route = { remotePath, resourcePath, regexp, isFile };
234
+ if (this._options.rootDir !== void 0 && !import_path2.default.isAbsolute(routeDef.resourcePath)) {
235
+ routeDef = { ...routeDef };
236
+ routeDef.resourcePath = import_path2.default.join(
237
+ this._options.rootDir,
238
+ routeDef.resourcePath
239
+ );
240
+ }
241
+ const debug = this.getDebuggerFor(this.defineRoute);
242
+ const route = new StaticRoute(routeDef);
243
+ debug("Adding a new route.");
244
+ debug("Resource path is %v.", route.resourcePath);
245
+ debug("Remote path is %v.", route.remotePath);
246
+ debug("Resource type is %s.", route.isFile ? "File" : "Folder");
109
247
  this._routes.push(route);
110
248
  this._routes.sort((a, b) => b.remotePath.length - a.remotePath.length);
111
249
  return this;
112
250
  }
113
251
  /**
114
- * Match route.
252
+ * Handle request.
115
253
  *
116
- * @param {import('http').IncomingMessage} req
117
- * @returns {object|undefined}
254
+ * @param {import('http').IncomingMessage} request
255
+ * @param {import('http').ServerResponse} response
256
+ * @returns {Promise<boolean>}
118
257
  */
119
- matchRoute(req) {
120
- const debug = this.getDebuggerFor(this.matchRoute);
121
- debug("Matching routes with incoming request.");
122
- const url = (req.url || "/").replace(/\?.*$/, "");
123
- debug("Incoming request is %s %v.", req.method, url);
124
- if (req.method !== "GET" && req.method !== "HEAD") {
125
- debug("Method not allowed.");
126
- return;
258
+ async handleRequest(request, response) {
259
+ const fileInfo = await this._findFileForRequest(request);
260
+ if (fileInfo !== void 0) {
261
+ this._sendFile(request, response, fileInfo);
262
+ return true;
127
263
  }
128
- debug("Walking through %v routes.", this._routes.length);
129
- const route = this._routes.find((route2) => {
130
- const res = route2.regexp.test(url);
131
- const phrase = res ? "matched" : "not matched";
132
- debug("Resource %v %s.", route2.resourcePath, phrase);
133
- return res;
134
- });
135
- route ? debug("Resource %v matched.", route.resourcePath) : debug("No route matched.");
136
- return route;
264
+ return false;
137
265
  }
138
266
  /**
139
- * Send file by route.
267
+ * Find file for request.
140
268
  *
141
- * @param {object} route
142
- * @param {import('http').IncomingMessage} req
143
- * @param {import('http').ServerResponse} res
269
+ * @param {import('http').IncomingMessage} request
270
+ * @returns {Promise<FileInfo|undefined>|undefined}
144
271
  */
145
- sendFileByRoute(route, req, res) {
146
- const reqUrl = req.url || "/";
147
- const reqPath = reqUrl.replace(/\?.*$/, "");
148
- let targetPath = route.resourcePath;
149
- if (!route.isFile) {
150
- const relativePath = reqPath.replace(route.regexp, "");
151
- targetPath = import_path.default.join(route.resourcePath, relativePath);
152
- }
153
- targetPath = import_path.default.resolve(targetPath);
154
- const resourceRoot = import_path.default.resolve(route.resourcePath);
155
- if (!targetPath.startsWith(resourceRoot)) {
156
- res.writeHead(403, { "content-type": "text/plain" });
157
- res.end("403 Forbidden");
272
+ async _findFileForRequest(request) {
273
+ if (!(request instanceof import_http.IncomingMessage)) {
274
+ throw new import_js_format3.InvalidArgumentError(
275
+ 'Parameter "request" must be an instance of IncomingMessage, but %v was given.',
276
+ request
277
+ );
278
+ }
279
+ const debug = this.getDebuggerFor(this._findFileForRequest);
280
+ debug("File finding for an incoming request.");
281
+ debug("Incoming request %s %v.", request.method, request.url);
282
+ let requestPath;
283
+ try {
284
+ requestPath = decodeURIComponent(getPathnameFromUrl(request.url || ""));
285
+ } catch {
286
+ debug("Invalid URL encoding .");
158
287
  return;
159
288
  }
160
- import_fs.default.stat(targetPath, (statsError, stats) => {
161
- if (statsError) {
162
- return _handleFsError(statsError, res);
163
- }
164
- if (stats.isDirectory()) {
165
- if (/[^/]$/.test(reqPath)) {
166
- const searchMatch = reqUrl.match(/\?.*$/);
167
- const search = searchMatch ? searchMatch[0] : "";
168
- const normalizedPath = reqUrl.replace(/\/{2,}/g, "/");
169
- res.writeHead(302, { location: `${normalizedPath}/${search}` });
170
- res.end();
171
- return;
289
+ if (request.method !== "GET" && request.method !== "HEAD") {
290
+ debug("Method not allowed.");
291
+ return;
292
+ }
293
+ if (requestPath.includes("//")) {
294
+ debug("Request path contains duplicate slashes.");
295
+ return;
296
+ }
297
+ if (!this._routes.length) {
298
+ debug("No registered routes.");
299
+ return;
300
+ }
301
+ debug("Walking through %v routes.", this._routes.length);
302
+ for (const route of this._routes) {
303
+ const isMatched = route.regexp.test(requestPath);
304
+ if (isMatched) {
305
+ debug("Matched route %v.", route.remotePath);
306
+ let targetPath = route.resourcePath;
307
+ if (!route.isFile) {
308
+ const relativePath = requestPath.replace(route.regexp, "");
309
+ targetPath = import_path2.default.join(route.resourcePath, relativePath);
172
310
  }
173
- if (/\/{2,}/.test(reqUrl)) {
174
- const normalizedUrl = reqUrl.replace(/\/{2,}/g, "/");
175
- res.writeHead(302, { location: normalizedUrl });
176
- res.end();
311
+ targetPath = import_path2.default.resolve(targetPath);
312
+ const resourceRoot = import_path2.default.resolve(route.resourcePath);
313
+ if (targetPath !== resourceRoot && !targetPath.startsWith(resourceRoot + import_path2.default.sep)) {
177
314
  return;
178
315
  }
179
- targetPath = import_path.default.join(targetPath, "index.html");
180
- }
181
- const extname = import_path.default.extname(targetPath);
182
- const contentType = import_mime_types.default.contentType(extname) || "application/octet-stream";
183
- const fileStream = (0, import_fs.createReadStream)(targetPath);
184
- fileStream.on("error", (error) => {
185
- _handleFsError(error, res);
186
- });
187
- fileStream.on("open", () => {
188
- res.writeHead(200, { "content-type": contentType });
189
- if (req.method === "HEAD") {
190
- res.end();
191
- return;
316
+ const fileSize = await new Promise((resolve) => {
317
+ import_fs2.default.stat(targetPath, (statsError, stats) => {
318
+ if (statsError || stats.isDirectory()) {
319
+ resolve(void 0);
320
+ return;
321
+ }
322
+ resolve(stats.size);
323
+ return;
324
+ });
325
+ });
326
+ if (fileSize !== void 0) {
327
+ if (requestPath.endsWith("/")) {
328
+ continue;
329
+ }
330
+ debug("File found %v.", targetPath);
331
+ return { path: targetPath, size: fileSize };
192
332
  }
193
- fileStream.pipe(res);
333
+ }
334
+ }
335
+ debug("File not found.");
336
+ }
337
+ /**
338
+ * Send file.
339
+ *
340
+ * @param {import('http').IncomingMessage} request
341
+ * @param {import('http').ServerResponse} response
342
+ * @param {FileInfo} fileInfo
343
+ */
344
+ _sendFile(request, response, fileInfo) {
345
+ const debug = this.getDebuggerFor(this._sendFile);
346
+ debug("File sending for an incoming request.");
347
+ debug("Incoming request %s %v.", request.method, request.url);
348
+ debug("File path %v.", fileInfo.path);
349
+ debug("File size %v bytes.", fileInfo.size);
350
+ if (request.method !== "GET" && request.method !== "HEAD") {
351
+ debug("Method not allowed.");
352
+ return;
353
+ }
354
+ const extname = import_path2.default.extname(fileInfo.path);
355
+ const contentType = import_mime_types.default.contentType(extname) || "application/octet-stream";
356
+ const fileStream = (0, import_fs2.createReadStream)(fileInfo.path);
357
+ fileStream.on("error", (error) => {
358
+ debug("Unable to open a file stream.");
359
+ this._handleFsError(error, response);
360
+ });
361
+ fileStream.on("open", () => {
362
+ response.writeHead(200, {
363
+ "Content-Type": contentType,
364
+ "Content-Length": fileInfo.size
194
365
  });
366
+ if (request.method === "HEAD") {
367
+ response.end();
368
+ debug("Response has been sent without a body for the HEAD request.");
369
+ fileStream.destroy();
370
+ return;
371
+ }
372
+ fileStream.pipe(response);
373
+ });
374
+ request.on("close", () => {
375
+ debug("File has been sent.");
376
+ fileStream.destroy();
195
377
  });
196
378
  }
379
+ /**
380
+ * Handle filesystem error.
381
+ *
382
+ * @param {object} error
383
+ * @param {object} response
384
+ * @returns {undefined}
385
+ */
386
+ _handleFsError(error, response) {
387
+ if (response.headersSent) {
388
+ response.destroy();
389
+ return;
390
+ }
391
+ if ("code" in error && error.code === "ENOENT") {
392
+ response.writeHead(404, { "Content-Type": "text/plain" });
393
+ response.write("404 Not Found");
394
+ response.end();
395
+ } else {
396
+ response.writeHead(500, { "Content-Type": "text/plain" });
397
+ response.write("500 Internal Server Error");
398
+ response.end();
399
+ }
400
+ }
197
401
  };
198
402
  __name(_HttpStaticRouter, "HttpStaticRouter");
199
403
  var HttpStaticRouter = _HttpStaticRouter;
200
- function _handleFsError(error, res) {
201
- if (res.headersSent) {
202
- return;
203
- }
204
- if ("code" in error && error.code === "ENOENT") {
205
- res.writeHead(404, { "content-type": "text/plain" });
206
- res.write("404 Not Found");
207
- res.end();
208
- } else {
209
- res.writeHead(500, { "content-type": "text/plain" });
210
- res.write("500 Internal Server Error");
211
- res.end();
212
- }
213
- }
214
- __name(_handleFsError, "_handleFsError");
215
404
  // Annotate the CommonJS export names for ESM import in node:
216
405
  0 && (module.exports = {
217
- HttpStaticRouter
406
+ HttpStaticRouter,
407
+ StaticRoute
218
408
  });
package/example/server.js CHANGED
@@ -2,40 +2,43 @@ import http from 'http';
2
2
  import {HttpStaticRouter} from '@e22m4u/js-http-static-router';
3
3
 
4
4
  // создание экземпляра маршрутизатора
5
- const staticRouter = new HttpStaticRouter();
5
+ const staticRouter = new HttpStaticRouter({
6
+ // при использовании опции "rootDir", относительные пути
7
+ // в регистрируемых маршрутах будут разрешаться относительно
8
+ // указанного адреса файловой системы
9
+ rootDir: import.meta.dirname,
10
+ });
6
11
 
7
- // определение директории "../static"
8
- // доступной по адресу "/static"
9
- staticRouter.addRoute(
10
- '/static', // путь маршрута
11
- `${import.meta.dirname}/static`, // файловый путь
12
- );
12
+ // экспозиция содержимого директории "/static"
13
+ // доступным по адресу "/assets/{file_name}"
14
+ staticRouter.defineRoute({
15
+ remotePath: '/assets', // путь маршрута
16
+ resourcePath: './static', // файловый путь
17
+ });
13
18
 
14
- // объявление файла "./static/file.txt"
15
- // доступным по адресу "/static"
16
- staticRouter.addRoute(
17
- '/file.txt',
18
- `${import.meta.dirname}/static/file.txt`,
19
- );
19
+ // объявление файла "./index.html"
20
+ // доступным по адресу "/home"
21
+ staticRouter.defineRoute({
22
+ remotePath: '/home',
23
+ resourcePath: './static/index.html',
24
+ });
20
25
 
21
- // создание HTTP сервера и подключение обработчика
26
+ // создание HTTP сервера и определение
27
+ // функции для обработки запросов
22
28
  const server = new http.Server();
23
- server.on('request', (req, res) => {
24
- // если статический маршрут найден,
25
- // выполняется поиск и отдача файла
26
- const staticRoute = staticRouter.matchRoute(req);
27
- if (staticRoute) {
28
- return staticRouter.sendFileByRoute(staticRoute, req, res);
29
+ server.on('request', async (req, res) => {
30
+ const fileSent = await staticRouter.handleRequest(req, res);
31
+ if (!fileSent) {
32
+ res.writeHead(404, {'Content-Type': 'text/plain'});
33
+ res.write('404 Not Found');
34
+ res.end();
29
35
  }
30
- // в противном случае запрос обрабатывается
31
- // основной логикой приложения
32
- res.writeHead(200, {'Content-Type': 'text/plain'});
33
- res.end('Hello from App!');
34
36
  });
35
37
 
36
38
  server.listen(3000, () => {
37
39
  console.log('Server is running on http://localhost:3000');
38
40
  console.log('Try to open:');
39
- console.log('http://localhost:3000/static/');
40
- console.log('http://localhost:3000/file.txt');
41
+ console.log('http://localhost:3000/home');
42
+ console.log('http://localhost:3000/assets/file.txt');
43
+ console.log('http://localhost:3000/assets/nested/file.txt');
41
44
  });
@@ -0,0 +1,11 @@
1
+ ⠀⠀⠀⣀⣤⣤⣀⡀⠀⠀⠀⠀⢀⣀⣤⣤⣀⡀
2
+ ⠀⣴⣿⣋⢯⣩⢭⣟⣶⡄⢀⣴⣟⡏⣭⣉⣏⡻⣷⡀
3
+ ⣸⣿⡷⣯⣿⡽⣿⣞⣷⣿⣾⣿⣯⣿⣽⣳⣾⣽⣻⣷
4
+ ⢿⣿⣟⣿⡷⣿⣿⣿⣿⣻⣿⢿⣾⣿⣾⢿⣽⡿⣿⣿⠀
5
+ ⠸⣿⣿⣿⣽⣿⢿⣾⣿⡿⣽⣿⣿⣷⢿⣿⣯⣿⣿⡟⠀
6
+ ⠀⠻⣿⣿⣿⣿⣿⣿⣷⣿⣿⢿⣾⡿⣿⣻⣽⣿⡟⠀⠀
7
+ ⠀⠀⠙⢿⣿⣿⣿⣷⣿⣻⣾⣿⣿⣿⣿⣿⣿⠏⠀⠀
8
+ ⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣟⣿⣿⣿⠟⠁⠀⠀⠀
9
+ ⠀⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀
10
+ ⠀⠀⠀⠀⠀⠀⠀⠙⠿⣿⣿⡿⠋⠀⠀⠀⠀⠀
11
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠏⠀⠀⠀⠀⠀⠀