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