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