Line data Source code
1 : #include "server_base.hpp"
2 : #include "server_connection.hpp"
3 : #include "request.hpp"
4 : #include "../../util/logger.hpp"
5 : #include <boost/asio/error.hpp>
6 : #include <algorithm>
7 : #include <variant>
8 :
9 : namespace thinger::http {
10 :
11 : namespace {
12 :
13 : using listen_endpoint = decltype(asio::listener_options::endpoint);
14 :
15 : // Whether a listener opened for `opened` (listening on `port`) listens where `wanted` asks to:
16 : // the same Unix socket, or the same host and port (a port 0 asks for a new one)
17 1925 : bool same_endpoint(const listen_endpoint& opened, uint16_t port, const listen_endpoint& wanted) {
18 1925 : if (opened.index() != wanted.index()) return false;
19 1397 : if (auto* local = std::get_if<asio::unix_endpoint>(&wanted)) {
20 303 : return std::get<asio::unix_endpoint>(opened).path == local->path;
21 : }
22 1094 : const auto& tcp = std::get<asio::tcp_endpoint>(wanted);
23 1094 : return tcp.port != 0 && tcp.port == port && std::get<asio::tcp_endpoint>(opened).host == tcp.host;
24 : }
25 :
26 : }
27 :
28 444 : void server_base::set_connection_options(connection_options options) {
29 444 : auto shared = std::make_shared<const connection_options>(std::move(options));
30 444 : std::scoped_lock lock(listeners_mutex_);
31 444 : options_ = std::move(shared);
32 444 : }
33 :
34 24408 : boost::system::error_code server_base::listen(asio::listener_options options) {
35 24408 : std::shared_ptr<asio::listener> listener;
36 : {
37 : // Reserved before it opens (out of the locks): what stops the listeners from now on, a
38 : // shutdown starting too, stops it, and another listen() on its endpoint is refused
39 24408 : std::scoped_lock shutdown_lock(shutdown_mutex_);
40 24408 : std::scoped_lock lock(listeners_mutex_);
41 25727 : for (const auto& open : listeners_) {
42 1925 : if (!same_endpoint(open.endpoint, open.port, options.endpoint)) continue;
43 606 : if (open.url.empty()) LOG_ERROR("cannot listen: another listen() is opening that endpoint");
44 6 : else LOG_ERROR("cannot listen on {}: the server listens there already", open.url);
45 606 : return boost::asio::error::address_in_use;
46 : }
47 23802 : if (!reset_shutdown()) {
48 904 : LOG_ERROR("cannot listen: the server is shutting down");
49 904 : return boost::asio::error::shut_down;
50 : }
51 22898 : auto endpoint = options.endpoint;
52 22898 : auto* tcp = std::get_if<asio::tcp_endpoint>(&endpoint);
53 22898 : uint16_t port = tcp ? tcp->port : 0;
54 22898 : listener = create_listener(std::move(options));
55 22898 : listeners_.push_back({listener, std::move(endpoint), port, {}});
56 25918 : }
57 : // (why it cannot is logged by the listener; operation_aborted if stopped meanwhile)
58 22898 : if (auto ec = listener->open()) {
59 1691 : std::scoped_lock lock(listeners_mutex_);
60 1730 : std::erase_if(listeners_, [&](const open_listener& open) { return open.listener == listener; });
61 1691 : return ec;
62 1691 : }
63 21207 : auto url = listener->url();
64 : {
65 21207 : std::scoped_lock lock(listeners_mutex_);
66 21207 : auto open = std::ranges::find(listeners_, listener, &open_listener::listener);
67 : // Taken by stop_listening() (from stop() or a shutdown too) once open: stopped by it
68 21207 : if (open == listeners_.end()) return boost::asio::error::operation_aborted;
69 20793 : open->port = listener->local_port();
70 20793 : open->url = url;
71 : // Under the lock, as it only starts accepting (on the server's threads): whatever stops
72 : // the listeners takes it once it accepts
73 44579 : listener->start([this](std::shared_ptr<asio::socket> socket) { serve(std::move(socket)); });
74 21207 : }
75 20793 : LOG_INFO("listening on {}", url);
76 20793 : listening();
77 20793 : return {};
78 47306 : }
79 :
80 1003 : boost::system::error_code server_base::listen(std::string host, uint16_t port) {
81 1003 : asio::listener_options options;
82 1003 : options.endpoint = asio::tcp_endpoint{std::move(host), port};
83 2006 : return listen(std::move(options));
84 1003 : }
85 :
86 0 : bool server_base::start(uint16_t port) {
87 0 : return start("0.0.0.0", port);
88 : }
89 :
90 0 : bool server_base::start(uint16_t port, const sync_callback<void()> &on_listening) {
91 0 : return start("0.0.0.0", port, on_listening);
92 : }
93 :
94 0 : bool server_base::start(const std::string& host, uint16_t port) {
95 0 : return start(host, port, nullptr);
96 : }
97 :
98 54 : bool server_base::start(const std::string& host, uint16_t port, const sync_callback<void()> &on_listening) {
99 54 : if (listen(host, port)) {
100 0 : return false;
101 : }
102 54 : if (on_listening) {
103 54 : on_listening();
104 : }
105 54 : wait();
106 54 : return true;
107 : }
108 :
109 22047 : void server_base::stop() {
110 22047 : end_shutdown();
111 : // Not logged when a shutdown stops it: it stopped listening already, and the shutdown tells
112 22047 : auto name = is_listening() ? log_name() : std::string();
113 : // (a listen() in progress too)
114 22047 : stop_listening();
115 : // The connections still open are closed (those accepted meanwhile too, see
116 : // server_connection::start()), and the handlers still running cancelled: none is served
117 : // again, even if the server listens again. Not waited for: the closes keep the tracker, so
118 : // they close the connections once the server is gone too
119 22047 : connections_->set_phase(connection_tracker::phase::closing);
120 22047 : connections_->cancel_and_close_all([tracker = connections_] {});
121 22047 : if (!name.empty()) LOG_INFO("{} stopped", name);
122 22047 : }
123 :
124 41411 : void server_base::stop_listening() {
125 41411 : std::vector<open_listener> listeners;
126 : {
127 41411 : std::scoped_lock lock(listeners_mutex_);
128 41411 : listeners.swap(listeners_);
129 41411 : }
130 : // Out of the lock: stopping waits for the handlers running on other threads, which serve().
131 : // A listener still opening gives up (see listen())
132 64282 : for (auto& open : listeners) open.listener->stop();
133 41411 : }
134 :
135 24270 : bool server_base::is_listening() const {
136 24270 : std::scoped_lock lock(listeners_mutex_);
137 57162 : return std::ranges::any_of(listeners_, [](const open_listener& open) { return !open.url.empty(); });
138 24270 : }
139 :
140 28769 : std::vector<std::string> server_base::listening_on() const {
141 28769 : std::scoped_lock lock(listeners_mutex_);
142 28769 : std::vector<std::string> urls;
143 28769 : urls.reserve(listeners_.size());
144 50874 : for (const auto& open : listeners_) {
145 22105 : if (!open.url.empty()) urls.push_back(open.url);
146 : }
147 57538 : return urls;
148 28769 : }
149 :
150 :
151 : // Private methods
152 11712 : std::string server_base::log_name() const {
153 11712 : std::string name;
154 22989 : for (const auto& url : listening_on()) {
155 11277 : name += (name.empty() ? "server on " : ", ") + url;
156 11712 : }
157 24052 : return name.empty() ? "server" : name;
158 11712 : }
159 :
160 23786 : void server_base::serve(std::shared_ptr<asio::socket> socket) {
161 23786 : std::shared_ptr<const connection_options> options;
162 : {
163 23786 : std::scoped_lock lock(listeners_mutex_);
164 23786 : options = options_;
165 23786 : }
166 23786 : auto connection = std::make_shared<server_connection>(std::move(socket), std::move(options));
167 23786 : connection->track(connections_);
168 :
169 : // Never this server, which may be destroyed while the connection lives
170 23786 : connection->set_handler(connection_handler());
171 :
172 23786 : connection->start();
173 23786 : }
174 :
175 : } // namespace thinger::http
|