@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 +35 -29
- package/dist/cjs/index.cjs +313 -123
- package/example/server.js +29 -26
- package/example/static/nested/file.txt +11 -0
- package/package.json +4 -4
- package/src/http-static-router.d.ts +29 -26
- package/src/http-static-router.js +259 -150
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/static-route.d.ts +48 -0
- package/src/static-route.js +91 -0
- package/src/utils/get-pathname-from-url.d.ts +7 -0
- package/src/utils/get-pathname-from-url.js +27 -0
- package/src/utils/get-request-pathname.spec.js +37 -0
- package/src/utils/index.d.ts +1 -1
- package/src/utils/index.js +1 -1
- package/example/static/nested/index.html +0 -10
- package/src/types.d.ts +0 -38
- package/src/utils/normalize-path.d.ts +0 -12
- package/src/utils/normalize-path.js +0 -22
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
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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/
|
|
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
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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/
|
|
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/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
return noStartingSlash ? res : "/" + res;
|
|
146
|
+
return url.replace(HOST_RE, "").replace(QUERY_STRING_RE, "");
|
|
56
147
|
}
|
|
57
|
-
__name(
|
|
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}
|
|
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(
|
|
75
|
-
|
|
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
|
-
*
|
|
222
|
+
* Define route.
|
|
82
223
|
*
|
|
83
|
-
* @param {
|
|
84
|
-
* @
|
|
85
|
-
* @returns {object}
|
|
224
|
+
* @param {import('./static-route.js').StaticRouteDefinition} routeDef
|
|
225
|
+
* @returns {this}
|
|
86
226
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
*
|
|
252
|
+
* Handle request.
|
|
115
253
|
*
|
|
116
|
-
* @param {import('http').IncomingMessage}
|
|
117
|
-
* @
|
|
254
|
+
* @param {import('http').IncomingMessage} request
|
|
255
|
+
* @param {import('http').ServerResponse} response
|
|
256
|
+
* @returns {Promise<boolean>}
|
|
118
257
|
*/
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
267
|
+
* Find file for request.
|
|
140
268
|
*
|
|
141
|
-
* @param {
|
|
142
|
-
* @
|
|
143
|
-
* @param {import('http').ServerResponse} res
|
|
269
|
+
* @param {import('http').IncomingMessage} request
|
|
270
|
+
* @returns {Promise<FileInfo|undefined>|undefined}
|
|
144
271
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
8
|
-
//
|
|
9
|
-
staticRouter.
|
|
10
|
-
'/
|
|
11
|
-
|
|
12
|
-
);
|
|
12
|
+
// экспозиция содержимого директории "/static"
|
|
13
|
+
// доступным по адресу "/assets/{file_name}"
|
|
14
|
+
staticRouter.defineRoute({
|
|
15
|
+
remotePath: '/assets', // путь маршрута
|
|
16
|
+
resourcePath: './static', // файловый путь
|
|
17
|
+
});
|
|
13
18
|
|
|
14
|
-
// объявление файла "./
|
|
15
|
-
// доступным по адресу "/
|
|
16
|
-
staticRouter.
|
|
17
|
-
'/
|
|
18
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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/
|
|
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
|
});
|