Crow  0.3
A C++ microframework for the web
app.h
1 #pragma once
2 
3 #include <chrono>
4 #include <string>
5 #include <functional>
6 #include <memory>
7 #include <future>
8 #include <cstdint>
9 #include <type_traits>
10 #include <thread>
11 #include <condition_variable>
12 
13 #include "crow/version.h"
14 #include "crow/settings.h"
15 #include "crow/logging.h"
16 #include "crow/utility.h"
17 #include "crow/routing.h"
18 #include "crow/middleware_context.h"
19 #include "crow/http_request.h"
20 #include "crow/http_server.h"
21 #include "crow/dumb_timer_queue.h"
22 #ifdef CROW_ENABLE_COMPRESSION
23 #include "crow/compression.h"
24 #endif
25 
26 #ifdef CROW_MSVC_WORKAROUND
27 #define CROW_ROUTE(app, url) app.route_dynamic(url)
28 #else
29 #define CROW_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url)
30 #endif
31 #define CROW_CATCHALL_ROUTE(app) app.catchall_route()
32 
33 namespace crow
34 {
35 #ifdef CROW_MAIN
36  int detail::dumb_timer_queue::tick = 5;
37 #endif
38 
39 #ifdef CROW_ENABLE_SSL
40  using ssl_context_t = boost::asio::ssl::context;
41 #endif
42  ///The main server application
43 
44  ///
45  /// Use `SimpleApp` or `App<Middleware1, Middleware2, etc...>`
46  template <typename ... Middlewares>
47  class Crow
48  {
49  public:
50  ///This crow application
51  using self_t = Crow;
52  ///The HTTP server
53  using server_t = Server<Crow, SocketAdaptor, Middlewares...>;
54 #ifdef CROW_ENABLE_SSL
55  ///An HTTP server that runs on SSL with an SSLAdaptor
56  using ssl_server_t = Server<Crow, SSLAdaptor, Middlewares...>;
57 #endif
58  Crow()
59  {
60  }
61 
62  ///Process an Upgrade request
63 
64  ///
65  ///Currently used to upgrrade an HTTP connection to a WebSocket connection
66  template <typename Adaptor>
67  void handle_upgrade(const request& req, response& res, Adaptor&& adaptor)
68  {
69  router_.handle_upgrade(req, res, adaptor);
70  }
71 
72  ///Process the request and generate a response for it
73  void handle(const request& req, response& res)
74  {
75  router_.handle(req, res);
76  }
77 
78  ///Create a dynamic route using a rule (**Use CROW_ROUTE instead**)
79  DynamicRule& route_dynamic(std::string&& rule)
80  {
81  return router_.new_rule_dynamic(std::move(rule));
82  }
83 
84  ///Create a route using a rule (**Use CROW_ROUTE instead**)
85  template <uint64_t Tag>
86  auto route(std::string&& rule)
87  -> typename std::result_of<decltype(&Router::new_rule_tagged<Tag>)(Router, std::string&&)>::type
88  {
89  return router_.new_rule_tagged<Tag>(std::move(rule));
90  }
91 
92  ///Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**)
94  {
95  return router_.catchall_rule();
96  }
97 
98  self_t& signal_clear()
99  {
100  signals_.clear();
101  return *this;
102  }
103 
104  self_t& signal_add(int signal_number)
105  {
106  signals_.push_back(signal_number);
107  return *this;
108  }
109 
110  ///Set the port that Crow will handle requests on
111  self_t& port(std::uint16_t port)
112  {
113  port_ = port;
114  return *this;
115  }
116 
117  ///Set the connection timeout in seconds (default is 5)
118  self_t& timeout(std::uint8_t timeout)
119  {
120  detail::dumb_timer_queue::tick = timeout;
121  return *this;
122  }
123 
124  ///Set the server name
126  {
127  server_name_ = server_name;
128  return *this;
129  }
130 
131  ///The IP address that Crow will handle requests on (default is 0.0.0.0)
132  self_t& bindaddr(std::string bindaddr)
133  {
134  bindaddr_ = bindaddr;
135  return *this;
136  }
137 
138  ///Run the server on multiple threads using all available threads
140  {
141  return concurrency(std::thread::hardware_concurrency());
142  }
143 
144  ///Run the server on multiple threads using a specific number
146  {
147  if (concurrency < 1)
148  concurrency = 1;
149  concurrency_ = concurrency;
150  return *this;
151  }
152 
153  ///Set the server's log level
154 
155  ///
156  /// Possible values are:<br>
157  /// crow::LogLevel::Debug (0)<br>
158  /// crow::LogLevel::Info (1)<br>
159  /// crow::LogLevel::Warning (2)<br>
160  /// crow::LogLevel::Error (3)<br>
161  /// crow::LogLevel::Critical (4)<br>
162  self_t& loglevel(crow::LogLevel level)
163  {
164  crow::logger::setLogLevel(level);
165  return *this;
166  }
167 
168  ///Set a custom duration and function to run on every tick
169  template <typename Duration, typename Func>
170  self_t& tick(Duration d, Func f) {
171  tick_interval_ = std::chrono::duration_cast<std::chrono::milliseconds>(d);
172  tick_function_ = f;
173  return *this;
174  }
175 
176 #ifdef CROW_ENABLE_COMPRESSION
177  self_t& use_compression(compression::algorithm algorithm)
178  {
179  comp_algorithm_ = algorithm;
180  return *this;
181  }
182 
183 
184  compression::algorithm compression_algorithm()
185  {
186  return comp_algorithm_;
187  }
188 #endif
189  ///A wrapper for `validate()` in the router
190 
191  ///
192  ///Go through the rules, upgrade them if possible, and add them to the list of rules
193  void validate()
194  {
195  router_.validate();
196  }
197 
198  ///Notify anything using `wait_for_server_start()` to proceed
200  {
201  std::unique_lock<std::mutex> lock(start_mutex_);
202  server_started_ = true;
203  cv_started_.notify_all();
204  }
205 
206  ///Run the server
207  void run()
208  {
209 #ifndef CROW_DISABLE_STATIC_DIR
210  route<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)
211  ([](crow::response& res, std::string file_path_partial)
212  {
213  res.set_static_file_info(CROW_STATIC_DIRECTORY + file_path_partial);
214  res.end();
215  });
216 #endif
217  validate();
218 
219 #ifdef CROW_ENABLE_SSL
220  if (use_ssl_)
221  {
222  ssl_server_ = std::move(std::unique_ptr<ssl_server_t>(new ssl_server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, &ssl_context_)));
223  ssl_server_->set_tick_function(tick_interval_, tick_function_);
225  ssl_server_->run();
226  }
227  else
228 #endif
229  {
230  server_ = std::move(std::unique_ptr<server_t>(new server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, nullptr)));
231  server_->set_tick_function(tick_interval_, tick_function_);
232  server_->signal_clear();
233  for (auto snum : signals_)
234  {
235  server_->signal_add(snum);
236  }
238  server_->run();
239  }
240  }
241 
242  ///Stop the server
243  void stop()
244  {
245 #ifdef CROW_ENABLE_SSL
246  if (use_ssl_)
247  {
248  if (ssl_server_) {
249  ssl_server_->stop();
250  }
251  }
252  else
253 #endif
254  {
255  if (server_) {
256  server_->stop();
257  }
258  }
259  }
260 
261  void debug_print()
262  {
263  CROW_LOG_DEBUG << "Routing:";
264  router_.debug_print();
265  }
266 
267 
268 #ifdef CROW_ENABLE_SSL
269 
270  ///use certificate and key files for SSL
271  self_t& ssl_file(const std::string& crt_filename, const std::string& key_filename)
272  {
273  use_ssl_ = true;
274  ssl_context_.set_verify_mode(boost::asio::ssl::verify_peer);
275  ssl_context_.set_verify_mode(boost::asio::ssl::verify_client_once);
276  ssl_context_.use_certificate_file(crt_filename, ssl_context_t::pem);
277  ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem);
278  ssl_context_.set_options(
279  boost::asio::ssl::context::default_workarounds
280  | boost::asio::ssl::context::no_sslv2
281  | boost::asio::ssl::context::no_sslv3
282  );
283  return *this;
284  }
285 
286  ///use .pem file for SSL
287  self_t& ssl_file(const std::string& pem_filename)
288  {
289  use_ssl_ = true;
290  ssl_context_.set_verify_mode(boost::asio::ssl::verify_peer);
291  ssl_context_.set_verify_mode(boost::asio::ssl::verify_client_once);
292  ssl_context_.load_verify_file(pem_filename);
293  ssl_context_.set_options(
294  boost::asio::ssl::context::default_workarounds
295  | boost::asio::ssl::context::no_sslv2
296  | boost::asio::ssl::context::no_sslv3
297  );
298  return *this;
299  }
300 
301  self_t& ssl(boost::asio::ssl::context&& ctx)
302  {
303  use_ssl_ = true;
304  ssl_context_ = std::move(ctx);
305  return *this;
306  }
307 
308 
309  bool use_ssl_{false};
310  ssl_context_t ssl_context_{boost::asio::ssl::context::sslv23};
311 
312 #else
313  template <typename T, typename ... Remain>
314  self_t& ssl_file(T&&, Remain&&...)
315  {
316  // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined.
317  static_assert(
318  // make static_assert dependent to T; always false
319  std::is_base_of<T, void>::value,
320  "Define CROW_ENABLE_SSL to enable ssl support.");
321  return *this;
322  }
323 
324  template <typename T>
325  self_t& ssl(T&&)
326  {
327  // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined.
328  static_assert(
329  // make static_assert dependent to T; always false
330  std::is_base_of<T, void>::value,
331  "Define CROW_ENABLE_SSL to enable ssl support.");
332  return *this;
333  }
334 #endif
335 
336  // middleware
337  using context_t = detail::context<Middlewares...>;
338  template <typename T>
339  typename T::context& get_context(const request& req)
340  {
341  static_assert(black_magic::contains<T, Middlewares...>::value, "App doesn't have the specified middleware type.");
342  auto& ctx = *reinterpret_cast<context_t*>(req.middleware_context);
343  return ctx.template get<T>();
344  }
345 
346  template <typename T>
347  T& get_middleware()
348  {
349  return utility::get_element_by_type<T, Middlewares...>(middlewares_);
350  }
351 
352  ///Wait until the server has properly started
354  {
355  std::unique_lock<std::mutex> lock(start_mutex_);
356  if (server_started_)
357  return;
358  cv_started_.wait(lock);
359  }
360 
361  private:
362  uint16_t port_ = 80;
363  uint16_t concurrency_ = 1;
364  std::string server_name_ = std::string("Crow/") + VERSION;
365  std::string bindaddr_ = "0.0.0.0";
366  Router router_;
367 
368 #ifdef CROW_ENABLE_COMPRESSION
369  compression::algorithm comp_algorithm_;
370 #endif
371 
372  std::chrono::milliseconds tick_interval_;
373  std::function<void()> tick_function_;
374 
375  std::tuple<Middlewares...> middlewares_;
376 
377 #ifdef CROW_ENABLE_SSL
378  std::unique_ptr<ssl_server_t> ssl_server_;
379 #endif
380  std::unique_ptr<server_t> server_;
381 
382  std::vector<int> signals_{SIGINT, SIGTERM};
383 
384  bool server_started_{false};
385  std::condition_variable cv_started_;
386  std::mutex start_mutex_;
387  };
388  template <typename ... Middlewares>
389  using App = Crow<Middlewares...>;
390  using SimpleApp = Crow<>;
391 }
crow::Crow::multithreaded
self_t & multithreaded()
Run the server on multiple threads using all available threads.
Definition: app.h:139
crow::Crow::concurrency
self_t & concurrency(std::uint16_t concurrency)
Run the server on multiple threads using a specific number.
Definition: app.h:145
crow::response::set_static_file_info
void set_static_file_info(std::string path)
Return a static file as the response body.
Definition: http_response.h:199
crow::Crow::port
self_t & port(std::uint16_t port)
Set the port that Crow will handle requests on.
Definition: app.h:111
crow::CatchallRule
Definition: routing.h:275
crow::Crow
The main server application.
Definition: app.h:47
crow::Crow::handle
void handle(const request &req, response &res)
Process the request and generate a response for it.
Definition: app.h:73
crow::request
An HTTP request.
Definition: http_request.h:26
crow::Crow::self_t
Crow self_t
This crow application.
Definition: app.h:51
crow::Crow::route
auto route(std::string &&rule) -> typename std::result_of< decltype(&Router::new_rule_tagged< Tag >)(Router, std::string &&)>::type
Create a route using a rule (Use CROW_ROUTE instead)
Definition: app.h:86
crow::Crow::bindaddr
self_t & bindaddr(std::string bindaddr)
The IP address that Crow will handle requests on (default is 0.0.0.0)
Definition: app.h:132
crow::Crow::handle_upgrade
void handle_upgrade(const request &req, response &res, Adaptor &&adaptor)
Process an Upgrade request.
Definition: app.h:67
crow::response
HTTP response.
Definition: http_response.h:23
crow::Crow::tick
self_t & tick(Duration d, Func f)
Set a custom duration and function to run on every tick.
Definition: app.h:170
crow::Crow::server_name
self_t & server_name(std::string server_name)
Set the server name.
Definition: app.h:125
crow::DynamicRule
A rule that can change its parameters during runtime.
Definition: routing.h:483
crow::Crow::timeout
self_t & timeout(std::uint8_t timeout)
Set the connection timeout in seconds (default is 5)
Definition: app.h:118
crow::Crow::wait_for_server_start
void wait_for_server_start()
Wait until the server has properly started.
Definition: app.h:353
crow::Crow::notify_server_start
void notify_server_start()
Notify anything using wait_for_server_start() to proceed.
Definition: app.h:199
crow::Crow::validate
void validate()
A wrapper for validate() in the router.
Definition: app.h:193
crow::SocketAdaptor
A wrapper for the asio::ip::tcp::socket and asio::ssl::stream.
Definition: socket_adaptors.h:18
crow::Crow::loglevel
self_t & loglevel(crow::LogLevel level)
Set the server's log level.
Definition: app.h:162
crow::Server
Definition: http_server.h:27
crow::response::end
void end()
Set the response completion flag and call the handler (to send the response).
Definition: http_response.h:151
crow::Crow::server_t
Server< Crow, SocketAdaptor, Middlewares... > server_t
The HTTP server.
Definition: app.h:53
crow::Crow::route_dynamic
DynamicRule & route_dynamic(std::string &&rule)
Create a dynamic route using a rule (Use CROW_ROUTE instead)
Definition: app.h:79
crow::Crow::run
void run()
Run the server.
Definition: app.h:207
crow::Crow::catchall_route
CatchallRule & catchall_route()
Create a route for any requests without a proper route (Use CROW_CATCHALL_ROUTE instead)
Definition: app.h:93
crow::Router
Handles matching requests to existing rules and upgrade requests.
Definition: routing.h:1054
crow::Crow::stop
void stop()
Stop the server.
Definition: app.h:243