@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.
- package/README.md +35 -49
- package/dist/cjs/index.cjs +238 -209
- package/example/server.js +29 -26
- package/example/static/nested/file.txt +11 -0
- package/package.json +10 -10
- package/src/http-static-router.d.ts +27 -22
- package/src/http-static-router.js +224 -222
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/{http-static-route.d.ts → static-route.d.ts} +13 -12
- 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/http-static-route.js +0 -73
- 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/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
34
|
+
HttpStaticRouter: () => HttpStaticRouter,
|
|
35
|
+
StaticRoute: () => StaticRoute
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(index_exports);
|
|
38
38
|
|
|
39
|
-
// src/
|
|
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
|
|
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}
|
|
70
|
-
* @param {string} resourcePath
|
|
71
|
-
* @param {RegExp} regexp
|
|
72
|
-
* @param {boolean} isFile
|
|
79
|
+
* @param {string} routeDef
|
|
73
80
|
*/
|
|
74
|
-
constructor(
|
|
75
|
-
if (typeof
|
|
81
|
+
constructor(routeDef) {
|
|
82
|
+
if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef)) {
|
|
76
83
|
throw new import_js_format.InvalidArgumentError(
|
|
77
|
-
'Parameter "
|
|
78
|
-
|
|
84
|
+
'Parameter "routeDef" must be an Object, but %v was given.',
|
|
85
|
+
routeDef
|
|
79
86
|
);
|
|
80
87
|
}
|
|
81
|
-
if (typeof
|
|
88
|
+
if (typeof routeDef.remotePath !== "string") {
|
|
82
89
|
throw new import_js_format.InvalidArgumentError(
|
|
83
|
-
'
|
|
84
|
-
|
|
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 (
|
|
100
|
+
if (typeof routeDef.resourcePath !== "string") {
|
|
88
101
|
throw new import_js_format.InvalidArgumentError(
|
|
89
|
-
'
|
|
90
|
-
|
|
102
|
+
'Option "resourcePath" must be a String, but %v was given.',
|
|
103
|
+
routeDef.resourcePath
|
|
91
104
|
);
|
|
92
105
|
}
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
113
|
+
"Resource path %v does not exist.",
|
|
114
|
+
resourcePath
|
|
97
115
|
);
|
|
98
116
|
}
|
|
99
|
-
|
|
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(
|
|
106
|
-
var
|
|
126
|
+
__name(_StaticRoute, "StaticRoute");
|
|
127
|
+
var StaticRoute = _StaticRoute;
|
|
107
128
|
|
|
108
129
|
// src/http-static-router.js
|
|
109
|
-
var
|
|
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
|
|
133
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
114
134
|
|
|
115
|
-
// src/utils/
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
return noStartingSlash ? res : "/" + res;
|
|
146
|
+
return url.replace(HOST_RE, "").replace(QUERY_STRING_RE, "");
|
|
128
147
|
}
|
|
129
|
-
__name(
|
|
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 {
|
|
158
|
+
* @type {StaticRoute[]}
|
|
139
159
|
*/
|
|
140
160
|
_routes = [];
|
|
141
161
|
/**
|
|
142
162
|
* Options.
|
|
143
163
|
*
|
|
144
|
-
* @type {
|
|
164
|
+
* @type {import('./http-static-router.js').HttpStaticRouterOptions}
|
|
165
|
+
* @protected
|
|
145
166
|
*/
|
|
146
167
|
_options = {};
|
|
147
168
|
/**
|
|
148
169
|
* Constructor.
|
|
149
170
|
*
|
|
150
|
-
* @param {
|
|
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
|
-
|
|
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 (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
167
|
-
if (typeof options
|
|
168
|
-
throw new
|
|
169
|
-
'
|
|
170
|
-
options
|
|
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
|
|
219
|
+
Object.freeze(this._options);
|
|
175
220
|
}
|
|
176
221
|
/**
|
|
177
|
-
*
|
|
222
|
+
* Define route.
|
|
178
223
|
*
|
|
179
|
-
* @param {
|
|
180
|
-
* @
|
|
181
|
-
* @returns {object}
|
|
224
|
+
* @param {import('./static-route.js').StaticRouteDefinition} routeDef
|
|
225
|
+
* @returns {this}
|
|
182
226
|
*/
|
|
183
|
-
|
|
184
|
-
if (typeof
|
|
185
|
-
throw new
|
|
186
|
-
"
|
|
187
|
-
|
|
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 (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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.
|
|
197
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
252
|
+
* Handle request.
|
|
223
253
|
*
|
|
224
|
-
* @param {IncomingMessage}
|
|
225
|
-
* @
|
|
254
|
+
* @param {import('http').IncomingMessage} request
|
|
255
|
+
* @param {import('http').ServerResponse} response
|
|
256
|
+
* @returns {Promise<boolean>}
|
|
226
257
|
*/
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
267
|
+
* Find file for request.
|
|
254
268
|
*
|
|
255
|
-
* @param {
|
|
256
|
-
* @
|
|
257
|
-
* @param {import('http').ServerResponse} res
|
|
269
|
+
* @param {import('http').IncomingMessage} request
|
|
270
|
+
* @returns {Promise<FileInfo|undefined>|undefined}
|
|
258
271
|
*/
|
|
259
|
-
|
|
260
|
-
if (!(
|
|
261
|
-
throw new
|
|
262
|
-
'Parameter "
|
|
263
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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 (
|
|
289
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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 =
|
|
322
|
-
|
|
323
|
-
if (
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
383
|
+
* @param {object} response
|
|
356
384
|
* @returns {undefined}
|
|
357
385
|
*/
|
|
358
|
-
_handleFsError(error,
|
|
359
|
-
if (
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
392
|
+
response.writeHead(404, { "Content-Type": "text/plain" });
|
|
393
|
+
response.write("404 Not Found");
|
|
394
|
+
response.end();
|
|
366
395
|
} else {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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
|
-
//
|
|
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
|
});
|