Fawkes API  Fawkes Development Version
rest_api.h
1 
2 /***************************************************************************
3  * rest_api.h - Webview REST API
4  *
5  * Created: Fri Mar 16 17:39:57 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_REST_API_H_
23 #define _LIBS_WEBVIEW_REST_API_H_
24 
25 #include <core/exception.h>
26 #include <logging/logger.h>
27 #include <utils/misc/string_split.h>
28 #include <webview/reply.h>
29 #include <webview/request.h>
30 
31 #include <algorithm>
32 #include <functional>
33 #include <map>
34 #include <memory>
35 #include <regex>
36 #include <string>
37 #include <vector>
38 
39 namespace fawkes {
40 
41 template <typename T>
42 class WebviewRouter;
43 
44 /** REST reply via Webview.
45  * @author Tim Niemueller
46  */
48 {
49 public:
50  /** Constructor.
51  * @param code HTTP response code
52  * @param body body of reply, usually a JSON document.
53  * @param content_type content type of reply, defaults to application/json.
54  * When sending text error messages, set to text/plain.
55  */
57  const std::string &body = "",
58  const std::string &content_type = "application/json")
60  {
61  add_header("Content-type", content_type);
62  }
63 };
64 
65 /** REST processing exception.
66  * Use to indicate failure with more specific response. The HTTP code
67  * will be used for the static response with the formatted error message.
68  * @author Tim Niemueller
69  */
71 {
72 public:
73  /** Constructor.
74  * @param code HTTP response code
75  * @param format format string for error message (cf. printf)
76  */
77  explicit WebviewRestException(WebReply::Code code, const char *format, ...)
78  : Exception(), code_(code), content_type_("text/plain")
79  {
80  va_list va;
81  va_start(va, format);
82  append_va(format, va);
83  va_end(va);
84  }
85 
86  /** Constructor.
87  * @param code HTTP response code
88  * @param o Object to convert to JSON
89  * @param pretty true to enable pretty printing of the JSON input
90  */
91  template <typename T, typename = std::enable_if_t<std::is_class<T>::value>>
92  WebviewRestException(WebReply::Code code, const T &o, bool pretty = false)
93  : Exception(), code_(code), content_type_("application/json")
94  {
95  append("%s", o.to_json(pretty).c_str());
96  }
97 
98  /** Get HTTP response code.
99  * @return HTTP response code
100  */
103  {
104  return code_;
105  }
106 
107  /** Get content type of response.
108  * @return HTTP content type
109  */
110  const std::string &
111  content_type() const
112  {
113  return content_type_;
114  }
115 
116 private:
117  WebReply::Code code_;
118  std::string content_type_;
119 };
120 
121 /** REST parameters to pass to handlers.
122  * @author Tim Niemueller
123  */
125 {
126  /// REST API can call private methods.
127  friend class WebviewRestApi;
128 
129 public:
130  /** Constructor. */
131  WebviewRestParams() : pretty_json_(false)
132  {
133  }
134 
135  /** Get a path argument.
136  * Retrieves a named argument that was a token in the
137  * registration URL, e.g., retrieve "id" for "/item/{id}".
138  * @param what what to retrieve
139  * @return item passed in URL or empty string
140  */
141  std::string
142  path_arg(const std::string &what)
143  {
144  if (path_args_.find(what) != path_args_.end()) {
145  return path_args_[what];
146  } else {
147  return "";
148  }
149  }
150 
151  /** Get a query argument.
152  * Retrieves a named query argument that was passed in the
153  * URL, e.g., retrieve "pretty" for "?pretty=true".
154  * @param what what to retrieve
155  * @return item passed in URL or empty string
156  */
157  std::string
158  query_arg(const std::string &what)
159  {
160  if (query_args_.find(what) != query_args_.end()) {
161  return query_args_[what];
162  } else {
163  return "";
164  }
165  }
166 
167  /** Check if query argument is set.
168  * Retrieves a named query argument that was passed in the
169  * URL, e.g., retrieve "pretty" for "?pretty".
170  * @param what what to check
171  * @return true if the argument exists (with any value), false otherwise
172  */
173  bool
174  has_query_arg(const std::string &what)
175  {
176  return (query_args_.find(what) != query_args_.end());
177  }
178 
179  /** Is pretty-printed JSON enabled?
180  * @return true true to request enabling pretty mode
181  */
182  bool
184  {
185  return pretty_json_;
186  }
187 
188  /** Enable or disable pretty printed results.
189  * Note that this only works when using the generated API
190  * interface and classes which support the "pretty" flag.
191  * @param pretty true to enable, false to disable
192  */
193  void
194  set_pretty_json(bool pretty)
195  {
196  pretty_json_ = pretty;
197  }
198 
199 private:
200  void
201  set_path_args(std::map<std::string, std::string> &&args)
202  {
203  path_args_ = std::move(args);
204  }
205 
206  void
207  set_query_args(const std::map<std::string, std::string> &args)
208  {
209  query_args_ = args;
210  }
211 
212 private:
213  bool pretty_json_;
214  std::map<std::string, std::string> path_args_;
215  std::map<std::string, std::string> query_args_;
216 };
217 
218 class Logger;
219 
221 {
222 public:
223  WebviewRestApi(const std::string &name, fawkes::Logger *logger);
224 
225  /** REST API call handler function type. */
226  typedef std::function<std::unique_ptr<WebReply>(std::string, WebviewRestParams &)> Handler;
227 
228  const std::string &name() const;
229  void add_handler(WebRequest::Method method, std::string path, Handler handler);
230  void set_pretty_json(bool pretty);
231 
232  /** Add simple handler.
233  * For a handler that does not require input parameters and that outputs
234  * a WebviewRestReply instance, for example, only to indicate success.
235  * @param method HTTP method to react to
236  * @param path path (after component base path) to react to
237  * @param handler handler function
238  */
239  void
241  std::string path,
242  std::function<std::unique_ptr<WebReply>(WebviewRestParams &)> handler)
243  {
244  add_handler(method,
245  path,
246  [handler](const std::string &body,
247  WebviewRestParams &m) -> std::unique_ptr<WebReply> {
248  try {
249  return handler(m);
250  } catch (WebviewRestException &e) {
251  return std::make_unique<WebviewRestReply>(e.code(),
252  e.what_no_backtrace(),
253  e.content_type());
254  } catch (Exception &e) {
255  auto r =
256  std::make_unique<WebviewRestReply>(WebReply::HTTP_INTERNAL_SERVER_ERROR);
257  r->append_body("Execution failed: %s", e.what_no_backtrace());
258  r->add_header("Content-type", "text/plain");
259  return r;
260  }
261  });
262  }
263 
264  /** Add handler function.
265  * @param method HTTP method to react to
266  * @param path path (after component base path) to react to
267  * @param handler handler function
268  */
269  template <class O, class I>
270  void
272  std::string path,
273  std::function<O(I &, WebviewRestParams &)> handler)
274  {
275  add_handler(method,
276  path,
277  [this, handler](const std::string &body,
278  WebviewRestParams &m) -> std::unique_ptr<WebReply> {
279  I input;
280  input.from_json(body);
281  try {
282  O output{handler(input, m)};
283  try {
284  output.validate();
285  } catch (std::runtime_error &e) {
286  logger_->log_warn(("RestAPI|" + name_).c_str(), "%s", e.what());
287  }
288  if (m.has_query_arg("pretty")) {
289  m.set_pretty_json(true);
290  }
291  return std::make_unique<WebviewRestReply>(WebReply::HTTP_OK,
292  output.to_json(pretty_json_
293  || m.pretty_json()));
294  } catch (WebviewRestException &e) {
295  return std::make_unique<WebviewRestReply>(e.code(),
296  e.what_no_backtrace(),
297  e.content_type());
298  } catch (Exception &e) {
299  auto r =
300  std::make_unique<WebviewRestReply>(WebReply::HTTP_INTERNAL_SERVER_ERROR);
301  r->append_body("Execution failed: %s", e.what_no_backtrace());
302  r->add_header("Content-type", "text/plain");
303  return r;
304  }
305  });
306  }
307 
308  /** Add handler function.
309  * @param method HTTP method to react to
310  * @param path path (after component base path) to react to
311  * @param handler handler function
312  */
313  template <class I>
314  void
316  std::string path,
317  std::function<std::unique_ptr<WebReply>(I, WebviewRestParams &)> handler)
318  {
319  add_handler(method,
320  path,
321  [this, handler](const std::string &body,
322  WebviewRestParams &m) -> std::unique_ptr<WebReply> {
323  I input;
324  input.from_json(body);
325  try {
326  return handler(std::forward<I>(input), m);
327  } catch (WebviewRestException &e) {
328  return std::make_unique<WebviewRestReply>(e.code(),
329  e.what_no_backtrace(),
330  e.content_type());
331  } catch (Exception &e) {
332  auto r =
333  std::make_unique<WebviewRestReply>(WebReply::HTTP_INTERNAL_SERVER_ERROR);
334  r->append_body("Execution failed: %s", e.what_no_backtrace());
335  r->add_header("Content-type", "text/plain");
336  return r;
337  }
338  });
339  }
340 
341  /** Add handler function.
342  * @param method HTTP method to react to
343  * @param path path (after component base path) to react to
344  * @param handler handler function
345  */
346  template <class O>
347  void
349  std::string path,
350  std::function<O(WebviewRestParams &)> handler)
351  {
352  add_handler(method,
353  path,
354  [this, handler](const std::string &body,
355  WebviewRestParams &m) -> std::unique_ptr<WebReply> {
356  try {
357  O output{handler(m)};
358  try {
359  output.validate();
360  } catch (std::runtime_error &e) {
361  logger_->log_warn(("RestAPI|" + name_).c_str(), "%s", e.what());
362  }
363  if (m.has_query_arg("pretty")) {
364  m.set_pretty_json(true);
365  }
366  return std::make_unique<WebviewRestReply>(WebReply::HTTP_OK,
367  output.to_json(pretty_json_
368  || m.pretty_json()));
369  } catch (WebviewRestException &e) {
370  return std::make_unique<WebviewRestReply>(e.code(),
371  e.what_no_backtrace(),
372  e.content_type());
373  } catch (Exception &e) {
374  auto r =
375  std::make_unique<WebviewRestReply>(WebReply::HTTP_INTERNAL_SERVER_ERROR);
376  r->append_body("Execution failed: %s", e.what_no_backtrace());
377  r->add_header("Content-type", "text/plain");
378  return r;
379  }
380  });
381  }
382 
383  WebReply *process_request(const WebRequest *request, const std::string &rest_url);
384 
385 private:
386  std::string name_;
387  fawkes::Logger * logger_;
388  bool pretty_json_;
389  std::shared_ptr<WebviewRouter<Handler>> router_;
390 };
391 
392 } // namespace fawkes
393 
394 #endif
Base class for exceptions in Fawkes.
Definition: exception.h:36
virtual const char * what_no_backtrace() const noexcept
Get primary string (does not implicitly print the back trace).
Definition: exception.cpp:663
void append_va(const char *format, va_list va) noexcept
Append messages to the message list.
Definition: exception.cpp:353
void append(const char *format,...) noexcept
Append messages to the message list.
Definition: exception.cpp:333
Interface for logging.
Definition: logger.h:42
virtual void log_warn(const char *component, const char *format,...)=0
Log warning message.
Static web reply.
Definition: reply.h:136
virtual const std::string & body()
Get body.
Definition: reply.cpp:289
Basic web reply.
Definition: reply.h:34
void add_header(const std::string &header, const std::string &content)
Add a HTTP header.
Definition: reply.cpp:123
Code code() const
Get response code.
Definition: reply.cpp:104
Code
HTTP response code.
Definition: reply.h:37
@ HTTP_OK
OK.
Definition: reply.h:42
@ HTTP_INTERNAL_SERVER_ERROR
INTERNAL_SERVER_ERROR.
Definition: reply.h:85
Web request meta data carrier.
Definition: request.h:42
Method
HTTP transfer methods.
Definition: request.h:47
Webview REST API component.
Definition: rest_api.h:221
void add_handler(WebRequest::Method method, std::string path, std::function< std::unique_ptr< WebReply >(WebviewRestParams &)> handler)
Add simple handler.
Definition: rest_api.h:240
void add_handler(WebRequest::Method method, std::string path, std::function< O(WebviewRestParams &)> handler)
Add handler function.
Definition: rest_api.h:348
void set_pretty_json(bool pretty)
Enable or disable pretty JSON printing globally.
Definition: rest_api.cpp:94
WebReply * process_request(const WebRequest *request, const std::string &rest_url)
Process REST API request.
Definition: rest_api.cpp:64
void add_handler(WebRequest::Method method, std::string path, std::function< std::unique_ptr< WebReply >(I, WebviewRestParams &)> handler)
Add handler function.
Definition: rest_api.h:315
const std::string & name() const
Get name of component.
Definition: rest_api.cpp:53
std::function< std::unique_ptr< WebReply >std::string, WebviewRestParams &)> Handler
REST API call handler function type.
Definition: rest_api.h:226
void add_handler(WebRequest::Method method, std::string path, std::function< O(I &, WebviewRestParams &)> handler)
Add handler function.
Definition: rest_api.h:271
void add_handler(WebRequest::Method method, std::string path, Handler handler)
Add handler function.
Definition: rest_api.cpp:85
WebviewRestApi(const std::string &name, fawkes::Logger *logger)
Constructor.
Definition: rest_api.cpp:41
REST processing exception.
Definition: rest_api.h:71
WebReply::Code code()
Get HTTP response code.
Definition: rest_api.h:102
const std::string & content_type() const
Get content type of response.
Definition: rest_api.h:111
WebviewRestException(WebReply::Code code, const char *format,...)
Constructor.
Definition: rest_api.h:77
WebviewRestException(WebReply::Code code, const T &o, bool pretty=false)
Constructor.
Definition: rest_api.h:92
REST parameters to pass to handlers.
Definition: rest_api.h:125
bool pretty_json()
Is pretty-printed JSON enabled?
Definition: rest_api.h:183
std::string query_arg(const std::string &what)
Get a query argument.
Definition: rest_api.h:158
bool has_query_arg(const std::string &what)
Check if query argument is set.
Definition: rest_api.h:174
std::string path_arg(const std::string &what)
Get a path argument.
Definition: rest_api.h:142
void set_pretty_json(bool pretty)
Enable or disable pretty printed results.
Definition: rest_api.h:194
WebviewRestParams()
Constructor.
Definition: rest_api.h:131
REST reply via Webview.
Definition: rest_api.h:48
WebviewRestReply(WebReply::Code code, const std::string &body="", const std::string &content_type="application/json")
Constructor.
Definition: rest_api.h:56
Fawkes library namespace.