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 
34 namespace 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  */
42 template <typename T>
44 {
45 public:
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 
147 private:
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 
230 private:
231  std::list<std::tuple<int, WebRequest::Method, std::string, path_regex, T>> routes_;
232 };
233 
234 } // end of namespace fawkes
235 
236 #endif
fawkes::WebviewRouter::add
void add(WebRequest::Method method, const std::string &path, T handler)
Add a handler.
Definition: router.h:127
fawkes::WebRequest
Web request meta data carrier.
Definition: request.h:42
fawkes::WebviewRouter::find_handler
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
Fawkes library namespace.
fawkes::WebRequest::Method
Method
HTTP transfer methods.
Definition: request.h:47
fawkes::WebviewRouter::find_handler
T & find_handler(const WebRequest *request, std::map< std::string, std::string > &path_args)
Find a handler.
Definition: router.h:54
fawkes::NullPointerException
A NULL pointer was supplied where not allowed.
Definition: software.h:32
fawkes::WebviewRouter::add
void add(WebRequest::Method method, const std::string &path, T handler, int weight)
Add a handler with weight.
Definition: router.h:103
fawkes::WebviewRouter
URL path router.
Definition: router.h:44
fawkes::WebviewRouter::remove
void remove(WebRequest::Method method, const std::string &path)
Remove a handler.
Definition: router.h:137
fawkes::Exception
Base class for exceptions in Fawkes.
Definition: exception.h:36