1 module vibrant.router;
2 
3 import std.algorithm;
4 import std.conv;
5 import std.functional;
6 import std.traits;
7 import std.typecons;
8 import std.typetuple;
9 
10 import vibe.d;
11 
12 import vibrant.helper;
13 
14 // TODO : This is probably a hack.
15 extern (C) int _d_isbaseof(ClassInfo oc, ClassInfo c);
16 
17 /++
18  + The vibrant router class.
19  ++/
20 class VibrantRouter(bool GenerateAll = false) : HTTPServerRequestHandler {
21 	/++
22 		+ The URL router that manages Vibrant's routes.
23 		++/
24 	URLRouter router;
25 
26 	private {
27 
28 		/++
29 		 + An internal throwable type used to halt execution.
30 		 ++/
31 		class HaltThrowable : Throwable {
32 
33 			/++
34 			 + The status code sent in the response.
35 			 ++/
36 			private int status;
37 
38 			/++
39 			 + Constructs a HaltThrowable.
40 			 +
41 			 + Params:
42 			 +     status = The status code to send to the client.
43 			 +     msg    = A message body to include in the response.
44 			 ++/
45 			this(int status, string msg) {
46 				super(msg);
47 				this.status = status;
48 			}
49 
50 		}
51 
52 		/++
53 		 + A saved listener, used to stop and restart the server.
54 		 ++/
55 		Nullable!HTTPListener savedListener;
56 
57 		/++
58 		 + Filter callbacks invoked before a route handler.
59 		 ++/
60 		VoidCallback[][string] beforeCallbacks;
61 
62 		/++
63 		 + Filter callbacks invoked after a route handler.
64 		 ++/
65 		VoidCallback[][string] afterCallbacks;
66 
67 		/++
68 		 + A table storing exception callbacks.
69 		 ++/
70 		ExceptionCallback[ClassInfo] exceptionCallbacks;
71 
72 		/++
73 		 + Tests if a type is a valid result from a callback.
74 		 ++/
75 		template isValidResultType(Result) {
76 			enum isValidResultType =
77 				is(Result == const(ubyte[])) ||
78 				is(Result == ubyte[]) ||
79 				is(Result == string) ||
80 				is(Result == void);
81 		}
82 
83 		/++
84 		 + Tests if a type is a valid result from a transform function.
85 		 ++/
86 		template isValidTransformedType(Temp) {
87 			enum isValidTransformedType =
88 				is(Temp == const(ubyte[])) ||
89 				is(Temp == ubyte[]) ||
90 				is(Temp == string);
91 		}
92 
93 	}
94 
95 	/++
96 	 + Constructs a new vibrant router.
97 	 +
98 	 + Params:
99 	 +     router = A URLRouter to forward requests to.
100 	 ++/
101 	package this(URLRouter router) {
102 		this.router = router;
103 
104 		// Preload the HaltThrowable handler.
105 		Catch(HaltThrowable.classinfo, (t, req, res) {
106 			// Get the HaltThrowable object.
107 			HaltThrowable ht = cast(HaltThrowable) t;
108 
109 			// Check for a status code.
110 			if (ht.status != 0) {
111 				res.statusCode = ht.status;
112 			}
113 
114 			// Write the response body.
115 			res.writeBody(ht.msg);
116 		});
117 	}
118 
119 	/++
120 	 + Constructs a new vibrant router and starts listening for connections.
121 	 +
122 	 + Params:
123 	 +     settings = The settings for the HTTP server.
124 	 +     prefix   = The prefix to place all routes at.
125 	 ++/
126 	package this(HTTPServerSettings settings, string prefix) {
127 		this(new URLRouter(prefix));
128 		savedListener = listenHTTP(settings, router);
129 	}
130 
131 	/++
132 	 + Forwards a requrest to the internal URLRouter.
133 	 +
134 	 + Params:
135 	 +     req = The HTTPServerRequest object.
136 	 +     res = The HTTPServerResponse object.
137 	 ++/
138 	void handleRequest(HTTPServerRequest req, HTTPServerResponse res) {
139 		router.handleRequest(req, res);
140 	}
141 
142 	/++
143 	 + Produces a child of the current scope with the given prefix.
144 	 +
145 	 + Params:
146 	 +     prefix = The prefix the scope operates from.
147 	 +
148 	 + Returns:
149 	 +     A new VibrantRouter object.
150 	 ++/
151 	auto Scope(string prefix) {
152 		// Create a subrouter and forward requests that match its prefix.
153 		auto subrouter = new URLRouter(router.prefix ~ prefix);
154 		router.any("*", subrouter);
155 
156 		// Create a new child vibrant router, and pass along the listener.
157 		auto newRouter = new VibrantRouter!GenerateAll(subrouter);
158 		newRouter.savedListener = savedListener;
159 		return newRouter;
160 	}
161 
162 	/++
163 	 + Instantly updates the installed routes (instead of lazily).
164 	 ++/
165 	void Flush() {
166 		router.rebuild;
167 	}
168 
169 	/++
170 	 + Instantly stops the server.
171 	 ++/
172 	void Stop() {
173 		savedListener.get.stopListening;
174 		savedListener.nullify;
175 	}
176 
177 	/++
178 	 + Attaches a handler to an exception type.
179 	 +
180 	 + Params:
181 	 +     type     = The type of exception to catch.
182 	 +     callback = The handler for the exception.
183 	 ++/
184 	void Catch(ClassInfo type, ExceptionCallback callback) {
185 		// Add the callback to the type list.
186 		exceptionCallbacks[type] = callback;
187 	}
188 
189 	/++
190 	 + Adds a filter to all paths which is called before the handler.
191 	 +
192 	 + Params:
193 	 +     callback = The filter that handles the event.
194 	 ++/
195 	void Before(VoidCallback callback) {
196 		addFilterCallback(beforeCallbacks, null, callback);
197 	}
198 
199 	/++
200 	 + Adds a filter to the given path which is called before the handler.
201 	 +
202 	 + Params:
203 	 +     path     = The path that this filter is specific to.
204 	 +     callback = The filter that handles the event.
205 	 ++/
206 	void Before(string path, VoidCallback callback) {
207 		addFilterCallback(beforeCallbacks, path, callback);
208 	}
209 
210 	/++
211 	 + Adds a filter to all paths which is called after the handler.
212 	 +
213 	 + Params:
214 	 +     callback = The filter that handles the event.
215 	 ++/
216 	void After(VoidCallback callback) {
217 		addFilterCallback(afterCallbacks, null, callback);
218 	}
219 
220 	/++
221 	 + Adds a filter to the given path which is called after the handler.
222 	 +
223 	 + Params:
224 	 +     path     = The path that this filter is specific to.
225 	 +     callback = The filter that handles the event.
226 	 ++/
227 	void After(string path, VoidCallback callback) {
228 		addFilterCallback(afterCallbacks, path, callback);
229 	}
230 
231 	/++
232 	 + Halt execution of a route or filter handler.
233 	 + Halt uses a HaltThrowable. If caught, it should be re-thrown
234 	 + to properly stop exection of a callback.
235 	 +
236 	 + Params:
237 	 +     message = A message body to optionally include. Defaults to empty.
238 	 ++/
239 	void halt(string message = "") {
240 		throw new HaltThrowable(0, message);
241 	}
242 
243 	/++
244 	 + Halt execution of a route or filter handler.
245 	 + Halt uses a HaltThrowable. If caught, it should be re-thrown
246 	 + to properly stop exection of a callback.
247 	 +
248 	 + Params:
249 	 +     status  = The status code sent with the message.
250 	 +     message = A message body to optionally include. Defaults to empty.
251 	 ++/
252 	void halt(int status, string message = "") {
253 		throw new HaltThrowable(status, message);
254 	}
255 
256 	void Resource(Type)() {
257 		Type.install(this);
258 	}
259 
260 	void Resources(TList...)() {
261 		foreach (Type; TList) {
262 			Resource!Type;
263 		}
264 	}
265 
266 	/++
267 	 + Adds a handler for all method types on the given path.
268 	 +
269 	 + Params:
270 	 +     path     = The path that gets handled.
271 	 +     callback = The handler that gets called for requests.
272 	 ++/
273 	void Any(Result)(string path,
274 		Result function(HTTPServerRequest, HTTPServerResponse) callback)
275 			if (isValidResultType!Result) {
276 		return Any!(Result)(path, null, callback);
277 	}
278 
279 	/++
280 	 + Adds a handler for all method types on the given path.
281 	 +
282 	 + Params:
283 	 +     path        = The path that gets handled.
284 	 +     contentType = The content type header to include in the response.
285 	 +     callback    = The handler that gets called for requests.
286 	 ++/
287 	void Any(Result)(string path, string contentType,
288 		Result function(HTTPServerRequest, HTTPServerResponse) callback)
289 			if (isValidResultType!Result) {
290 		foreach (method; EnumMembers!HTTPMethod) {
291 			// Match each HTTP method type.
292 			Match(method, path, contentType, callback);
293 		}
294 	}
295 
296 	/++
297 	 + Adds a handler for all method types on the given path.
298 	 +
299 	 + Params:
300 	 +     path     = The path that gets handled.
301 	 +     callback = The handler that gets called for requests.
302 	 ++/
303 	void Any(Result)(string path, string contentType,
304 		Result delegate(HTTPServerRequest, HTTPServerResponse) callback)
305 			if (isValidResultType!Result) {
306 		return Any!(Result)(path, null, callback);
307 	}
308 
309 	/++
310 	 + Adds a handler for all method types on the given path.
311 	 +
312 	 + Params:
313 	 +     path        = The path that gets handled.
314 	 +     contentType = The content type header to include in the response.
315 	 +     callback    = The handler that gets called for requests.
316 	 ++/
317 	void Any(Result)(string path, string contentType,
318 		Result delegate(HTTPServerRequest, HTTPServerResponse) callback)
319 			if (isValidResultType!Result) {
320 		foreach (method; EnumMembers!HTTPMethod) {
321 			// Match each HTTP method type.
322 			Match(method, path, contentType, callback);
323 		}
324 	}
325 
326 	template Any(Temp) if (isValidTransformedType!Result) {
327 		static if (!is(Temp == void)) {
328 			/++
329 			 + Adds a handler for all method types on the given path.
330 			 +
331 			 + Params:
332 			 +     path        = The path that gets handled.
333 			 +     callback    = The handler that gets called for requests.
334 			 +     transformer = The transformer function that converts output.
335 			 ++/
336 			void Any(Result = string)(string path,
337 				Temp function(HTTPServerRequest, HTTPServerResponse) callback,
338 				Result function(Temp) transformer)
339 					if (isValidTransformedType!Result) {
340 				return Any!(Result)(path, null, callback, transformer);
341 			}
342 
343 			/++
344 			 + Adds a handler for all method types on the given path.
345 			 +
346 			 + Params:
347 			 +     path        = The path that gets handled.
348 	 		 +     contentType = The content type header to include in the response.
349 			 +     callback    = The handler that gets called for requests.
350 			 +     transformer = The transformer function that converts output.
351 			 ++/
352 			void Any(Result = string)(string path, string contentType,
353 				Temp function(HTTPServerRequest, HTTPServerResponse) callback,
354 				Result function(Temp) transformer)
355 					if (isValidTransformedType!Result) {
356 				foreach (method; EnumMembers!HTTPMethod) {
357 					// Match each HTTP method type.
358 					Match!(Temp)(method, path, contentType, callback, transformer);
359 				}
360 			}
361 
362 			/++
363 			 + Adds a handler for all method types on the given path.
364 			 +
365 			 + Params:
366 			 +     path        = The path that gets handled.
367 			 +     callback    = The handler that gets called for requests.
368 			 +     transformer = The transformer delegate that converts output.
369 			 ++/
370 			void Any(Result = string)(string path,
371 				Temp delegate(HTTPServerRequest, HTTPServerResponse) callback,
372 				Result delegate(Temp) transformer)
373 					if (isValidTransformedType!Result) {
374 				return Any!(Result)(path, null, callback, transformer);
375 			}
376 
377 			/++
378 			 + Adds a handler for all method types on the given path.
379 			 +
380 			 + Params:
381 			 +     path        = The path that gets handled.
382 	 		 +     contentType = The content type header to include in the response.
383 			 +     callback    = The handler that gets called for requests.
384 			 +     transformer = The transformer delegate that converts output.
385 			 ++/
386 			void Any(Result = string)(string path, string contentType,
387 				Temp delegate(HTTPServerRequest, HTTPServerResponse) callback,
388 				Result delegate(Temp) transformer)
389 					if (isValidTransformedType!Result) {
390 				foreach (method; EnumMembers!HTTPMethod) {
391 					// Match each HTTP method type.
392 					Match!(Temp)(method, path, contentType, callback, transformer);
393 				}
394 			}
395 		}
396 	}
397 
398 	/++
399 	 + A template that generates the source for HTTP methods.
400 	 +
401 	 + Params:
402 	 +     method = The name of the method to produce.
403 	 ++/
404 	private template GetHTTPMethodCode(string method) {
405 		import std.string;
406 
407 		enum GetHTTPMethodCode = format(`
408 			void %1$s(Result)(string path,
409 				Result function(HTTPServerRequest, HTTPServerResponse) callback)
410 			if(isValidResultType!Result)
411 			{
412 				%1$s!(Result)(path, null, callback);
413 			}
414 
415 			void %1$s(Result)(string path, string contentType,
416 				Result function(HTTPServerRequest, HTTPServerResponse) callback)
417 			if(isValidResultType!Result)
418 			{
419 				Match(HTTPMethod.%2$s, path, contentType, callback);
420 			}
421 
422 			void %1$s(Result)(string path,
423 				Result delegate(HTTPServerRequest, HTTPServerResponse) callback)
424 			if(isValidResultType!Result)
425 			{
426 				%1$s!(Result)(path, null, callback);
427 			}
428 
429 			void %1$s(Result)(string path, string contentType,
430 				Result delegate(HTTPServerRequest, HTTPServerResponse) callback)
431 			if(isValidResultType!Result)
432 			{
433 				Match(HTTPMethod.%2$s, path, contentType, callback);
434 			}
435 
436 			template %1$s(Temp)
437 			if(!is(Temp == void))
438 			{
439 				static if(!is(Temp == void))
440 				{
441 					void %1$s(Result = string)(string path,
442 						Temp function(HTTPServerRequest, HTTPServerResponse) callback,
443 						Result function(Temp) transformer)
444 					if(isValidTransformedType!Result)
445 					{
446 						%1$s!(Result)(path, null, callback, transformer);
447 					}
448 
449 					void %1$s(Result = string)(string path, string contentType,
450 						Temp function(HTTPServerRequest, HTTPServerResponse) callback,
451 						Result function(Temp) transformer)
452 					if(isValidTransformedType!Result)
453 					{
454 						Match!(Temp)(
455 							HTTPMethod.%2$s, path, contentType, callback, transformer
456 						);
457 					}
458 
459 					void %1$s(Result = string)(string path,
460 						Temp delegate(HTTPServerRequest, HTTPServerResponse) callback,
461 						Result delegate(Temp) transformer)
462 					if(isValidTransformedType!Result)
463 					{
464 						%1$s!(Result)(path, null, callback, transformer);
465 					}
466 
467 					void %1$s(Result = string)(string path, string contentType,
468 						Temp delegate(HTTPServerRequest, HTTPServerResponse) callback,
469 						Result delegate(Temp) transformer)
470 					if(isValidTransformedType!Result)
471 					{
472 						Match!(Temp)(
473 							HTTPMethod.%2$s, path, contentType, callback, transformer
474 						);
475 					}
476 				}
477 			}
478 		`,// The Titlecase function name.
479 				method[0 .. 1].toUpper ~ method[1 .. $].toLower,// The UPPERCASE HTTP method name.
480 				method.toUpper
481 			);
482 	}
483 
484 	static if (GenerateAll) {
485 		// Include all supported methods.
486 		private enum HTTPEnabledMethodList = __traits(allMembers, HTTPMethod);
487 	} else {
488 		// Include only common methods.
489 		private enum HTTPEnabledMethodList = TypeTuple!(
490 				"GET", "POST", "PUT", "PATCH", "DELETE",
491 				"HEAD", "OPTIONS", "CONNECT", "TRACE"
492 			);
493 	}
494 
495 	// Generate methods.
496 	mixin(
497 		joiner([
498 				staticMap!(
499 				GetHTTPMethodCode,
500 				HTTPEnabledMethodList
501 				)
502 			]).text
503 	);
504 
505 	/++
506 	 + Matches a path and method type using a function callback.
507 	 +
508 	 + Params:
509 	 +     method   = The HTTP method matched.
510 	 +     path     = The path assigned to this route.
511 	 +     callback = A function callback handler for the route.
512 	 ++/
513 	void Match(Result)(HTTPMethod method, string path,
514 		Result function(HTTPServerRequest, HTTPServerResponse) callback)
515 			if (isValidResultType!Result) {
516 		// Wrap the function in a delegate.
517 		Match!(Result)(method, path, null, callback);
518 	}
519 
520 	/++
521 	 + Matches a path and method type using a function callback.
522 	 +
523 	 + Params:
524 	 +     method      = The HTTP method matched.
525 	 +     path        = The path assigned to this route.
526 	 +     contentType = The content type header to include in the response.
527 	 +     callback    = A function callback handler for the route.
528 	 ++/
529 	void Match(Result)(HTTPMethod method, string path, string contentType,
530 		Result function(HTTPServerRequest, HTTPServerResponse) callback)
531 			if (isValidResultType!Result) {
532 		// Wrap the function in a delegate.
533 		Match!(Result)(method, path, contentType, toDelegate(callback));
534 	}
535 
536 	/++
537 	 + Matches a path and method type using a delegate callback.
538 	 +
539 	 + Params:
540 	 +     method   = The HTTP method matched.
541 	 +     path     = The path assigned to this route.
542 	 +     callback = A delegate callback handler for the route.
543 	 ++/
544 	void Match(Result)(HTTPMethod method, string path,
545 		Result delegate(HTTPServerRequest, HTTPServerResponse) callback)
546 			if (isValidResultType!Result) {
547 		return Match!(Result)(method, path, null, callback);
548 	}
549 
550 	/++
551 	 + Matches a path and method type using a delegate callback.
552 	 +
553 	 + Params:
554 	 +     method      = The HTTP method matched.
555 	 +     path        = The path assigned to this route.
556 	 +     contentType = The content type header to include in the response.
557 	 +     callback    = A delegate callback handler for the route.
558 	 ++/
559 	void Match(Result)(HTTPMethod method, string path, string contentType,
560 		Result delegate(HTTPServerRequest, HTTPServerResponse) callback)
561 			if (isValidResultType!Result) {
562 		router.match(method, path, (HTTPServerRequest req, HTTPServerResponse res) {
563 			try {
564 				// Invoke before-filters.
565 				applyFilterCallback(beforeCallbacks, path, req, res);
566 
567 				static if (!is(Result == void)) {
568 					// Call the callback and save the result.
569 					auto result = callback(req, res);
570 				} else {
571 					// Call the callback; no result.
572 					callback(req, res);
573 					auto result = "";
574 				}
575 
576 				// Invoke after-filters.
577 				applyFilterCallback(afterCallbacks, path, req, res);
578 
579 				// Just send an empty response.
580 				res.writeBody(result, contentType);
581 			} catch (Throwable t) {
582 				handleException(t, req, res);
583 			}
584 		});
585 	}
586 
587 	template Match(Temp) if (!is(Temp == void)) {
588 		static if (!is(Temp == void)) {
589 			/++
590 			 + Matches a path and method type using a function callback.
591 			 +
592 			 + Params:
593 			 +     method      = The HTTP method matched.
594 			 +     path        = The path assigned to this route.
595 			 +     callback    = A function callback handler for the route.
596 			 +     transformer = A transformer that converts the handler's output.
597 			 ++/
598 			void Match(Result = string)(HTTPMethod method, string path,
599 				Temp function(HTTPServerRequest, HTTPServerResponse) callback,
600 				Result function(Temp) transformer)
601 					if (isValidTransformedType!Result) {
602 				Match!(Result)(method, path, null, callback, transformer);
603 			}
604 
605 			/++
606 			 + Matches a path and method type using a function callback.
607 			 +
608 			 + Params:
609 			 +     method      = The HTTP method matched.
610 			 +     path        = The path assigned to this route.
611 	 		 +     contentType = The content type header to include in the response.
612 			 +     callback    = A function callback handler for the route.
613 			 +     transformer = A transformer that converts the handler's output.
614 			 ++/
615 			void Match(Result = string)(HTTPMethod method, string path, string contentType,
616 				Temp function(HTTPServerRequest, HTTPServerResponse) callback,
617 				Result function(Temp) transformer)
618 					if (isValidTransformedType!Result) {
619 				// Wrap the function in a delegate.
620 				Match!(Result)(
621 					method, path, contentType, toDelegate(callback), toDelegate(transformer)
622 				);
623 			}
624 
625 			/++
626 			 + Matches a path and method type using a delegate callback.
627 			 +
628 			 + Params:
629 			 +     method      = The HTTP method matched.
630 			 +     path        = The path assigned to this route.
631 			 +     callback    = A delegate callback handler for the route.
632 			 +     transformer = A transformer that converts the handler's output.
633 			 ++/
634 			void Match(Result = string)(HTTPMethod method, string path,
635 				Temp delegate(HTTPServerRequest, HTTPServerResponse) callback,
636 				Result delegate(Temp) transformer)
637 					if (isValidTransformedType!Result) {
638 				Match!(Result)(method, path, null, callback, transformer);
639 			}
640 
641 			/++
642 			 + Matches a path and method type using a delegate callback.
643 			 +
644 			 + Params:
645 			 +     method      = The HTTP method matched.
646 			 +     path        = The path assigned to this route.
647 	 		 +     contentType = The content type header to include in the response.
648 			 +     callback    = A delegate callback handler for the route.
649 			 +     transformer = A transformer that converts the handler's output.
650 			 ++/
651 			void Match(Result = string)(HTTPMethod method, string path, string contentType,
652 				Temp delegate(HTTPServerRequest, HTTPServerResponse) callback,
653 				Result delegate(Temp) transformer)
654 					if (isValidTransformedType!Result) {
655 				router.match(method, path, (HTTPServerRequest req, HTTPServerResponse res) {
656 					try {
657 						// Invoke before-filters.
658 						applyFilterCallback(beforeCallbacks, path, req, res);
659 
660 						// Transform the result into a string.
661 						string result = transformer(callback(req, res));
662 
663 						// Invoke after-filters.
664 						applyFilterCallback(afterCallbacks, path, req, res);
665 
666 						// Just send the response.
667 						res.writeBody(result, contentType);
668 					} catch (Throwable t) {
669 						handleException(t, req, res);
670 					}
671 				});
672 			}
673 		}
674 	}
675 
676 	private {
677 
678 		/++
679 		 + Adds a filter to a filter callback table.
680 		 +
681 		 + Params:
682 		 +     filterTable = The table to add the callback to.
683 		 +     path        = The path the callback runs on.
684 		 +     callback    = The callback to add.
685 		 ++/
686 		void addFilterCallback(ref VoidCallback[][string] filterTable,/+ @Nullable +/
687 			string path, VoidCallback callback) {
688 			// Check if the path has callbacks.
689 			auto ptr = path in filterTable;
690 
691 			if (ptr is null) {
692 				filterTable[path] = [callback];
693 			} else {
694 				*ptr ~= callback;
695 			}
696 		}
697 
698 		/++
699 		 + Matches a filter to a path and invokes matched callbacks.
700 		 +
701 		 + Params:
702 		 +     table = The table of callbacks to scan.
703 		 +     path  = The path to be matched.
704 		 +     req   = The server request object.
705 		 +     res   = The server response object. 
706 		 ++/
707 		void applyFilterCallback(ref VoidCallback[][string] table, string path,
708 			HTTPServerRequest req, HTTPServerResponse res) {
709 			// Search for matching callbacks.
710 			foreach (callbackPath, callbacks; table) {
711 				bool matches = true;
712 
713 				if (callbackPath !is null) {
714 					// Substitue wildwards.
715 					import std.array : replace;
716 
717 					string pattern = callbackPath.replace("*", ".*?");
718 
719 					// Check the pattern for a match.
720 					import std.regex : matchFirst;
721 
722 					matches = !path.matchFirst(pattern).empty;
723 				}
724 
725 				if (matches) {
726 					// Invoke matched callbacks.
727 					foreach (callback; callbacks) {
728 						callback(req, res);
729 					}
730 				}
731 			}
732 		}
733 
734 		/++
735 		 + Matches a throwable type and invokes its handler.
736 		 +
737 		 + Params:
738 		 +     t   = The throwable being matched.
739 		 +     req = The server request object.
740 		 +     res = The server response object.
741 		 ++/
742 		void handleException(Throwable t, HTTPServerRequest req, HTTPServerResponse res) {
743 			foreach (typeinfo, handler; exceptionCallbacks) {
744 				if (_d_isbaseof(t.classinfo, typeinfo)) {
745 					// Forward error to handler.
746 					handler(t, req, res);
747 					return;
748 				}
749 			}
750 
751 			// Rethrow.
752 			throw t;
753 		}
754 
755 	}
756 
757 }