1 /*
2  * Router - A highly performant web framework written in D.
3  *
4  * Copyright (C) 2021-2022 Kerisy.com
5  *
6  * Website: https://www.kerisy.com
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module archttp.Router;
13 
14 public import archttp.Route;
15 public import archttp.HttpMethod;
16 
17 import archttp.Archttp;
18 
19 import std.stdio;
20 
21 import std.regex : regex, match, matchAll;
22 import std.array : replaceFirst;
23 import std.uri : decode;
24 
25 class Router(RoutingHandler, MiddlewareHandler)
26 {
27     private
28     {
29         Route!(RoutingHandler, MiddlewareHandler)[string] _routes;
30         Route!(RoutingHandler, MiddlewareHandler)[string] _regexRoutes;
31         MiddlewareHandler[] _middlewareHandlers;
32         Archttp _app;
33     }
34     
35     Router add(string path, HttpMethod method, RoutingHandler handler)
36     {
37         auto route = _routes.get(path, null);
38 
39         if (route is null)
40         {
41             route = _regexRoutes.get(path, null);
42         }
43 
44         if (route is null)
45         {
46             route = CreateRoute(path, method, handler);
47 
48             if (route.regular)
49             {
50                 _regexRoutes[path] = route;
51             }
52             else
53             {
54                 _routes[path] = route;
55             }
56         }
57         else
58         {
59             route.bindMethod(method, handler);
60         }
61 
62         return this;
63     }
64 
65     Router get(string route, RoutingHandler handler)
66     {
67         add(route, HttpMethod.GET, handler);
68         return this;
69     }
70 
71     Router post(string route, RoutingHandler handler)
72     {
73         add(route, HttpMethod.POST, handler);
74         return this;
75     }
76 
77     Router put(string route, RoutingHandler handler)
78     {
79         add(route, HttpMethod.PUT, handler);
80         return this;
81     }
82 
83     Router Delete(string route, RoutingHandler handler)
84     {
85         add(route, HttpMethod.DELETE, handler);
86         return this;
87     }
88 
89     Router use(MiddlewareHandler handler)
90     {
91         _middlewareHandlers ~= handler;
92         return this;
93     }
94 
95     void onMount(Archttp app)
96     {
97         _app = app;
98     }
99 
100     MiddlewareHandler[] middlewareHandlers()
101     {
102         return _middlewareHandlers;
103     }
104 
105     private Route!(RoutingHandler, MiddlewareHandler) CreateRoute(string path, HttpMethod method, RoutingHandler handler)
106     {
107         auto route = new Route!(RoutingHandler, MiddlewareHandler)(path, method, handler);
108 
109         auto matches = path.matchAll(regex(`\{(\w+)(:([^\}]+))?\}`));
110         if (matches)
111         {
112             string[uint] paramKeys;
113             int paramCount = 0;
114             string pattern = path;
115             string urlTemplate = path;
116 
117             foreach (m; matches)
118             {
119                 paramKeys[paramCount] = m[1];
120                 string reg = m[3].length ? m[3] : "\\w+";
121                 pattern = pattern.replaceFirst(m[0], "(" ~ reg ~ ")");
122                 urlTemplate = urlTemplate.replaceFirst(m[0], "{" ~ m[1] ~ "}");
123                 paramCount++;
124             }
125 
126             route.pattern = pattern;
127             route.paramKeys = paramKeys;
128             route.regular = true;
129             route.urlTemplate = urlTemplate;
130         }
131 
132         return route;
133     }
134 
135     RoutingHandler match(string path, HttpMethod method, ref MiddlewareHandler[] middlewareHandlers, ref string[string] params)
136     {
137         auto route = _routes.get(path, null);
138 
139         if (route is null)
140         {
141             foreach ( r ; _regexRoutes )
142             {
143                 auto matched = path.match(regex(r.pattern));
144 
145                 if (matched)
146                 {
147                     route = r;
148                     
149                     foreach ( i, key ; route.paramKeys )
150                     {
151                         params[key] = decode(matched.captures[i + 1]);
152                     }
153                 }
154             }
155         }
156 
157         if (route is null)
158         {
159             writeln(path, " is Not Found.");
160             return cast(RoutingHandler) null;
161         }
162 
163         RoutingHandler handler;
164         handler = route.find(method);
165 
166         if (handler is null)
167         {
168             writeln("Request: ", path, " method ", method, " is Not Allowed.");
169             return cast(RoutingHandler) null;
170         }
171 
172         middlewareHandlers = route.middlewareHandlers();
173 
174         return handler;
175     }
176 }