@e22m4u/js-http-static-router 0.0.2 → 0.1.1

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