/src/uWebSockets/src/HttpRouter.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Authored by Alex Hultman, 2018-2020. |
3 | | * Intellectual property of third-party. |
4 | | |
5 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
6 | | * you may not use this file except in compliance with the License. |
7 | | * You may obtain a copy of the License at |
8 | | |
9 | | * http://www.apache.org/licenses/LICENSE-2.0 |
10 | | |
11 | | * Unless required by applicable law or agreed to in writing, software |
12 | | * distributed under the License is distributed on an "AS IS" BASIS, |
13 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 | | * See the License for the specific language governing permissions and |
15 | | * limitations under the License. |
16 | | */ |
17 | | |
18 | | #ifndef UWS_HTTPROUTER_HPP |
19 | | #define UWS_HTTPROUTER_HPP |
20 | | |
21 | | #include <map> |
22 | | #include <vector> |
23 | | #include <cstring> |
24 | | #include <string_view> |
25 | | #include <string> |
26 | | #include <algorithm> |
27 | | #include <memory> |
28 | | #include <utility> |
29 | | |
30 | | #include <iostream> |
31 | | |
32 | | #include "MoveOnlyFunction.h" |
33 | | |
34 | | namespace uWS { |
35 | | |
36 | | template <class USERDATA> |
37 | | struct HttpRouter { |
38 | | /* These are public for now */ |
39 | | std::vector<std::string> upperCasedMethods = {"GET", "POST", "HEAD", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"}; |
40 | | static const uint32_t HIGH_PRIORITY = 0xd0000000, MEDIUM_PRIORITY = 0xe0000000, LOW_PRIORITY = 0xf0000000; |
41 | | |
42 | | private: |
43 | | USERDATA userData; |
44 | | static const unsigned int MAX_URL_SEGMENTS = 100; |
45 | | |
46 | | /* Handler ids are 32-bit */ |
47 | | static const uint32_t HANDLER_MASK = 0x0fffffff; |
48 | | |
49 | | /* Methods and their respective priority */ |
50 | | std::map<std::string, int> priority; |
51 | | |
52 | | /* List of handlers */ |
53 | | std::vector<MoveOnlyFunction<bool(HttpRouter *)>> handlers; |
54 | | |
55 | | /* Current URL cache */ |
56 | | std::string_view currentUrl; |
57 | | std::string_view urlSegmentVector[MAX_URL_SEGMENTS]; |
58 | | int urlSegmentTop; |
59 | | |
60 | | /* The matching tree */ |
61 | | struct Node { |
62 | | std::string name; |
63 | | std::vector<std::unique_ptr<Node>> children; |
64 | | std::vector<uint32_t> handlers; |
65 | | bool isHighPriority; |
66 | | |
67 | 20 | Node(std::string name) : name(name) {} |
68 | | } root = {"rootNode"}; |
69 | | |
70 | | /* Sort wildcards after alphanum */ |
71 | 12 | int lexicalOrder(std::string &name) { |
72 | 12 | if (!name.length()) { |
73 | 0 | return 2; |
74 | 0 | } |
75 | 12 | if (name[0] == ':') { |
76 | 4 | return 1; |
77 | 4 | } |
78 | 8 | if (name[0] == '*') { |
79 | 4 | return 0; |
80 | 4 | } |
81 | 4 | return 2; |
82 | 8 | } |
83 | | |
84 | | /* Advance from parent to child, adding child if necessary */ |
85 | 22 | Node *getNode(Node *parent, std::string child, bool isHighPriority) { |
86 | 22 | for (std::unique_ptr<Node> &node : parent->children) { |
87 | 12 | if (node->name == child && node->isHighPriority == isHighPriority) { |
88 | 4 | return node.get(); |
89 | 4 | } |
90 | 12 | } |
91 | | |
92 | | /* Insert sorted, but keep order if parent is root (we sort methods by priority elsewhere) */ |
93 | 18 | std::unique_ptr<Node> newNode(new Node(child)); |
94 | 18 | newNode->isHighPriority = isHighPriority; |
95 | 18 | return parent->children.emplace(std::upper_bound(parent->children.begin(), parent->children.end(), newNode, [parent, this](auto &a, auto &b) { |
96 | | |
97 | 8 | if (a->isHighPriority != b->isHighPriority) { |
98 | 0 | return a->isHighPriority; |
99 | 0 | } |
100 | | |
101 | 8 | return b->name.length() && (parent != &root) && (lexicalOrder(b->name) < lexicalOrder(a->name)); |
102 | 8 | }), std::move(newNode))->get(); |
103 | 22 | } |
104 | | |
105 | | /* Basically a pre-allocated stack */ |
106 | | struct RouteParameters { |
107 | | friend struct HttpRouter; |
108 | | private: |
109 | | std::string_view params[MAX_URL_SEGMENTS]; |
110 | | int paramsTop; |
111 | | |
112 | 10.0k | void reset() { |
113 | 10.0k | paramsTop = -1; |
114 | 10.0k | } |
115 | | |
116 | 8.83k | void push(std::string_view param) { |
117 | | /* We check these bounds indirectly via the urlSegments limit */ |
118 | 8.83k | params[++paramsTop] = param; |
119 | 8.83k | } |
120 | | |
121 | 130 | void pop() { |
122 | | /* Same here, we cannot pop outside */ |
123 | 130 | paramsTop--; |
124 | 130 | } |
125 | | } routeParameters; |
126 | | |
127 | | /* Set URL for router. Will reset any URL cache */ |
128 | 10.0k | inline void setUrl(std::string_view url) { |
129 | | |
130 | | /* Todo: URL may also start with "http://domain/" or "*", not only "/" */ |
131 | | |
132 | | /* We expect to stand on a slash */ |
133 | 10.0k | currentUrl = url; |
134 | 10.0k | urlSegmentTop = -1; |
135 | 10.0k | } |
136 | | |
137 | | /* Lazily parse or read from cache */ |
138 | 23.1k | inline std::pair<std::string_view, bool> getUrlSegment(int urlSegment) { |
139 | 23.1k | if (urlSegment > urlSegmentTop) { |
140 | | /* Signal as STOP when we have no more URL or stack space */ |
141 | 22.8k | if (!currentUrl.length() || urlSegment > 99) { |
142 | 7.67k | return {{}, true}; |
143 | 7.67k | } |
144 | | |
145 | | /* We always stand on a slash here, so step over it */ |
146 | 15.1k | currentUrl.remove_prefix(1); |
147 | | |
148 | 15.1k | auto segmentLength = currentUrl.find('/'); |
149 | 15.1k | if (segmentLength == std::string::npos) { |
150 | 8.99k | segmentLength = currentUrl.length(); |
151 | | |
152 | | /* Push to url segment vector */ |
153 | 8.99k | urlSegmentVector[urlSegment] = currentUrl.substr(0, segmentLength); |
154 | 8.99k | urlSegmentTop++; |
155 | | |
156 | | /* Update currentUrl */ |
157 | 8.99k | currentUrl = currentUrl.substr(segmentLength); |
158 | 8.99k | } else { |
159 | | /* Push to url segment vector */ |
160 | 6.19k | urlSegmentVector[urlSegment] = currentUrl.substr(0, segmentLength); |
161 | 6.19k | urlSegmentTop++; |
162 | | |
163 | | /* Update currentUrl */ |
164 | 6.19k | currentUrl = currentUrl.substr(segmentLength); |
165 | 6.19k | } |
166 | 15.1k | } |
167 | | /* In any case we return it */ |
168 | 15.5k | return {urlSegmentVector[urlSegment], false}; |
169 | 23.1k | } |
170 | | |
171 | | /* Executes as many handlers it can */ |
172 | 23.1k | bool executeHandlers(Node *parent, int urlSegment, USERDATA &userData) { |
173 | | |
174 | 23.1k | auto [segment, isStop] = getUrlSegment(urlSegment); |
175 | | |
176 | | /* If we are on STOP, return where we may stand */ |
177 | 23.1k | if (isStop) { |
178 | | /* We have reached accross the entire URL with no stoppage, execute */ |
179 | 7.65k | for (uint32_t handler : parent->handlers) { |
180 | 7.58k | if (handlers[handler & HANDLER_MASK](this)) { |
181 | 7.58k | return true; |
182 | 7.58k | } |
183 | 7.58k | } |
184 | | /* We reached the end, so go back */ |
185 | 77 | return false; |
186 | 7.65k | } |
187 | | |
188 | 18.3k | for (auto &p : parent->children) { |
189 | 18.3k | if (p->name.length() && p->name[0] == '*') { |
190 | | /* Wildcard match (can be seen as a shortcut) */ |
191 | 1.65k | for (uint32_t handler : p->handlers) { |
192 | 1.65k | if (handlers[handler & HANDLER_MASK](this)) { |
193 | 1.50k | return true; |
194 | 1.50k | } |
195 | 1.65k | } |
196 | 16.6k | } else if (p->name.length() && p->name[0] == ':' && segment.length()) { |
197 | | /* Parameter match */ |
198 | 8.83k | routeParameters.push(segment); |
199 | 8.83k | if (executeHandlers(p.get(), urlSegment + 1, userData)) { |
200 | 8.70k | return true; |
201 | 8.70k | } |
202 | 130 | routeParameters.pop(); |
203 | 7.81k | } else if (p->name == segment) { |
204 | | /* Static match */ |
205 | 5.03k | if (executeHandlers(p.get(), urlSegment + 1, userData)) { |
206 | 4.73k | return true; |
207 | 4.73k | } |
208 | 5.03k | } |
209 | 18.3k | } |
210 | 526 | return false; |
211 | 15.4k | } |
212 | | |
213 | | /* Scans for one matching handler, returning the handler and its priority or UINT32_MAX for not found */ |
214 | 8 | uint32_t findHandler(std::string method, std::string pattern, uint32_t priority) { |
215 | 10 | for (std::unique_ptr<Node> &node : root.children) { |
216 | 10 | if (method == node->name) { |
217 | 8 | setUrl(pattern); |
218 | 8 | Node *n = node.get(); |
219 | 22 | for (int i = 0; !getUrlSegment(i).second; i++) { |
220 | | /* Go to next segment or quit */ |
221 | 14 | std::string segment = std::string(getUrlSegment(i).first); |
222 | 14 | Node *next = nullptr; |
223 | 16 | for (std::unique_ptr<Node> &child : n->children) { |
224 | 16 | if (child->name == segment && child->isHighPriority == (priority == HIGH_PRIORITY)) { |
225 | 14 | next = child.get(); |
226 | 14 | break; |
227 | 14 | } |
228 | 16 | } |
229 | 14 | if (!next) { |
230 | 0 | return UINT32_MAX; |
231 | 0 | } |
232 | 14 | n = next; |
233 | 14 | } |
234 | | /* Seek for a priority match in the found node */ |
235 | 8 | for (unsigned int i = 0; i < n->handlers.size(); i++) { |
236 | 8 | if ((n->handlers[i] & ~HANDLER_MASK) == priority) { |
237 | 8 | return n->handlers[i]; |
238 | 8 | } |
239 | 8 | } |
240 | 0 | return UINT32_MAX; |
241 | 8 | } |
242 | 10 | } |
243 | 0 | return UINT32_MAX; |
244 | 8 | } |
245 | | |
246 | | public: |
247 | 2 | HttpRouter() { |
248 | 2 | int p = 0; |
249 | 18 | for (std::string &method : upperCasedMethods) { |
250 | 18 | priority[method] = p++; |
251 | 18 | } |
252 | 2 | } |
253 | | |
254 | 9.23k | std::pair<int, std::string_view *> getParameters() { |
255 | 9.23k | return {routeParameters.paramsTop, routeParameters.params}; |
256 | 9.23k | } |
257 | | |
258 | 10.0k | USERDATA &getUserData() { |
259 | 10.0k | return userData; |
260 | 10.0k | } |
261 | | |
262 | | /* Fast path */ |
263 | 10.0k | bool route(std::string_view method, std::string_view url) { |
264 | | /* Reset url parsing cache */ |
265 | 10.0k | setUrl(url); |
266 | 10.0k | routeParameters.reset(); |
267 | | |
268 | | /* Begin by finding the method node */ |
269 | 12.2k | for (auto &p : root.children) { |
270 | 12.2k | if (p->name == method) { |
271 | | /* Then route the url */ |
272 | 9.24k | return executeHandlers(p.get(), 0, userData); |
273 | 9.24k | } |
274 | 12.2k | } |
275 | | |
276 | | /* We did not find any handler for this method and url */ |
277 | 766 | return false; |
278 | 10.0k | } |
279 | | |
280 | | /* Adds the corresponding entires in matching tree and handler list */ |
281 | 8 | void add(std::vector<std::string> methods, std::string pattern, MoveOnlyFunction<bool(HttpRouter *)> &&handler, uint32_t priority = MEDIUM_PRIORITY) { |
282 | 8 | for (std::string method : methods) { |
283 | | /* Lookup method */ |
284 | 8 | Node *node = getNode(&root, method, false); |
285 | | /* Iterate over all segments */ |
286 | 8 | setUrl(pattern); |
287 | 22 | for (int i = 0; !getUrlSegment(i).second; i++) { |
288 | 14 | node = getNode(node, std::string(getUrlSegment(i).first), priority == HIGH_PRIORITY); |
289 | 14 | } |
290 | | /* Insert handler in order sorted by priority (most significant 1 byte) */ |
291 | 8 | node->handlers.insert(std::upper_bound(node->handlers.begin(), node->handlers.end(), (uint32_t) (priority | handlers.size())), (uint32_t) (priority | handlers.size())); |
292 | 8 | } |
293 | | |
294 | | /* Alloate this handler */ |
295 | 8 | handlers.emplace_back(std::move(handler)); |
296 | | |
297 | | /* Assume can find this handler again */ |
298 | 8 | if (((handlers.size() - 1) | priority) != findHandler(methods[0], pattern, priority)) { |
299 | 0 | std::cerr << "Error: Internal routing error" << std::endl; |
300 | 0 | std::abort(); |
301 | 0 | } |
302 | 8 | } |
303 | | |
304 | | bool cullNode(Node *parent, Node *node, uint32_t handler) { |
305 | | /* For all children */ |
306 | | for (unsigned int i = 0; i < node->children.size(); ) { |
307 | | /* Optimization todo: only enter those with same isHighPrioirty */ |
308 | | /* Enter child so we get depth first */ |
309 | | if (!cullNode(node, node->children[i].get(), handler)) { |
310 | | /* Only increase if this node was not removed */ |
311 | | i++; |
312 | | } |
313 | | } |
314 | | |
315 | | /* Cull this node (but skip the root node) */ |
316 | | if (parent /*&& parent != &root*/) { |
317 | | /* Scan for equal (remove), greater (lower by 1) */ |
318 | | for (auto it = node->handlers.begin(); it != node->handlers.end(); ) { |
319 | | if ((*it & HANDLER_MASK) > (handler & HANDLER_MASK)) { |
320 | | *it = ((*it & HANDLER_MASK) - 1) | (*it & ~HANDLER_MASK); |
321 | | } else if (*it == handler) { |
322 | | it = node->handlers.erase(it); |
323 | | continue; |
324 | | } |
325 | | it++; |
326 | | } |
327 | | |
328 | | /* If we have no children and no handlers, remove us from the parent->children list */ |
329 | | if (!node->handlers.size() && !node->children.size()) { |
330 | | parent->children.erase(std::find_if(parent->children.begin(), parent->children.end(), [node](const std::unique_ptr<Node> &a) { |
331 | | return a.get() == node; |
332 | | })); |
333 | | /* Returning true means we removed node from parent */ |
334 | | return true; |
335 | | } |
336 | | } |
337 | | |
338 | | return false; |
339 | | } |
340 | | |
341 | | /* Removes ALL routes with the same handler as can be found with the given parameters. |
342 | | * Removing a wildcard is done by removing ONE OF the methods the wildcard would match with. |
343 | | * Example: If wildcard includes POST, GET, PUT, you can remove ALL THREE by removing GET. */ |
344 | | void remove(std::string method, std::string pattern, uint32_t priority) { |
345 | | uint32_t handler = findHandler(method, pattern, priority); |
346 | | if (handler == UINT32_MAX) { |
347 | | /* Not found or already removed, do nothing */ |
348 | | return; |
349 | | } |
350 | | |
351 | | /* Cull the entire tree */ |
352 | | /* For all nodes in depth first tree traveral; |
353 | | * if node contains handler - remove the handler - |
354 | | * if node holds no handlers after removal, remove the node and return */ |
355 | | cullNode(nullptr, &root, handler); |
356 | | |
357 | | /* Now remove the actual handler */ |
358 | | handlers.erase(handlers.begin() + (handler & HANDLER_MASK)); |
359 | | } |
360 | | }; |
361 | | |
362 | | } |
363 | | |
364 | | #endif // UWS_HTTPROUTER_HPP |