/src/crow/include/crow/http_server.h
Line | Count | Source |
1 | | #pragma once |
2 | | |
3 | | #ifdef CROW_USE_BOOST |
4 | | #include <boost/asio.hpp> |
5 | | #ifdef CROW_ENABLE_SSL |
6 | | #include <boost/asio/ssl.hpp> |
7 | | #endif |
8 | | #else |
9 | | #ifndef ASIO_STANDALONE |
10 | | #define ASIO_STANDALONE |
11 | | #endif |
12 | | #include <asio.hpp> |
13 | | #ifdef CROW_ENABLE_SSL |
14 | | #include <asio/ssl.hpp> |
15 | | #endif |
16 | | #endif |
17 | | |
18 | | #include <atomic> |
19 | | #include <chrono> |
20 | | #include <cstdint> |
21 | | #include <future> |
22 | | #include <memory> |
23 | | #include <thread> |
24 | | #include <vector> |
25 | | |
26 | | #include "crow/version.h" |
27 | | #include "crow/http_connection.h" |
28 | | #include "crow/logging.h" |
29 | | #include "crow/task_timer.h" |
30 | | #include "crow/socket_acceptors.h" |
31 | | |
32 | | |
33 | | namespace crow // NOTE: Already documented in "crow/app.h" |
34 | | { |
35 | | #ifdef CROW_USE_BOOST |
36 | | namespace asio = boost::asio; |
37 | | using error_code = boost::system::error_code; |
38 | | #else |
39 | | using error_code = asio::error_code; |
40 | | #endif |
41 | | using tcp = asio::ip::tcp; |
42 | | using stream_protocol = asio::local::stream_protocol; |
43 | | |
44 | | template<typename Handler, typename Acceptor = TCPAcceptor, typename Adaptor = SocketAdaptor, typename... Middlewares> |
45 | | class Server |
46 | | { |
47 | | public: |
48 | | Server(Handler* handler, |
49 | | typename Acceptor::endpoint endpoint, |
50 | | std::string server_name = std::string("Crow/") + VERSION, |
51 | | std::tuple<Middlewares...>* middlewares = nullptr, |
52 | | unsigned int concurrency = 1, |
53 | | uint8_t timeout = 5, |
54 | | typename Adaptor::context* adaptor_ctx = nullptr): |
55 | 0 | concurrency_(concurrency), |
56 | 0 | task_queue_length_pool_(concurrency_ - 1), |
57 | 0 | acceptor_(io_context_), |
58 | 0 | signals_(io_context_), |
59 | 0 | tick_timer_(io_context_), |
60 | 0 | handler_(handler), |
61 | 0 | timeout_(timeout), |
62 | 0 | server_name_(server_name), |
63 | 0 | middlewares_(middlewares), |
64 | 0 | adaptor_ctx_(adaptor_ctx) |
65 | 0 | { |
66 | 0 | if (startup_failed_) { |
67 | 0 | CROW_LOG_ERROR << "Startup failed; not running server."; |
68 | 0 | return; |
69 | 0 | } |
70 | | |
71 | 0 | error_code ec; |
72 | |
|
73 | 0 | acceptor_.raw_acceptor().open(endpoint.protocol(), ec); |
74 | 0 | if (ec) { |
75 | 0 | CROW_LOG_ERROR << "Failed to open acceptor: " << ec.message(); |
76 | 0 | startup_failed_ = true; |
77 | 0 | return; |
78 | 0 | } |
79 | | |
80 | 0 | acceptor_.raw_acceptor().set_option(Acceptor::reuse_address_option(), ec); |
81 | 0 | if (ec) { |
82 | 0 | CROW_LOG_ERROR << "Failed to set socket option: " << ec.message(); |
83 | 0 | startup_failed_ = true; |
84 | 0 | return; |
85 | 0 | } |
86 | | |
87 | 0 | acceptor_.raw_acceptor().bind(endpoint, ec); |
88 | 0 | if (ec) { |
89 | 0 | CROW_LOG_ERROR << "Failed to bind to " << acceptor_.address() |
90 | 0 | << ":" << acceptor_.port() << " - " << ec.message(); |
91 | 0 | startup_failed_ = true; |
92 | 0 | return; |
93 | 0 | } |
94 | | |
95 | 0 | acceptor_.raw_acceptor().listen(tcp::acceptor::max_listen_connections, ec); |
96 | 0 | if (ec) { |
97 | 0 | CROW_LOG_ERROR << "Failed to listen on port: " << ec.message(); |
98 | 0 | startup_failed_ = true; |
99 | 0 | return; |
100 | 0 | } |
101 | | |
102 | |
|
103 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::Server(crow::Crow<>*, asio::local::basic_endpoint<asio::local::stream_protocol>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::tuple<>*, unsigned int, unsigned char, void*) Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::Server(crow::Crow<>*, asio::ip::basic_endpoint<asio::ip::tcp>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::tuple<>*, unsigned int, unsigned char, void*) |
104 | | |
105 | | void set_tick_function(std::chrono::milliseconds d, std::function<void()> f) |
106 | 0 | { |
107 | 0 | tick_interval_ = d; |
108 | 0 | tick_function_ = f; |
109 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::set_tick_function(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l> >, std::__1::function<void ()>) Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::set_tick_function(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l> >, std::__1::function<void ()>) |
110 | | |
111 | | void on_tick() |
112 | 0 | { |
113 | 0 | tick_function_(); |
114 | 0 | tick_timer_.expires_after(std::chrono::milliseconds(tick_interval_.count())); |
115 | 0 | tick_timer_.async_wait([this](const error_code& ec) { |
116 | 0 | if (ec) |
117 | 0 | return; |
118 | 0 | on_tick(); |
119 | 0 | }); Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::on_tick()::{lambda(std::__1::error_code const&)#1}::operator()(std::__1::error_code const&) constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::on_tick()::{lambda(std::__1::error_code const&)#1}::operator()(std::__1::error_code const&) const |
120 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::on_tick() Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::on_tick() |
121 | | |
122 | | void run() |
123 | 0 | { |
124 | |
|
125 | 0 | if (startup_failed_) { |
126 | 0 | CROW_LOG_ERROR << "Server startup failed. Aborting run()."; |
127 | 0 | return; |
128 | 0 | } |
129 | | |
130 | 0 | uint16_t worker_thread_count = concurrency_ - 1; |
131 | 0 | for (int i = 0; i < worker_thread_count; i++) |
132 | 0 | io_context_pool_.emplace_back(new asio::io_context()); |
133 | 0 | get_cached_date_str_pool_.resize(worker_thread_count); |
134 | 0 | task_timer_pool_.resize(worker_thread_count); |
135 | |
|
136 | 0 | std::vector<std::future<void>> v; |
137 | 0 | std::atomic<int> init_count(0); |
138 | 0 | for (uint16_t i = 0; i < worker_thread_count; i++) |
139 | 0 | v.push_back( |
140 | 0 | std::async( |
141 | 0 | std::launch::async, [this, i, &init_count] { |
142 | | // thread local date string get function |
143 | 0 | auto last = std::chrono::steady_clock::now(); |
144 | |
|
145 | 0 | std::string date_str; |
146 | 0 | auto update_date_str = [&] { |
147 | 0 | auto last_time_t = time(0); |
148 | 0 | tm my_tm; |
149 | |
|
150 | | #if defined(_MSC_VER) || defined(__MINGW32__) |
151 | | gmtime_s(&my_tm, &last_time_t); |
152 | | #else |
153 | 0 | gmtime_r(&last_time_t, &my_tm); |
154 | 0 | #endif |
155 | 0 | date_str.resize(100); |
156 | 0 | size_t date_str_sz = strftime(&date_str[0], 99, "%a, %d %b %Y %H:%M:%S GMT", &my_tm); |
157 | 0 | date_str.resize(date_str_sz); |
158 | 0 | }; Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::run()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::run()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const |
159 | 0 | update_date_str(); |
160 | 0 | get_cached_date_str_pool_[i] = [&]() -> std::string { |
161 | 0 | if (std::chrono::steady_clock::now() - last >= std::chrono::seconds(1)) |
162 | 0 | { |
163 | 0 | last = std::chrono::steady_clock::now(); |
164 | 0 | update_date_str(); |
165 | 0 | } |
166 | 0 | return date_str; |
167 | 0 | }; Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::run()::{lambda()#1}::operator()() const::{lambda()#2}::operator()() constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::run()::{lambda()#1}::operator()() const::{lambda()#2}::operator()() const |
168 | | |
169 | | // initializing task timers |
170 | 0 | detail::task_timer task_timer(*io_context_pool_[i]); |
171 | 0 | task_timer.set_default_timeout(timeout_); |
172 | 0 | task_timer_pool_[i] = &task_timer; |
173 | 0 | task_queue_length_pool_[i] = 0; |
174 | |
|
175 | 0 | init_count++; |
176 | 0 | while (1) |
177 | 0 | { |
178 | 0 | try |
179 | 0 | { |
180 | 0 | if (io_context_pool_[i]->run() == 0) |
181 | 0 | { |
182 | | // when io_service.run returns 0, there are no more works to do. |
183 | 0 | break; |
184 | 0 | } |
185 | 0 | } |
186 | 0 | catch (std::exception& e) |
187 | 0 | { |
188 | 0 | CROW_LOG_ERROR << "Worker Crash: An uncaught exception occurred: " << e.what(); |
189 | 0 | } |
190 | 0 | } |
191 | 0 | })); Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::run()::{lambda()#1}::operator()() constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::run()::{lambda()#1}::operator()() const |
192 | |
|
193 | 0 | if (tick_function_ && tick_interval_.count() > 0) |
194 | 0 | { |
195 | 0 | tick_timer_.expires_after(std::chrono::milliseconds(tick_interval_.count())); |
196 | 0 | tick_timer_.async_wait( |
197 | 0 | [this](const error_code& ec) { |
198 | 0 | if (ec) |
199 | 0 | return; |
200 | 0 | on_tick(); |
201 | 0 | }); Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::run()::{lambda(std::__1::error_code const&)#1}::operator()(std::__1::error_code const&) constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::run()::{lambda(std::__1::error_code const&)#1}::operator()(std::__1::error_code const&) const |
202 | 0 | } |
203 | 0 | handler_->port(acceptor_.port()); |
204 | 0 | handler_->address_is_bound(); |
205 | 0 | CROW_LOG_INFO << server_name_ |
206 | 0 | << " server is running at " << acceptor_.url_display(handler_->ssl_used()) |
207 | 0 | << " using " << concurrency_ << " threads"; |
208 | 0 | CROW_LOG_INFO << "Call `app.loglevel(crow::LogLevel::Warning)` to hide Info level logs."; |
209 | |
|
210 | 0 | signals_.async_wait( |
211 | 0 | [&](const error_code& /*error*/, int /*signal_number*/) { |
212 | 0 | stop(); |
213 | 0 | }); Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::run()::{lambda(std::__1::error_code const&, int)#1}::operator()(std::__1::error_code const&, int) constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::run()::{lambda(std::__1::error_code const&, int)#1}::operator()(std::__1::error_code const&, int) const |
214 | |
|
215 | 0 | while (worker_thread_count != init_count) |
216 | 0 | std::this_thread::yield(); |
217 | |
|
218 | 0 | do_accept(); |
219 | |
|
220 | 0 | std::thread( |
221 | 0 | [this] { |
222 | 0 | notify_start(); |
223 | 0 | io_context_.run(); |
224 | 0 | CROW_LOG_INFO << "Exiting."; |
225 | 0 | }) Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::run()::{lambda()#2}::operator()() constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::run()::{lambda()#2}::operator()() const |
226 | 0 | .join(); |
227 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::run() Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::run() |
228 | | |
229 | | void stop() |
230 | 0 | { |
231 | 0 | shutting_down_ = true; // Prevent the acceptor from taking new connections |
232 | | |
233 | | // Explicitly close the acceptor |
234 | | // else asio will throw an exception (linux only), when trying to start server again: |
235 | | // what(): bind: Address already in use |
236 | 0 | if (acceptor_.raw_acceptor().is_open()) |
237 | 0 | { |
238 | 0 | CROW_LOG_INFO << "Closing acceptor. " << &acceptor_; |
239 | 0 | error_code ec; |
240 | 0 | acceptor_.raw_acceptor().close(ec); |
241 | 0 | if (ec) |
242 | 0 | { |
243 | 0 | CROW_LOG_WARNING << "Failed to close acceptor: " << ec.message(); |
244 | 0 | } |
245 | 0 | } |
246 | |
|
247 | 0 | for (auto& io_context : io_context_pool_) |
248 | 0 | { |
249 | 0 | if (io_context != nullptr) |
250 | 0 | { |
251 | 0 | CROW_LOG_INFO << "Closing IO service " << &io_context; |
252 | 0 | io_context->stop(); // Close all io_services (and HTTP connections) |
253 | 0 | } |
254 | 0 | } |
255 | |
|
256 | 0 | CROW_LOG_INFO << "Closing main IO service (" << &io_context_ << ')'; |
257 | 0 | io_context_.stop(); // Close main io_service |
258 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::stop() Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::stop() |
259 | | |
260 | | |
261 | | uint16_t port() const { |
262 | | return acceptor_.local_endpoint().port(); |
263 | | } |
264 | | |
265 | | /// Wait until the server has properly started or until timeout |
266 | | std::cv_status wait_for_start(std::chrono::steady_clock::time_point wait_until) |
267 | | { |
268 | | std::unique_lock<std::mutex> lock(start_mutex_); |
269 | | |
270 | | std::cv_status status = std::cv_status::no_timeout; |
271 | | while (!server_started_ && !startup_failed_ && status == std::cv_status::no_timeout) |
272 | | status = cv_started_.wait_until(lock, wait_until); |
273 | | return status; |
274 | | } |
275 | | |
276 | | |
277 | | void signal_clear() |
278 | | { |
279 | | signals_.clear(); |
280 | | } |
281 | | |
282 | | void signal_add(int signal_number) |
283 | 0 | { |
284 | 0 | signals_.add(signal_number); |
285 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::signal_add(int) Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::signal_add(int) |
286 | | |
287 | | private: |
288 | | size_t pick_io_context_idx() |
289 | 0 | { |
290 | 0 | size_t min_queue_idx = 0; |
291 | | |
292 | | // TODO improve load balancing |
293 | | // size_t is used here to avoid the security issue https://codeql.github.com/codeql-query-help/cpp/cpp-comparison-with-wider-type/ |
294 | | // even though the max value of this can be only uint16_t as concurrency is uint16_t. |
295 | 0 | for (size_t i = 1; i < task_queue_length_pool_.size() && task_queue_length_pool_[min_queue_idx] > 0; i++) |
296 | | // No need to check other io_services if the current one has no tasks |
297 | 0 | { |
298 | 0 | if (task_queue_length_pool_[i] < task_queue_length_pool_[min_queue_idx]) |
299 | 0 | min_queue_idx = i; |
300 | 0 | } |
301 | 0 | return min_queue_idx; |
302 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::pick_io_context_idx() Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::pick_io_context_idx() |
303 | | |
304 | | void do_accept() |
305 | 0 | { |
306 | 0 | if (!shutting_down_) |
307 | 0 | { |
308 | 0 | size_t context_idx = pick_io_context_idx(); |
309 | 0 | asio::io_context& ic = *io_context_pool_[context_idx]; |
310 | 0 | auto p = std::make_shared<Connection<Adaptor, Handler, Middlewares...>>( |
311 | 0 | ic, handler_, server_name_, middlewares_, |
312 | 0 | get_cached_date_str_pool_[context_idx], *task_timer_pool_[context_idx], adaptor_ctx_, task_queue_length_pool_[context_idx]); |
313 | | |
314 | 0 | CROW_LOG_DEBUG << &ic << " {" << context_idx << "} queue length: " << task_queue_length_pool_[context_idx]; |
315 | |
|
316 | 0 | acceptor_.raw_acceptor().async_accept( |
317 | 0 | p->socket(), |
318 | 0 | [this, p, &ic](error_code ec) { |
319 | 0 | if (!ec) |
320 | 0 | { |
321 | 0 | asio::post(ic, |
322 | 0 | [p] { |
323 | 0 | p->start(); |
324 | 0 | }); Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::do_accept()::{lambda(std::__1::error_code)#1}::operator()(std::__1::error_code) const::{lambda()#1}::operator()() constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::do_accept()::{lambda(std::__1::error_code)#1}::operator()(std::__1::error_code) const::{lambda()#1}::operator()() const |
325 | 0 | } |
326 | 0 | do_accept(); |
327 | 0 | }); Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::do_accept()::{lambda(std::__1::error_code)#1}::operator()(std::__1::error_code) constUnexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::do_accept()::{lambda(std::__1::error_code)#1}::operator()(std::__1::error_code) const |
328 | 0 | } |
329 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::do_accept() Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::do_accept() |
330 | | |
331 | | /// Notify anything using `wait_for_start()` to proceed |
332 | | void notify_start() |
333 | 0 | { |
334 | 0 | std::unique_lock<std::mutex> lock(start_mutex_); |
335 | 0 | server_started_ = true; |
336 | 0 | cv_started_.notify_all(); |
337 | 0 | } Unexecuted instantiation: crow::Server<crow::Crow<>, crow::UnixSocketAcceptor, crow::UnixSocketAdaptor>::notify_start() Unexecuted instantiation: crow::Server<crow::Crow<>, crow::TCPAcceptor, crow::SocketAdaptor>::notify_start() |
338 | | |
339 | | private: |
340 | | unsigned int concurrency_{2}; |
341 | | std::vector<std::atomic<unsigned int>> task_queue_length_pool_; |
342 | | std::vector<std::unique_ptr<asio::io_context>> io_context_pool_; |
343 | | asio::io_context io_context_; |
344 | | std::vector<detail::task_timer*> task_timer_pool_; |
345 | | std::vector<std::function<std::string()>> get_cached_date_str_pool_; |
346 | | Acceptor acceptor_; |
347 | | bool shutting_down_ = false; |
348 | | bool server_started_{false}; |
349 | | bool startup_failed_ = false; |
350 | | std::condition_variable cv_started_; |
351 | | std::mutex start_mutex_; |
352 | | asio::signal_set signals_; |
353 | | |
354 | | asio::basic_waitable_timer<std::chrono::high_resolution_clock> tick_timer_; |
355 | | |
356 | | Handler* handler_; |
357 | | std::uint8_t timeout_; |
358 | | std::string server_name_; |
359 | | bool use_unix_; |
360 | | |
361 | | std::chrono::milliseconds tick_interval_; |
362 | | std::function<void()> tick_function_; |
363 | | |
364 | | std::tuple<Middlewares...>* middlewares_; |
365 | | |
366 | | typename Adaptor::context* adaptor_ctx_; |
367 | | }; |
368 | | } // namespace crow |