1 module router; 2 3 import events; 4 import std..string : toUpper, toLower; 5 import std.regex; 6 7 package: 8 9 class Route(TRequest: IRoutedRequest, TResponse) : EventList!(void, TRequest, TResponse) { 10 private: 11 12 string _path; 13 Regex!char _compiledPath; 14 string[] _routeParams; 15 EventList!(void, TRequest, TResponse).Trigger _eventTrigger; 16 17 public: 18 19 this(string path) { 20 _path = path; 21 _eventTrigger = this.own; 22 _routeParams = extractRouteParams; 23 _compiledPath = compilePathRegex; 24 } 25 26 @property string path() { 27 return _path; 28 } 29 30 @property string[] routeParams() { 31 return _routeParams; 32 } 33 34 @property Regex!char compiledPath() { 35 return _compiledPath; 36 } 37 38 void execute(string uri, TRequest request, TResponse response) { 39 auto m = match(uri, _compiledPath); 40 string[string] params; 41 42 if(m.captures.length > 0) 43 { 44 for(int i = 1; i < m.captures.length; i++) { 45 params[_routeParams[i-1]] = m.captures[i]; 46 } 47 request.params = params; 48 _eventTrigger(request, response); 49 } 50 } 51 52 private: 53 string[] extractRouteParams() { 54 string[] keys; 55 string c; 56 57 foreach(m; match(_path, regex(r"(:\w+)", "gm"))){ 58 c = m.captures[1]; 59 keys ~= c[1..c.length]; 60 } 61 return keys; 62 } 63 64 Regex!char compilePathRegex() { 65 auto replaced = replaceAll(_path, regex(r"(:\w+)","g"), "([^/?#]+)"); 66 auto compiledRegexp = regex(replaced); 67 return compiledRegexp; 68 } 69 } 70 71 public: 72 73 interface IRoutedRequest { 74 @property { 75 string[string] params(); 76 void params(string[string] params); 77 } 78 } 79 80 class VerbHandler(TRequest : IRoutedRequest, TResponse) { 81 private: 82 string _verb; 83 Route!(TRequest, TResponse)[string] _routes; 84 85 public: 86 87 this(string normalizedVerb) { 88 _verb = normalizedVerb; 89 } 90 91 @property { 92 string verb() { 93 return _verb; 94 } 95 } 96 97 Route!(TRequest, TResponse) route(string path) { 98 Route!(TRequest, TResponse) route = null; 99 if(path in _routes) { 100 route = _routes[path]; 101 } 102 if(route is null) { 103 route = _routes[path] = new Route!(TRequest, TResponse)(path); 104 } 105 return route; 106 } 107 108 void execute(string path, TRequest request, TResponse response) { 109 foreach(r; _routes) { 110 r.execute(path, request, response); 111 } 112 } 113 } 114 115 private template BootstrapVerb(string methodName) { 116 const char[] BootstrapVerb = "EventList!(void, TRequest, TResponse) " ~ methodName.toLower ~ "(string path) { return this.map(\"" ~ methodName.toUpper ~ "\", path); }"; 117 } 118 119 120 class Router(TRequest : IRoutedRequest, TResponse) { 121 private: 122 VerbHandler!(TRequest, TResponse)[string] _verbs; 123 124 VerbHandler!(TRequest, TResponse) _getVerb(string verb) { 125 string normalizedVerb = verb.toUpper; 126 VerbHandler!(TRequest, TResponse) handler = null; 127 if(normalizedVerb in _verbs) { 128 handler = _verbs[normalizedVerb]; 129 } 130 if(handler is null) { 131 handler = _verbs[normalizedVerb] = new VerbHandler!(TRequest, TResponse)(normalizedVerb); 132 } 133 return handler; 134 } 135 136 public: 137 138 EventList!(void, TRequest, TResponse) map(string verb, string path) { 139 auto handler = _getVerb(verb); 140 return handler.route(path); 141 } 142 143 mixin(BootstrapVerb!"GET"); 144 mixin(BootstrapVerb!"HEAD"); 145 mixin(BootstrapVerb!"POST"); 146 mixin(BootstrapVerb!"PUT"); 147 148 void execute(string verb, string path, TRequest request, TResponse response) { 149 auto handler = _getVerb(verb); 150 handler.execute(path, request, response); 151 } 152 }