/src/crow/include/crow/app.h
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * \file crow/app.h |
3 | | * \brief This file includes the definition of the crow::Crow class, |
4 | | * the crow::App and crow::SimpleApp aliases, and some macros. |
5 | | * |
6 | | * In this file are defined: |
7 | | * - crow::Crow |
8 | | * - crow::App |
9 | | * - crow::SimpleApp |
10 | | * - \ref CROW_ROUTE |
11 | | * - \ref CROW_BP_ROUTE |
12 | | * - \ref CROW_WEBSOCKET_ROUTE |
13 | | * - \ref CROW_MIDDLEWARES |
14 | | * - \ref CROW_CATCHALL_ROUTE |
15 | | * - \ref CROW_BP_CATCHALL_ROUTE |
16 | | */ |
17 | | |
18 | | #pragma once |
19 | | |
20 | | #include <chrono> |
21 | | #include <string> |
22 | | #include <functional> |
23 | | #include <memory> |
24 | | #include <future> |
25 | | #include <cstdint> |
26 | | #include <type_traits> |
27 | | #include <thread> |
28 | | #include <condition_variable> |
29 | | |
30 | | #include "crow/version.h" |
31 | | #include "crow/settings.h" |
32 | | #include "crow/logging.h" |
33 | | #include "crow/utility.h" |
34 | | #include "crow/routing.h" |
35 | | #include "crow/middleware_context.h" |
36 | | #include "crow/http_request.h" |
37 | | #include "crow/http_server.h" |
38 | | #include "crow/task_timer.h" |
39 | | #include "crow/websocket.h" |
40 | | #ifdef CROW_ENABLE_COMPRESSION |
41 | | #include "crow/compression.h" |
42 | | #endif // #ifdef CROW_ENABLE_COMPRESSION |
43 | | |
44 | | |
45 | | #ifdef CROW_MSVC_WORKAROUND |
46 | | |
47 | | #define CROW_ROUTE(app, url) app.route_dynamic(url) // See the documentation in the comment below. |
48 | | #define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_dynamic(url) // See the documentation in the comment below. |
49 | | |
50 | | #else // #ifdef CROW_MSVC_WORKAROUND |
51 | | |
52 | | /** |
53 | | * \def CROW_ROUTE(app, url) |
54 | | * \brief Creates a route for app using a rule. |
55 | | * |
56 | | * It use crow::Crow::route_dynamic or crow::Crow::route to define |
57 | | * a rule for your application. It's usage is like this: |
58 | | * |
59 | | * ```cpp |
60 | | * auto app = crow::SimpleApp(); // or crow::App() |
61 | | * CROW_ROUTE(app, "/") |
62 | | * ([](){ |
63 | | * return "<h1>Hello, world!</h1>"; |
64 | | * }); |
65 | | * ``` |
66 | | * |
67 | | * This is the recommended way to define routes in a crow application. |
68 | | * \see [Page of guide "Routes"](https://crowcpp.org/master/guides/routes/). |
69 | | */ |
70 | 0 | #define CROW_ROUTE(app, url) app.template route<crow::black_magic::get_parameter_tag(url)>(url) |
71 | | |
72 | | /** |
73 | | * \def CROW_BP_ROUTE(blueprint, url) |
74 | | * \brief Creates a route for a blueprint using a rule. |
75 | | * |
76 | | * It may use crow::Blueprint::new_rule_dynamic or |
77 | | * crow::Blueprint::new_rule_tagged to define a new rule for |
78 | | * an given blueprint. It's usage is similar |
79 | | * to CROW_ROUTE macro: |
80 | | * |
81 | | * ```cpp |
82 | | * crow::Blueprint my_bp(); |
83 | | * CROW_BP_ROUTE(my_bp, "/") |
84 | | * ([](){ |
85 | | * return "<h1>Hello, world!</h1>"; |
86 | | * }); |
87 | | * ``` |
88 | | * |
89 | | * This is the recommended way to define routes in a crow blueprint |
90 | | * because of its compile-time capabilities. |
91 | | * |
92 | | * \see [Page of the guide "Blueprints"](https://crowcpp.org/master/guides/blueprints/). |
93 | | */ |
94 | | #define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_tagged<crow::black_magic::get_parameter_tag(url)>(url) |
95 | | |
96 | | /** |
97 | | * \def CROW_WEBSOCKET_ROUTE(app, url) |
98 | | * \brief Defines WebSocket route for app. |
99 | | * |
100 | | * It binds a WebSocket route to app. Easy solution to implement |
101 | | * WebSockets in your app. The usage syntax of this macro is |
102 | | * like this: |
103 | | * |
104 | | * ```cpp |
105 | | * auto app = crow::SimpleApp(); // or crow::App() |
106 | | * CROW_WEBSOCKET_ROUTE(app, "/ws") |
107 | | * .onopen([&](crow::websocket::connection& conn){ |
108 | | * do_something(); |
109 | | * }) |
110 | | * .onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t){ |
111 | | * do_something(); |
112 | | * }) |
113 | | * .onmessage([&](crow::websocket::connection&, const std::string& data, bool is_binary){ |
114 | | * if (is_binary) |
115 | | * do_something(data); |
116 | | * else |
117 | | * do_something_else(data); |
118 | | * }); |
119 | | * ``` |
120 | | * |
121 | | * \see [Page of the guide "WebSockets"](https://crowcpp.org/master/guides/websockets/). |
122 | | */ |
123 | | #define CROW_WEBSOCKET_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url).websocket<std::remove_reference<decltype(app)>::type>(&app) |
124 | | |
125 | | /** |
126 | | * \def CROW_MIDDLEWARES(app, ...) |
127 | | * \brief Enable a Middleware for an specific route in app |
128 | | * or blueprint. |
129 | | * |
130 | | * It defines the usage of a Middleware in one route. And it |
131 | | * can be used in both crow::SimpleApp (and crow::App) instances and |
132 | | * crow::Blueprint. Its usage syntax is like this: |
133 | | * |
134 | | * ```cpp |
135 | | * auto app = crow::SimpleApp(); // or crow::App() |
136 | | * CROW_ROUTE(app, "/with_middleware") |
137 | | * .CROW_MIDDLEWARES(app, LocalMiddleware) // Can be used more than one |
138 | | * ([]() { // middleware. |
139 | | * return "Hello world!"; |
140 | | * }); |
141 | | * ``` |
142 | | * |
143 | | * \see [Page of the guide "Middlewares"](https://crowcpp.org/master/guides/middleware/). |
144 | | */ |
145 | | #define CROW_MIDDLEWARES(app, ...) template middlewares<typename std::remove_reference<decltype(app)>::type, __VA_ARGS__>() |
146 | | |
147 | | #endif // #ifdef CROW_MSVC_WORKAROUND |
148 | | |
149 | | /** |
150 | | * \def CROW_CATCHALL_ROUTE(app) |
151 | | * \brief Defines a custom catchall route for app using a |
152 | | * custom rule. |
153 | | * |
154 | | * It defines a handler when the client make a request for an |
155 | | * undefined route. Instead of just reply with a `404` status |
156 | | * code (default behavior), you can define a custom handler |
157 | | * using this macro. |
158 | | * |
159 | | * \see [Page of the guide "Routes" (Catchall routes)](https://crowcpp.org/master/guides/routes/#catchall-routes). |
160 | | */ |
161 | | #define CROW_CATCHALL_ROUTE(app) app.catchall_route() |
162 | | |
163 | | /** |
164 | | * \def CROW_BP_CATCHALL_ROUTE(blueprint) |
165 | | * \brief Defines a custom catchall route for blueprint |
166 | | * using a custom rule. |
167 | | * |
168 | | * It defines a handler when the client make a request for an |
169 | | * undefined route in the blueprint. |
170 | | * |
171 | | * \see [Page of the guide "Blueprint" (Define a custom Catchall route)](https://crowcpp.org/master/guides/blueprints/#define-a-custom-catchall-route). |
172 | | */ |
173 | | #define CROW_BP_CATCHALL_ROUTE(blueprint) blueprint.catchall_rule() |
174 | | |
175 | | |
176 | | /** |
177 | | * \namespace crow |
178 | | * \brief The main namespace of the library. In this namespace |
179 | | * is defined the most important classes and functions of the |
180 | | * library. |
181 | | * |
182 | | * Within this namespace, the Crow class, Router class, Connection |
183 | | * class, and other are defined. |
184 | | */ |
185 | | namespace crow |
186 | | { |
187 | | #ifdef CROW_ENABLE_SSL |
188 | | using ssl_context_t = asio::ssl::context; |
189 | | #endif |
190 | | /** |
191 | | * \class Crow |
192 | | * \brief The main server application class. |
193 | | * |
194 | | * Use crow::SimpleApp or crow::App<Middleware1, Middleware2, etc...> instead of |
195 | | * directly instantiate this class. |
196 | | */ |
197 | | template<typename... Middlewares> |
198 | | class Crow |
199 | | { |
200 | | public: |
201 | | /// \brief This is the crow application |
202 | | using self_t = Crow; |
203 | | |
204 | | /// \brief The HTTP server |
205 | | using server_t = Server<Crow, SocketAdaptor, Middlewares...>; |
206 | | |
207 | | #ifdef CROW_ENABLE_SSL |
208 | | /// \brief An HTTP server that runs on SSL with an SSLAdaptor |
209 | | using ssl_server_t = Server<Crow, SSLAdaptor, Middlewares...>; |
210 | | #endif |
211 | | Crow() |
212 | 0 | {} |
213 | | |
214 | | /// \brief Construct Crow with a subset of middleware |
215 | | template<typename... Ts> |
216 | | Crow(Ts&&... ts): |
217 | | middlewares_(make_middleware_tuple(std::forward<Ts>(ts)...)) |
218 | | {} |
219 | | |
220 | | /// \brief Process an Upgrade request |
221 | | /// |
222 | | /// Currently used to upgrade an HTTP connection to a WebSocket connection |
223 | | template<typename Adaptor> |
224 | | void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) |
225 | 0 | { |
226 | 0 | router_.handle_upgrade(req, res, adaptor); |
227 | 0 | } |
228 | | |
229 | | /// \brief Process only the method and URL of a request and provide a route (or an error response) |
230 | | std::unique_ptr<routing_handle_result> handle_initial(request& req, response& res) |
231 | 0 | { |
232 | 0 | return router_.handle_initial(req, res); |
233 | 0 | } |
234 | | |
235 | | /// \brief Process the fully parsed request and generate a response for it |
236 | | void handle(request& req, response& res, std::unique_ptr<routing_handle_result>& found) |
237 | 0 | { |
238 | 0 | router_.handle<self_t>(req, res, *found); |
239 | 0 | } |
240 | | |
241 | | /// \brief Process a fully parsed request from start to finish (primarily used for debugging) |
242 | | void handle_full(request& req, response& res) |
243 | | { |
244 | | auto found = handle_initial(req, res); |
245 | | if (found->rule_index) |
246 | | handle(req, res, found); |
247 | | } |
248 | | |
249 | | /// \brief Create a dynamic route using a rule (**Use CROW_ROUTE instead**) |
250 | | DynamicRule& route_dynamic(const std::string& rule) |
251 | | { |
252 | | return router_.new_rule_dynamic(rule); |
253 | | } |
254 | | |
255 | | /// \brief Create a route using a rule (**Use CROW_ROUTE instead**) |
256 | | template<uint64_t Tag> |
257 | | auto route(const std::string& rule) |
258 | | -> typename std::invoke_result<decltype(&Router::new_rule_tagged<Tag>), Router, const std::string&>::type |
259 | 0 | { |
260 | 0 | return router_.new_rule_tagged<Tag>(rule); |
261 | 0 | } Unexecuted instantiation: _ZN4crow4CrowIJEE5routeILm10EEENSt3__113invoke_resultIDTadsr6RouterE15new_rule_taggedIXT_EEEJNS_6RouterERKNS3_12basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEE4typeESE_ Unexecuted instantiation: _ZN4crow4CrowIJEE5routeILm5EEENSt3__113invoke_resultIDTadsr6RouterE15new_rule_taggedIXT_EEEJNS_6RouterERKNS3_12basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEE4typeESE_ |
262 | | |
263 | | /// \brief Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**) |
264 | | CatchallRule& catchall_route() |
265 | | { |
266 | | return router_.catchall_rule(); |
267 | | } |
268 | | |
269 | | /// \brief Set the default max payload size for websockets |
270 | | self_t& websocket_max_payload(uint64_t max_payload) |
271 | | { |
272 | | max_payload_ = max_payload; |
273 | | return *this; |
274 | | } |
275 | | |
276 | | /// \brief Get the default max payload size for websockets |
277 | | uint64_t websocket_max_payload() |
278 | | { |
279 | | return max_payload_; |
280 | | } |
281 | | |
282 | | self_t& signal_clear() |
283 | | { |
284 | | signals_.clear(); |
285 | | return *this; |
286 | | } |
287 | | |
288 | | self_t& signal_add(int signal_number) |
289 | | { |
290 | | signals_.push_back(signal_number); |
291 | | return *this; |
292 | | } |
293 | | |
294 | | std::vector<int> signals() |
295 | | { |
296 | | return signals_; |
297 | | } |
298 | | |
299 | | /// \brief Set the port that Crow will handle requests on |
300 | | self_t& port(std::uint16_t port) |
301 | 0 | { |
302 | 0 | port_ = port; |
303 | 0 | return *this; |
304 | 0 | } |
305 | | |
306 | | /// \brief Get the port that Crow will handle requests on |
307 | | std::uint16_t port() const |
308 | | { |
309 | | if (!server_started_) |
310 | | { |
311 | | return port_; |
312 | | } |
313 | | #ifdef CROW_ENABLE_SSL |
314 | | if (ssl_used_) |
315 | | { |
316 | | return ssl_server_->port(); |
317 | | } |
318 | | else |
319 | | #endif |
320 | | { |
321 | | return server_->port(); |
322 | | } |
323 | | } |
324 | | |
325 | | /// \brief Set the connection timeout in seconds (default is 5) |
326 | | self_t& timeout(std::uint8_t timeout) |
327 | | { |
328 | | timeout_ = timeout; |
329 | | return *this; |
330 | | } |
331 | | |
332 | | /// \brief Set the server name |
333 | | self_t& server_name(std::string server_name) |
334 | | { |
335 | | server_name_ = server_name; |
336 | | return *this; |
337 | | } |
338 | | |
339 | | /// \brief The IP address that Crow will handle requests on (default is 0.0.0.0) |
340 | | self_t& bindaddr(std::string bindaddr) |
341 | 0 | { |
342 | 0 | bindaddr_ = bindaddr; |
343 | 0 | return *this; |
344 | 0 | } |
345 | | |
346 | | /// \brief Get the address that Crow will handle requests on |
347 | | std::string bindaddr() |
348 | | { |
349 | | return bindaddr_; |
350 | | } |
351 | | |
352 | | /// \brief Run the server on multiple threads using all available threads |
353 | | self_t& multithreaded() |
354 | 0 | { |
355 | 0 | return concurrency(std::thread::hardware_concurrency()); |
356 | 0 | } |
357 | | |
358 | | /// \brief Run the server on multiple threads using a specific number |
359 | | self_t& concurrency(std::uint16_t concurrency) |
360 | 0 | { |
361 | 0 | if (concurrency < 2) // Crow can have a minimum of 2 threads running |
362 | 0 | concurrency = 2; |
363 | 0 | concurrency_ = concurrency; |
364 | 0 | return *this; |
365 | 0 | } |
366 | | |
367 | | /// \brief Get the number of threads that server is using |
368 | | std::uint16_t concurrency() const |
369 | | { |
370 | | return concurrency_; |
371 | | } |
372 | | |
373 | | /// \brief Set the server's log level |
374 | | /// |
375 | | /// Possible values are: |
376 | | /// - crow::LogLevel::Debug (0) |
377 | | /// - crow::LogLevel::Info (1) |
378 | | /// - crow::LogLevel::Warning (2) |
379 | | /// - crow::LogLevel::Error (3) |
380 | | /// - crow::LogLevel::Critical (4) |
381 | | self_t& loglevel(LogLevel level) |
382 | | { |
383 | | crow::logger::setLogLevel(level); |
384 | | return *this; |
385 | | } |
386 | | |
387 | | /// \brief Set the response body size (in bytes) beyond which Crow automatically streams responses (Default is 1MiB) |
388 | | /// |
389 | | /// Any streamed response is unaffected by Crow's timer, and therefore won't timeout before a response is fully sent. |
390 | | self_t& stream_threshold(size_t threshold) |
391 | | { |
392 | | res_stream_threshold_ = threshold; |
393 | | return *this; |
394 | | } |
395 | | |
396 | | /// \brief Get the response body size (in bytes) beyond which Crow automatically streams responses |
397 | | size_t& stream_threshold() |
398 | 0 | { |
399 | 0 | return res_stream_threshold_; |
400 | 0 | } |
401 | | |
402 | | |
403 | | self_t& register_blueprint(Blueprint& blueprint) |
404 | | { |
405 | | router_.register_blueprint(blueprint); |
406 | | return *this; |
407 | | } |
408 | | |
409 | | /// \brief Set the function to call to handle uncaught exceptions generated in routes (Default generates error 500). |
410 | | /// |
411 | | /// The function must have the following signature: void(crow::response&). |
412 | | /// It must set the response passed in argument to the function, which will be sent back to the client. |
413 | | /// See Router::default_exception_handler() for the default implementation. |
414 | | template<typename Func> |
415 | | self_t& exception_handler(Func&& f) |
416 | | { |
417 | | router_.exception_handler() = std::forward<Func>(f); |
418 | | return *this; |
419 | | } |
420 | | |
421 | | std::function<void(crow::response&)>& exception_handler() |
422 | | { |
423 | | return router_.exception_handler(); |
424 | | } |
425 | | |
426 | | /// \brief Set a custom duration and function to run on every tick |
427 | | template<typename Duration, typename Func> |
428 | | self_t& tick(Duration d, Func f) |
429 | | { |
430 | | tick_interval_ = std::chrono::duration_cast<std::chrono::milliseconds>(d); |
431 | | tick_function_ = f; |
432 | | return *this; |
433 | | } |
434 | | |
435 | | #ifdef CROW_ENABLE_COMPRESSION |
436 | | |
437 | | self_t& use_compression(compression::algorithm algorithm) |
438 | | { |
439 | | comp_algorithm_ = algorithm; |
440 | | compression_used_ = true; |
441 | | return *this; |
442 | | } |
443 | | |
444 | | compression::algorithm compression_algorithm() |
445 | | { |
446 | | return comp_algorithm_; |
447 | | } |
448 | | |
449 | | bool compression_used() const |
450 | | { |
451 | | return compression_used_; |
452 | | } |
453 | | #endif |
454 | | |
455 | | /// \brief Apply blueprints |
456 | | void add_blueprint() |
457 | 0 | { |
458 | | #if defined(__APPLE__) || defined(__MACH__) |
459 | | if (router_.blueprints().empty()) return; |
460 | | #endif |
461 | |
|
462 | 0 | for (Blueprint* bp : router_.blueprints()) |
463 | 0 | { |
464 | 0 | if (bp->static_dir().empty()) { |
465 | 0 | CROW_LOG_ERROR << "Blueprint " << bp->prefix() << " and its sub-blueprints ignored due to empty static directory."; |
466 | 0 | continue; |
467 | 0 | } |
468 | 0 | auto static_dir_ = crow::utility::normalize_path(bp->static_dir()); |
469 | |
|
470 | 0 | bp->new_rule_tagged<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) { |
471 | 0 | utility::sanitize_filename(file_path_partial); |
472 | 0 | res.set_static_file_info_unsafe(static_dir_ + file_path_partial); |
473 | 0 | res.end(); |
474 | 0 | }); |
475 | 0 | } |
476 | |
|
477 | 0 | router_.validate_bp(); |
478 | 0 | } |
479 | | |
480 | | /// \brief Go through the rules, upgrade them if possible, and add them to the list of rules |
481 | | void add_static_dir() |
482 | 0 | { |
483 | 0 | if (are_static_routes_added()) return; |
484 | 0 | auto static_dir_ = crow::utility::normalize_path(CROW_STATIC_DIRECTORY); |
485 | |
|
486 | 0 | route<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) { |
487 | 0 | utility::sanitize_filename(file_path_partial); |
488 | 0 | res.set_static_file_info_unsafe(static_dir_ + file_path_partial); |
489 | 0 | res.end(); |
490 | 0 | }); |
491 | 0 | set_static_routes_added(); |
492 | 0 | } |
493 | | |
494 | | /// \brief A wrapper for `validate()` in the router |
495 | | void validate() |
496 | 0 | { |
497 | 0 | router_.validate(); |
498 | 0 | } |
499 | | |
500 | | /// \brief Run the server |
501 | | void run() |
502 | 0 | { |
503 | 0 | #ifndef CROW_DISABLE_STATIC_DIR |
504 | 0 | add_blueprint(); |
505 | 0 | add_static_dir(); |
506 | 0 | #endif |
507 | 0 | validate(); |
508 | |
|
509 | 0 | error_code ec; |
510 | 0 | asio::ip::address addr = asio::ip::make_address(bindaddr_,ec); |
511 | 0 | if (ec){ |
512 | 0 | CROW_LOG_ERROR << ec.message() << " - Can not create valid ip address from string: \"" << bindaddr_ << "\""; |
513 | 0 | return; |
514 | 0 | } |
515 | 0 | tcp::endpoint endpoint(addr, port_); |
516 | | #ifdef CROW_ENABLE_SSL |
517 | | if (ssl_used_) |
518 | | { |
519 | | router_.using_ssl = true; |
520 | | ssl_server_ = std::move(std::unique_ptr<ssl_server_t>(new ssl_server_t(this, endpoint, server_name_, &middlewares_, concurrency_, timeout_, &ssl_context_))); |
521 | | ssl_server_->set_tick_function(tick_interval_, tick_function_); |
522 | | ssl_server_->signal_clear(); |
523 | | for (auto snum : signals_) |
524 | | { |
525 | | ssl_server_->signal_add(snum); |
526 | | } |
527 | | notify_server_start(); |
528 | | ssl_server_->run(); |
529 | | } |
530 | | else |
531 | | #endif |
532 | 0 | { |
533 | 0 | server_ = std::move(std::unique_ptr<server_t>(new server_t(this, endpoint, server_name_, &middlewares_, concurrency_, timeout_, nullptr))); |
534 | 0 | server_->set_tick_function(tick_interval_, tick_function_); |
535 | 0 | for (auto snum : signals_) |
536 | 0 | { |
537 | 0 | server_->signal_add(snum); |
538 | 0 | } |
539 | 0 | notify_server_start(); |
540 | 0 | server_->run(); |
541 | 0 | } |
542 | 0 | } |
543 | | |
544 | | /// \brief Non-blocking version of \ref run() |
545 | | /// |
546 | | /// The output from this method needs to be saved into a variable! |
547 | | /// Otherwise the call will be made on the same thread. |
548 | | std::future<void> run_async() |
549 | | { |
550 | | return std::async(std::launch::async, [&] { |
551 | | this->run(); |
552 | | }); |
553 | | } |
554 | | |
555 | | /// \brief Stop the server |
556 | | void stop() |
557 | | { |
558 | | #ifdef CROW_ENABLE_SSL |
559 | | if (ssl_used_) |
560 | | { |
561 | | if (ssl_server_) { ssl_server_->stop(); } |
562 | | } |
563 | | else |
564 | | #endif |
565 | | { |
566 | | // TODO(EDev): Move these 6 lines to a method in http_server. |
567 | | std::vector<crow::websocket::connection*> websockets_to_close = websockets_; |
568 | | for (auto websocket : websockets_to_close) |
569 | | { |
570 | | CROW_LOG_INFO << "Quitting Websocket: " << websocket; |
571 | | websocket->close("Server Application Terminated"); |
572 | | } |
573 | | if (server_) { server_->stop(); } |
574 | | } |
575 | | } |
576 | | |
577 | | void add_websocket(crow::websocket::connection* conn) |
578 | | { |
579 | | websockets_.push_back(conn); |
580 | | } |
581 | | |
582 | | void remove_websocket(crow::websocket::connection* conn) |
583 | | { |
584 | | websockets_.erase(std::remove(websockets_.begin(), websockets_.end(), conn), websockets_.end()); |
585 | | } |
586 | | |
587 | | /// \brief Print the routing paths defined for each HTTP method |
588 | | void debug_print() |
589 | | { |
590 | | CROW_LOG_DEBUG << "Routing:"; |
591 | | router_.debug_print(); |
592 | | } |
593 | | |
594 | | |
595 | | #ifdef CROW_ENABLE_SSL |
596 | | |
597 | | /// \brief Use certificate and key files for SSL |
598 | | self_t& ssl_file(const std::string& crt_filename, const std::string& key_filename) |
599 | | { |
600 | | ssl_used_ = true; |
601 | | ssl_context_.set_verify_mode(asio::ssl::verify_peer); |
602 | | ssl_context_.set_verify_mode(asio::ssl::verify_client_once); |
603 | | ssl_context_.use_certificate_file(crt_filename, ssl_context_t::pem); |
604 | | ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem); |
605 | | ssl_context_.set_options( |
606 | | asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3); |
607 | | return *this; |
608 | | } |
609 | | |
610 | | /// \brief Use `.pem` file for SSL |
611 | | self_t& ssl_file(const std::string& pem_filename) |
612 | | { |
613 | | ssl_used_ = true; |
614 | | ssl_context_.set_verify_mode(asio::ssl::verify_peer); |
615 | | ssl_context_.set_verify_mode(asio::ssl::verify_client_once); |
616 | | ssl_context_.load_verify_file(pem_filename); |
617 | | ssl_context_.set_options( |
618 | | asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3); |
619 | | return *this; |
620 | | } |
621 | | |
622 | | /// \brief Use certificate chain and key files for SSL |
623 | | self_t& ssl_chainfile(const std::string& crt_filename, const std::string& key_filename) |
624 | | { |
625 | | ssl_used_ = true; |
626 | | ssl_context_.set_verify_mode(asio::ssl::verify_peer); |
627 | | ssl_context_.set_verify_mode(asio::ssl::verify_client_once); |
628 | | ssl_context_.use_certificate_chain_file(crt_filename); |
629 | | ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem); |
630 | | ssl_context_.set_options( |
631 | | asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3); |
632 | | return *this; |
633 | | } |
634 | | |
635 | | self_t& ssl(asio::ssl::context&& ctx) |
636 | | { |
637 | | ssl_used_ = true; |
638 | | ssl_context_ = std::move(ctx); |
639 | | return *this; |
640 | | } |
641 | | |
642 | | bool ssl_used() const |
643 | | { |
644 | | return ssl_used_; |
645 | | } |
646 | | #else |
647 | | |
648 | | template<typename T, typename... Remain> |
649 | | self_t& ssl_file(T&&, Remain&&...) |
650 | | { |
651 | | // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined. |
652 | | static_assert( |
653 | | // make static_assert dependent to T; always false |
654 | | std::is_base_of<T, void>::value, |
655 | | "Define CROW_ENABLE_SSL to enable ssl support."); |
656 | | return *this; |
657 | | } |
658 | | |
659 | | template<typename T, typename... Remain> |
660 | | self_t& ssl_chainfile(T&&, Remain&&...) |
661 | | { |
662 | | // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined. |
663 | | static_assert( |
664 | | // make static_assert dependent to T; always false |
665 | | std::is_base_of<T, void>::value, |
666 | | "Define CROW_ENABLE_SSL to enable ssl support."); |
667 | | return *this; |
668 | | } |
669 | | |
670 | | template<typename T> |
671 | | self_t& ssl(T&&) |
672 | | { |
673 | | // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined. |
674 | | static_assert( |
675 | | // make static_assert dependent to T; always false |
676 | | std::is_base_of<T, void>::value, |
677 | | "Define CROW_ENABLE_SSL to enable ssl support."); |
678 | | return *this; |
679 | | } |
680 | | |
681 | | bool ssl_used() const |
682 | 0 | { |
683 | 0 | return false; |
684 | 0 | } |
685 | | #endif |
686 | | |
687 | | // middleware |
688 | | using context_t = detail::context<Middlewares...>; |
689 | | using mw_container_t = std::tuple<Middlewares...>; |
690 | | template<typename T> |
691 | | typename T::context& get_context(const request& req) |
692 | | { |
693 | | static_assert(black_magic::contains<T, Middlewares...>::value, "App doesn't have the specified middleware type."); |
694 | | auto& ctx = *reinterpret_cast<context_t*>(req.middleware_context); |
695 | | return ctx.template get<T>(); |
696 | | } |
697 | | |
698 | | template<typename T> |
699 | | T& get_middleware() |
700 | | { |
701 | | return utility::get_element_by_type<T, Middlewares...>(middlewares_); |
702 | | } |
703 | | |
704 | | /// \brief Wait until the server has properly started |
705 | | std::cv_status wait_for_server_start(std::chrono::milliseconds wait_timeout = std::chrono::milliseconds(3000)) |
706 | | { |
707 | | std::cv_status status = std::cv_status::no_timeout; |
708 | | auto wait_until = std::chrono::steady_clock::now() + wait_timeout; |
709 | | { |
710 | | std::unique_lock<std::mutex> lock(start_mutex_); |
711 | | while (!server_started_ && (status == std::cv_status::no_timeout)) |
712 | | { |
713 | | status = cv_started_.wait_until(lock, wait_until); |
714 | | } |
715 | | } |
716 | | |
717 | | if (status == std::cv_status::no_timeout) |
718 | | { |
719 | | if (server_) |
720 | | { |
721 | | status = server_->wait_for_start(wait_until); |
722 | | } |
723 | | #ifdef CROW_ENABLE_SSL |
724 | | else if (ssl_server_) |
725 | | { |
726 | | status = ssl_server_->wait_for_start(wait_until); |
727 | | } |
728 | | #endif |
729 | | } |
730 | | return status; |
731 | | } |
732 | | |
733 | | private: |
734 | | template<typename... Ts> |
735 | | std::tuple<Middlewares...> make_middleware_tuple(Ts&&... ts) |
736 | | { |
737 | | auto fwd = std::forward_as_tuple((ts)...); |
738 | | return std::make_tuple( |
739 | | std::forward<Middlewares>( |
740 | | black_magic::tuple_extract<Middlewares, decltype(fwd)>(fwd))...); |
741 | | } |
742 | | |
743 | | /// \brief Notify anything using \ref wait_for_server_start() to proceed |
744 | | void notify_server_start() |
745 | 0 | { |
746 | 0 | std::unique_lock<std::mutex> lock(start_mutex_); |
747 | 0 | server_started_ = true; |
748 | 0 | cv_started_.notify_all(); |
749 | 0 | } |
750 | | |
751 | 0 | void set_static_routes_added() { |
752 | 0 | static_routes_added_ = true; |
753 | 0 | } |
754 | | |
755 | 0 | bool are_static_routes_added() { |
756 | 0 | return static_routes_added_; |
757 | 0 | } |
758 | | |
759 | | private: |
760 | | std::uint8_t timeout_{5}; |
761 | | uint16_t port_ = 80; |
762 | | uint16_t concurrency_ = 2; |
763 | | uint64_t max_payload_{UINT64_MAX}; |
764 | | std::string server_name_ = std::string("Crow/") + VERSION; |
765 | | std::string bindaddr_ = "0.0.0.0"; |
766 | | size_t res_stream_threshold_ = 1048576; |
767 | | Router router_; |
768 | | bool static_routes_added_{false}; |
769 | | |
770 | | #ifdef CROW_ENABLE_COMPRESSION |
771 | | compression::algorithm comp_algorithm_; |
772 | | bool compression_used_{false}; |
773 | | #endif |
774 | | |
775 | | std::chrono::milliseconds tick_interval_; |
776 | | std::function<void()> tick_function_; |
777 | | |
778 | | std::tuple<Middlewares...> middlewares_; |
779 | | |
780 | | #ifdef CROW_ENABLE_SSL |
781 | | std::unique_ptr<ssl_server_t> ssl_server_; |
782 | | bool ssl_used_{false}; |
783 | | ssl_context_t ssl_context_{asio::ssl::context::sslv23}; |
784 | | #endif |
785 | | |
786 | | std::unique_ptr<server_t> server_; |
787 | | |
788 | | std::vector<int> signals_{SIGINT, SIGTERM}; |
789 | | |
790 | | bool server_started_{false}; |
791 | | std::condition_variable cv_started_; |
792 | | std::mutex start_mutex_; |
793 | | std::vector<crow::websocket::connection*> websockets_; |
794 | | }; |
795 | | |
796 | | /// \brief Alias of Crow<Middlewares...>. Useful if you want |
797 | | /// a instance of an Crow application that require Middlewares |
798 | | template<typename... Middlewares> |
799 | | using App = Crow<Middlewares...>; |
800 | | |
801 | | /// \brief Alias of Crow<>. Useful if you want a instance of |
802 | | /// an Crow application that doesn't require of Middlewares |
803 | | using SimpleApp = Crow<>; |
804 | | } // namespace crow |