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