Fawkes API Fawkes Development Version
router.h
1
2/***************************************************************************
3 * router.h - Webview Router
4 *
5 * Created: Thu Mar 29 21:45:17 2018
6 * Copyright 2006-2018 Tim Niemueller [www.niemueller.de]
7 ****************************************************************************/
8
9/* This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Library General Public License for more details.
18 *
19 * Read the full text in the LICENSE.GPL file in the doc directory.
20 */
21
22#ifndef _LIBS_WEBVIEW_ROUTER_H_
23#define _LIBS_WEBVIEW_ROUTER_H_
24
25#include <core/exceptions/software.h>
26#include <webview/request.h>
27
28#include <algorithm>
29#include <list>
30#include <map>
31#include <regex>
32#include <string>
33
34namespace fawkes {
35
36/** URL path router.
37 * Register URL path patterns and some handler or item. Then match it
38 * later to request URLs to retrieve this very handler or item if the
39 * pattern matches the URL.
40 * @author Tim Niemueller
41 */
42template <typename T>
44{
45public:
46 /** Find a handler.
47 * @param request incoming request object
48 * @param path_args upon successful completion, will contain mappings from
49 * path patterns to matched segments of the URL.
50 * @return matched handler
51 * @exception NullPointerException thrown if no handler could be found
52 */
53 T &
54 find_handler(const WebRequest *request, std::map<std::string, std::string> &path_args)
55 {
56 auto ri =
57 std::find_if(routes_.begin(), routes_.end(), [this, &path_args, request](auto &r) -> bool {
58 //printf("Comparing %s to %s\n", request->url().c_str(), std::get<2>(r).c_str());
59 return (std::get<1>(r) == request->method()
60 && this->path_match(request->url(), std::get<3>(r), path_args));
61 });
62 if (ri == routes_.end()) {
63 throw NullPointerException("No handler found");
64 }
65 return std::get<4>(*ri);
66 }
67
68 /** Find a handler.
69 * @param method HTTP method of the request
70 * @param path path to match against stored paths
71 * @param path_args upon successful completion, will contain mappings from
72 * path patterns to matched segments of the URL.
73 * @return matched handler
74 * @exception NullPointerException thrown if no handler could be found
75 */
76 T &
78 const std::string & path,
79 std::map<std::string, std::string> &path_args)
80 {
81 auto ri = std::find_if(
82 routes_.begin(), routes_.end(), [this, &path_args, &method, &path](auto &r) -> bool {
83 return (std::get<1>(r) == method && this->path_match(path, std::get<3>(r), path_args));
84 });
85 if (ri == routes_.end()) {
86 throw NullPointerException("No handler found");
87 }
88 return std::get<4>(*ri);
89 }
90
91 /** Add a handler with weight.
92 * @param method HTTP method to match for
93 * @param path path pattern. A pattern may contain "{var}" segments
94 * for a URL. These will match an element of the path, i.e., a string not
95 * containing a slash. If a pattern has the form {var+} then it may contain
96 * a slash and therefore match multiple path segments. The handler would
97 * receive an entry named "var" in the parameters path arguments.
98 * @param handler handler to store
99 * @param weight higher weight means the handler is tried later by
100 * the router. The default is 0.
101 */
102 void
103 add(WebRequest::Method method, const std::string &path, T handler, int weight)
104 {
105 auto ri =
106 std::find_if(routes_.begin(), routes_.end(), [method, &path, &weight](auto &r) -> bool {
107 return (std::get<0>(r) == weight && std::get<1>(r) == method && std::get<2>(r) == path);
108 });
109 if (ri != routes_.end()) {
110 throw Exception("URL handler already registered for %s", path.c_str());
111 }
112 routes_.push_back(std::make_tuple(weight, method, path, gen_regex(path), handler));
113 routes_.sort(
114 [](const auto &a, const auto &b) -> bool { return (std::get<0>(a) < std::get<0>(b)); });
115 }
116
117 /** Add a handler.
118 * @param method HTTP method to match for
119 * @param path path pattern. A pattern may contain "{var}" segments
120 * for a URL. These will match an element of the path, i.e., a string not
121 * containing a slash. If a pattern has the form {var+} then it may contain
122 * a slash and therefore match multiple path segments. The handler would
123 * receive an entry named "var" in the parameters path arguments.
124 * @param handler handler to store
125 */
126 void
127 add(WebRequest::Method method, const std::string &path, T handler)
128 {
129 add(method, path, handler, 0);
130 }
131
132 /** Remove a handler.
133 * @param method HTTP method to match for
134 * @param path path pattern that equals the one given when adding.
135 */
136 void
137 remove(WebRequest::Method method, const std::string &path)
138 {
139 auto ri = std::find_if(routes_.begin(), routes_.end(), [method, &path](auto &r) -> bool {
140 return (std::get<1>(r) == method && std::get<2>(r) == path);
141 });
142 if (ri != routes_.end()) {
143 routes_.erase(ri);
144 }
145 }
146
147private:
148 typedef std::pair<std::regex, std::vector<std::string>> path_regex;
149
150 std::pair<std::regex, std::vector<std::string>>
151 gen_regex(const std::string &path)
152 {
153 std::string::size_type pos = 0;
154
155 if (path[0] != '/') {
156 throw Exception("Path '%s' must start with /", path.c_str());
157 }
158 if ((pos = path.find_first_of("[]()^$")) != std::string::npos) {
159 throw Exception("Found illegal character '%c' at position '%zu' in '%s'",
160 path[pos],
161 pos,
162 path.c_str());
163 }
164
165 std::regex to_re("\\{([^+*\\}]+?)[+*]?\\}");
166 std::string m_path = path;
167 // escape special characters for regex
168 pos = 0;
169 while ((pos = m_path.find_first_of(".", pos)) != std::string::npos) {
170 m_path.replace(pos, 1, "\\.");
171 pos += 2;
172 }
173 pos = 0;
174 while ((pos = m_path.find_first_of("+*", pos)) != std::string::npos) {
175 if (pos < m_path.length() - 1 && m_path[pos + 1] != '}') {
176 m_path.replace(pos, 1, std::string("\\") + m_path[pos]);
177 pos += 2;
178 } else {
179 pos += 1;
180 }
181 }
182 std::string re_url;
183 std::smatch match;
184 std::vector<std::string> match_indexes;
185 while (regex_search(m_path, match, to_re)) {
186 std::string full_match = match[0];
187 re_url += match.prefix();
188 if (full_match[full_match.length() - 2] == '+') {
189 re_url += "(.+?)";
190 } else if (full_match[full_match.length() - 2] == '*') {
191 re_url += "(.*)";
192 } else {
193 re_url += "([^/]+?)";
194 }
195 match_indexes.push_back(match[1]);
196 m_path = match.suffix();
197 }
198 re_url += m_path;
199 //printf("Regex: %s -> %s\n", path.c_str(), re_url.c_str());
200
201 return std::make_pair(std::regex(re_url), match_indexes);
202 }
203
204 /** Check if an actual path matches an API path pattern.
205 * @param url requested
206 * @param api_path configured API path to check
207 * @param params object to set argument mappings
208 * @return true if the path cold be matched, false otherwise.
209 */
210 bool
211 path_match(const std::string & url,
212 const path_regex & path_re,
213 std::map<std::string, std::string> &path_args)
214 {
215 std::smatch matches;
216 if (std::regex_match(url, matches, path_re.first)) {
217 if (matches.size() != path_re.second.size() + 1) {
218 return false;
219 }
220 for (size_t i = 0; i < path_re.second.size(); ++i) {
221 //printf("arg %s = %s\n", path_re.second[i].c_str(), matches[i+1].str().c_str());
222 path_args[path_re.second[i]] = matches[i + 1].str();
223 }
224 return true;
225 } else {
226 return false;
227 }
228 }
229
230private:
231 std::list<std::tuple<int, WebRequest::Method, std::string, path_regex, T>> routes_;
232};
233
234} // end of namespace fawkes
235
236#endif
Base class for exceptions in Fawkes.
Definition: exception.h:36
A NULL pointer was supplied where not allowed.
Definition: software.h:32
Web request meta data carrier.
Definition: request.h:42
Method
HTTP transfer methods.
Definition: request.h:47
URL path router.
Definition: router.h:44
void add(WebRequest::Method method, const std::string &path, T handler, int weight)
Add a handler with weight.
Definition: router.h:103
void add(WebRequest::Method method, const std::string &path, T handler)
Add a handler.
Definition: router.h:127
void remove(WebRequest::Method method, const std::string &path)
Remove a handler.
Definition: router.h:137
T & find_handler(const WebRequest *request, std::map< std::string, std::string > &path_args)
Find a handler.
Definition: router.h:54
T & find_handler(WebRequest::Method method, const std::string &path, std::map< std::string, std::string > &path_args)
Find a handler.
Definition: router.h:77
Fawkes library namespace.