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