@thetally/httptree 0.2.0 → 0.3.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/dist/router.js +74 -133
- package/package.json +1 -1
package/dist/router.js
CHANGED
|
@@ -101,8 +101,6 @@ function finishResponse(res, result) {
|
|
|
101
101
|
if (res.writableEnded)
|
|
102
102
|
return;
|
|
103
103
|
if (!result) {
|
|
104
|
-
res.statusCode = 204;
|
|
105
|
-
res.end();
|
|
106
104
|
return;
|
|
107
105
|
}
|
|
108
106
|
if (result[responseSymbol]) {
|
|
@@ -125,6 +123,25 @@ function finishResponse(res, result) {
|
|
|
125
123
|
res.statusCode = 204;
|
|
126
124
|
res.end();
|
|
127
125
|
}
|
|
126
|
+
function finishThrownResponse(res, err) {
|
|
127
|
+
if (res.writableEnded)
|
|
128
|
+
return;
|
|
129
|
+
if (err && typeof err === "object" && err[responseSymbol]) {
|
|
130
|
+
const r = err;
|
|
131
|
+
res.statusCode = r.status;
|
|
132
|
+
for (const [k, v] of Object.entries(r.headers || {})) {
|
|
133
|
+
try {
|
|
134
|
+
res.setHeader(k, v);
|
|
135
|
+
}
|
|
136
|
+
catch { }
|
|
137
|
+
}
|
|
138
|
+
res.end(r.body);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
res.statusCode = 500;
|
|
142
|
+
res.end("Internal Server Error");
|
|
143
|
+
console.error("Error in handler:", err);
|
|
144
|
+
}
|
|
128
145
|
class Router {
|
|
129
146
|
base;
|
|
130
147
|
routes;
|
|
@@ -141,7 +158,7 @@ class Router {
|
|
|
141
158
|
this.errorHandlers = new Map();
|
|
142
159
|
this.parent = undefined;
|
|
143
160
|
if (!root) {
|
|
144
|
-
this.
|
|
161
|
+
use(this.handle);
|
|
145
162
|
}
|
|
146
163
|
}
|
|
147
164
|
handleError(type, handler) {
|
|
@@ -204,16 +221,12 @@ class Router {
|
|
|
204
221
|
handle = async (req, res, next) => {
|
|
205
222
|
const pathname = url.parse(req.url || "/").pathname || "/";
|
|
206
223
|
const method = (req.method || "GET").toUpperCase();
|
|
207
|
-
debug(`Handling request: method='${method}', path='${pathname}'`);
|
|
208
|
-
let matchedRoute = null;
|
|
209
224
|
for (const r of this.routes) {
|
|
210
225
|
if (r.method !== "*" && r.method !== method)
|
|
211
226
|
continue;
|
|
212
227
|
const m = r.info.regex.exec(pathname);
|
|
213
228
|
if (!m)
|
|
214
229
|
continue;
|
|
215
|
-
debug(`Route matched: method='${r.method}', regex='${r.info.regex}', params=[${r.info.params.join(", ")}]`);
|
|
216
|
-
matchedRoute = r;
|
|
217
230
|
const params = {};
|
|
218
231
|
r.info.params.forEach((p, i) => {
|
|
219
232
|
debug(`Extracting param '${p}' from segment '${m[i + 1]}'`);
|
|
@@ -226,10 +239,8 @@ class Router {
|
|
|
226
239
|
}
|
|
227
240
|
});
|
|
228
241
|
for (const chk of r.prechecks) {
|
|
229
|
-
debug(`Running precheck for route: method='${r.method}', path regex='${r.info.regex}'`);
|
|
230
242
|
const ok = await chk(req, res);
|
|
231
243
|
if (!ok) {
|
|
232
|
-
debug(`Precheck failed for route: method='${r.method}', path regex='${r.info.regex}'`);
|
|
233
244
|
res.statusCode = 403;
|
|
234
245
|
res.end("Forbidden");
|
|
235
246
|
return;
|
|
@@ -237,129 +248,14 @@ class Router {
|
|
|
237
248
|
}
|
|
238
249
|
try {
|
|
239
250
|
req.params = params;
|
|
240
|
-
|
|
241
|
-
const result = await r.handler(req, res);
|
|
242
|
-
if (result && typeof result === "object" && result[responseSymbol]) {
|
|
243
|
-
finishResponse(res, result);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
if (!res.writableEnded) {
|
|
247
|
-
res.end();
|
|
248
|
-
}
|
|
249
|
-
return;
|
|
251
|
+
await r.handler(req, res);
|
|
250
252
|
}
|
|
251
253
|
catch (e) {
|
|
252
|
-
|
|
253
|
-
finishResponse(res, e);
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
let errType = (e && typeof e === "object" && e.name) || (e && typeof e === "object" && e.constructor && e.constructor.name) || typeof e;
|
|
257
|
-
let lastError = e;
|
|
258
|
-
let current = this;
|
|
259
|
-
if (process.env.HTTPTREE_DEBUG) {
|
|
260
|
-
debug(`Error thrown: type='${errType}' on base='${this.base}'`);
|
|
261
|
-
}
|
|
262
|
-
while (current) {
|
|
263
|
-
const handler = current.errorHandlers.get(String(errType)) || current.errorHandlers.get("*");
|
|
264
|
-
if (handler) {
|
|
265
|
-
if (process.env.HTTPTREE_DEBUG) {
|
|
266
|
-
debug(`Using error handler for type='${errType}' on base='${current.base}'`);
|
|
267
|
-
}
|
|
268
|
-
try {
|
|
269
|
-
handler(lastError, req, res);
|
|
270
|
-
}
|
|
271
|
-
catch (err) {
|
|
272
|
-
lastError = err;
|
|
273
|
-
if (process.env.HTTPTREE_DEBUG) {
|
|
274
|
-
debug(`Handler on base='${current.base}' threw, trying parent`);
|
|
275
|
-
}
|
|
276
|
-
current = current.parent;
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
else if (process.env.HTTPTREE_DEBUG) {
|
|
282
|
-
debug(`No handler for type='${errType}' on base='${current.base}', moving to parent`);
|
|
283
|
-
}
|
|
284
|
-
current = current.parent;
|
|
285
|
-
}
|
|
286
|
-
if (!res.writableEnded) {
|
|
287
|
-
res.statusCode = 500;
|
|
288
|
-
res.end("Internal Server Error");
|
|
289
|
-
}
|
|
290
|
-
if (lastError &&
|
|
291
|
-
((typeof lastError !== "object" || !('suppressLog' in lastError)) &&
|
|
292
|
-
!(lastError && typeof lastError === "object" && lastError.name === "NotFound"))) {
|
|
293
|
-
console.error("Uncaught handler error:", lastError);
|
|
294
|
-
}
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (!res.writableEnded) {
|
|
299
|
-
debug(`No route matched for method='${method}', path='${pathname}'. Resolving most specific branch.`);
|
|
300
|
-
let mostSpecific = this;
|
|
301
|
-
let current = this;
|
|
302
|
-
if (process.env.HTTPTREE_DEBUG) {
|
|
303
|
-
debug(`NotFound: resolving most specific branch for path='${pathname}'`);
|
|
304
|
-
}
|
|
305
|
-
while (true) {
|
|
306
|
-
let found = false;
|
|
307
|
-
if (current.branches) {
|
|
308
|
-
let bestBranch = null;
|
|
309
|
-
let bestLen = -1;
|
|
310
|
-
for (const branch of current.branches) {
|
|
311
|
-
if (pathname.startsWith(branch.base) && branch.base.length > bestLen) {
|
|
312
|
-
bestBranch = branch;
|
|
313
|
-
bestLen = branch.base.length;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
if (bestBranch) {
|
|
317
|
-
if (process.env.HTTPTREE_DEBUG) {
|
|
318
|
-
debug(`Path '${pathname}' matches branch base='${bestBranch.base}'`);
|
|
319
|
-
}
|
|
320
|
-
mostSpecific = bestBranch;
|
|
321
|
-
current = bestBranch;
|
|
322
|
-
found = true;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
if (!found)
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
let errType = "NotFound";
|
|
329
|
-
let lastError = Object.assign(new Error("Not Found"), { name: "NotFound" });
|
|
330
|
-
current = mostSpecific;
|
|
331
|
-
while (current) {
|
|
332
|
-
const handler = current.errorHandlers.get(errType) || current.errorHandlers.get("*");
|
|
333
|
-
if (handler) {
|
|
334
|
-
if (process.env.HTTPTREE_DEBUG) {
|
|
335
|
-
debug(`Using NotFound handler on base='${current.base}' for path='${pathname}'`);
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
handler(lastError, req, res);
|
|
339
|
-
}
|
|
340
|
-
catch (err) {
|
|
341
|
-
lastError = err;
|
|
342
|
-
if (process.env.HTTPTREE_DEBUG) {
|
|
343
|
-
debug(`NotFound handler on base='${current.base}' threw, trying parent`);
|
|
344
|
-
}
|
|
345
|
-
current = current.parent;
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
else if (process.env.HTTPTREE_DEBUG) {
|
|
351
|
-
debug(`No NotFound handler on base='${current.base}', moving to parent`);
|
|
352
|
-
}
|
|
353
|
-
current = current.parent;
|
|
354
|
-
}
|
|
355
|
-
if (!res.writableEnded) {
|
|
356
|
-
res.statusCode = 404;
|
|
357
|
-
res.end("Not Found");
|
|
358
|
-
}
|
|
359
|
-
if (lastError && (typeof lastError !== "object" || !('suppressLog' in lastError))) {
|
|
360
|
-
console.error("Uncaught handler error:", lastError);
|
|
254
|
+
return next(e instanceof Error ? e : new Error(String(e)));
|
|
361
255
|
}
|
|
256
|
+
return;
|
|
362
257
|
}
|
|
258
|
+
next();
|
|
363
259
|
};
|
|
364
260
|
handleWS = async (ws, req) => {
|
|
365
261
|
const pathname = url.parse(req.url || "/").pathname || "/";
|
|
@@ -390,10 +286,20 @@ class Router {
|
|
|
390
286
|
const full = join(this.base, path);
|
|
391
287
|
const info = compilePattern(full);
|
|
392
288
|
debug(`Adding route: method='${method}', full path='${full}', regex='${info.regex}', params=[${info.params.join(", ")}]`);
|
|
289
|
+
const wrappedHandler = async (req, res) => {
|
|
290
|
+
try {
|
|
291
|
+
const result = await handler(req, res);
|
|
292
|
+
finishResponse(res, result);
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
// throw e instanceof Error ? e : new Error(String(e));
|
|
296
|
+
finishThrownResponse(res, e);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
393
299
|
this.routes.push({
|
|
394
300
|
method,
|
|
395
301
|
info,
|
|
396
|
-
handler,
|
|
302
|
+
handler: wrappedHandler,
|
|
397
303
|
prechecks: [...this.inheritedChecks],
|
|
398
304
|
});
|
|
399
305
|
this.routes.sort((a, b) => b.info.score - a.info.score ||
|
|
@@ -415,10 +321,45 @@ export class BaseRouter extends Router {
|
|
|
415
321
|
res.end("Internal Error");
|
|
416
322
|
}
|
|
417
323
|
else {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
324
|
+
if (res.writableEnded)
|
|
325
|
+
return;
|
|
326
|
+
this.handle(req, res, (err) => {
|
|
327
|
+
if (err) {
|
|
328
|
+
const handler = this.errorHandlers.get(err.name) ||
|
|
329
|
+
this.errorHandlers.get("InternalError") ||
|
|
330
|
+
this.errorHandlers.get("*");
|
|
331
|
+
if (handler) {
|
|
332
|
+
try {
|
|
333
|
+
handler(err, req, res);
|
|
334
|
+
}
|
|
335
|
+
catch (e) {
|
|
336
|
+
res.statusCode = 500;
|
|
337
|
+
res.end("Internal Error");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
res.statusCode = 500;
|
|
342
|
+
res.end("Internal Error");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
if (!res.writableEnded) {
|
|
347
|
+
const notFoundHandler = this.errorHandlers.get("NotFound") ||
|
|
348
|
+
this.errorHandlers.get("*");
|
|
349
|
+
if (notFoundHandler) {
|
|
350
|
+
try {
|
|
351
|
+
notFoundHandler({ name: "NotFound", message: "Not Found" }, req, res);
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
354
|
+
res.statusCode = 500;
|
|
355
|
+
res.end("Internal Error");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
res.statusCode = 404;
|
|
360
|
+
res.end("Not Found");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
422
363
|
}
|
|
423
364
|
});
|
|
424
365
|
}
|