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