LCOV - code coverage report
Current view: top level - http/server - server_base.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 93.5 % 108 101
Test Date: 2026-11-10 10:38:07 Functions: 83.3 % 18 15

            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
        

Generated by: LCOV version 2.0-1