LCOV - code coverage report
Current view: top level - http/server - server_base.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 7 7
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 2 2

            Line data    Source code
       1              : #ifndef THINGER_HTTP_SERVER_BASE_HPP
       2              : #define THINGER_HTTP_SERVER_BASE_HPP
       3              : 
       4              : #include "http_application.hpp"
       5              : #include "connection_tracker.hpp"
       6              : #include "../common/body_logging.hpp"
       7              : #include "../../asio/listener.hpp"
       8              : #include "../../util/types.hpp"
       9              : #include <boost/system/error_code.hpp>
      10              : #include <memory>
      11              : #include <string>
      12              : #include <functional>
      13              : #include <chrono>
      14              : #include <vector>
      15              : #include <mutex>
      16              : #include <optional>
      17              : 
      18              : namespace thinger::http {
      19              : 
      20              : // How the connections of a server are served (see server_base::set_connection_options())
      21              : struct connection_options {
      22              :     // A connection idle for this long (no request arriving, nor a response being written, nor
      23              :     // the handler of a request received whole running) is closed
      24           18 :     std::chrono::seconds idle_timeout{120};
      25              : 
      26              :     // Deadlines of a request, whatever the pace of its bytes (Node's headersTimeout and
      27              :     // requestTimeout), from its start: its first byte, or the start of serving the connection
      28              :     // for its first request. A head not complete within `header_timeout` (capped by
      29              :     // `request_timeout`) is answered 408 Request Timeout and the connection closed, at once;
      30              :     // a connection that sent no byte of it is closed without a response. A request whose body
      31              :     // was not read to its end within `request_timeout` (which a route, a group or a virtual
      32              :     // host may change, see route::request_timeout()) is answered 408, or the connection closed
      33              :     // if its response already started; its handler is cancelled (cancel_reason::timeout). Zero
      34              :     // disables either
      35           18 :     std::chrono::milliseconds header_timeout{60000};
      36           18 :     std::chrono::milliseconds request_timeout{300000};
      37              : 
      38              :     // A response write of which the socket accepts no 64 KiB slice (the whole write, if
      39              :     // smaller) for this long closes the connection, as a client that stopped reading, whether
      40              :     // it was to be kept alive or not (nginx's send_timeout); `idle_timeout` never cuts a
      41              :     // response being written. The client taking bytes from the socket is not checked, as SSE
      42              :     // and WebSocket writes do: a slow client with a large send buffer may be cut while it
      43              :     // still reads, if the socket accepts no slice for this long. Zero disables it
      44           18 :     std::chrono::milliseconds write_timeout{60000};
      45              : 
      46              :     // Lingering close (RFC 9112 section 9.6): a connection closed after a response while the
      47              :     // client may still be sending its request (e.g. a body over the limit, answered 413 before
      48              :     // it was read) stops sending, then reads and drops what arrives, until the client closes,
      49              :     // `linger_max_bytes` were dropped or `linger_timeout` passed, and closes. Closed at once
      50              :     // instead, the unread data makes the system reset the connection, and a client still
      51              :     // sending may never read the response. Zero for either closes at once (also on sockets that
      52              :     // cannot half-close, such as custom ones). A graceful shutdown of the server closes the
      53              :     // connections lingering after at most 500 ms
      54              :     size_t linger_max_bytes = 8 << 20;
      55           18 :     std::chrono::milliseconds linger_timeout{5000};
      56              : 
      57              :     // Log the header values and the start of the bodies of the requests and responses: an
      58              :     // opt-in for debugging, off by default (see body_logging)
      59              :     body_logging logging;
      60              : };
      61              : 
      62              : // A server is an application (see http_application: routes, virtual hosts, middlewares and
      63              : // request processing settings) served on listeners: TCP, with or without TLS, or Unix sockets.
      64              : // It only adds the listeners and the connection settings. Every listener serves the same
      65              : // application: a route (or middleware, virtual host, hook) answers on all of them. Add them
      66              : // before any request arrives (before listen(), or after it but before a request comes): every
      67              : // request reads them without locks, so changing them while serving is a data race. An
      68              : // http_application can also be used on its own, e.g. for in-memory dispatch.
      69              : class server_base : public http_application {
      70              : public:
      71        10605 :     server_base() = default;
      72              :     // Stops the server as server_base::stop() does (the subclasses in the library call their
      73              :     // own stop() first; one whose listeners use something it owns, e.g. an io_context, must
      74              :     // call stop() in its destructor too): once it returns, its connections are closing and its
      75              :     // handlers cancelled, none waited for, and no code of the library touches the server. A handler still running keeps the routes, hosts,
      76              :     // middlewares, hooks and settings it reaches (shared with its connection), so it may
      77              :     // finish its current step against them; what it captured itself (the server included) is
      78              :     // the application's to keep alive. It may be destroyed from one of its own handlers, on
      79              :     // any worker thread: that handler goes on, and must not touch the server afterwards
      80              :     ~server_base() override;
      81              : 
      82              :     // --- Server configuration ---
      83              :     // How its connections are served: copied once, and read by every connection. Set it before
      84              :     // listen(): a later call applies to the connections accepted afterwards only, never to
      85              :     // those open
      86              :     void set_connection_options(connection_options options);
      87              : 
      88              :     // --- Server control ---
      89              :     // Listen with `options` (a TCP endpoint, with TLS if `options.tls` holds a certificate
      90              :     // manager, or a Unix socket; see asio::listener_options), and serve the connections it
      91              :     // accepts. May be called several times, each call opening a listener of its own: all serve
      92              :     // the application. An error, with the listeners open left as they are, if:
      93              :     //  - the listener cannot open (see asio::listener::open());
      94              :     //  - the server listens on that endpoint already, or another listen() is opening it
      95              :     //    (`address_in_use`; the same host and port, or the same Unix socket path);
      96              :     //  - a shutdown is in progress (`shut_down`): it works again once the shutdown ended;
      97              :     //  - stop_listening(), stop() or a shutdown stopped the listener while it opened
      98              :     //    (`operation_aborted`), e.g. while it waited to bind again.
      99              :     // The listener is in listening_on() from the moment it accepts connections, so whatever
     100              :     // stops the listeners, on any thread, stops it too.
     101              :     boost::system::error_code listen(asio::listener_options options);
     102              :     // Listen on plain TCP: the host and the port (0 for one chosen by the system, see
     103              :     // listening_on())
     104              :     boost::system::error_code listen(std::string host, uint16_t port);
     105              : 
     106              :     // Listen on plain TCP and wait() until the server stops: false if it cannot listen.
     107              :     // `on_listening` (if any) is called once it listens, before waiting
     108              :     bool start(uint16_t port);
     109              :     bool start(uint16_t port, const sync_callback<void()> &on_listening);
     110              :     bool start(const std::string& host, uint16_t port);
     111              :     bool start(const std::string& host, uint16_t port, const sync_callback<void()> &on_listening);
     112              : 
     113              :     // The three steps of stopping a server: stop_listening() (quiesce), shutdown() (drain, until
     114              :     // a deadline) and stop() (release). Each may be called from any thread, more than once.
     115              : 
     116              :     // Stop listening right away, on every listener: new connections are refused. Those open are
     117              :     // left as they are: they go on serving, and count in stats(). Later calls do nothing
     118              :     void stop_listening();
     119              : 
     120              :     // Graceful shutdown, with `by` as its whole budget (nothing waits past it):
     121              :     //  1. it stops listening, if that was not done already (see stop_listening());
     122              :     //  2. it calls on_shutdown(), tells the connections taken over (WebSockets, event streams,
     123              :     //     tunnels...) to finish (see websocket_connection::on_shutdown(),
     124              :     //     sse_connection::on_shutdown(), server_scope::on_shutdown()), closes the idle connections, and
     125              :     //     lets those handling a request finish it: its response tells the client the connection
     126              :     //     closes (Connection: close), and then it closes;
     127              :     //  3. at `by`, it closes whatever is still open.
     128              :     // Then the server stops (wait() returns). The result is true if every
     129              :     // connection finished before `by`; false if some were closed, or stop() ended the shutdown
     130              :     // first. The handlers still running at `by` are not waited for: they go on on their threads.
     131              :     //
     132              :     // It may be awaited from any executor, on a thread of the server too (a handler, a worker of
     133              :     // a pool_server), which it does not block. Every listener drains at once, under the same
     134              :     // `by`. Several calls, also concurrent ones, share the same end and result: the first one
     135              :     // sets `by`. From an asio::signal_set handler too, but not from a raw signal handler: it is
     136              :     // not async-signal-safe (it locks and allocates). A server that is not running (wait() not
     137              :     // called, or stopped) ends it at once: true if no connection is open.
     138              :     awaitable<bool> shutdown(std::chrono::steady_clock::time_point by);
     139              :     // The same, with `by` `grace` from now, blocking until it ends: for a thread that does not
     140              :     // serve the server (e.g. main()). On one that does (a handler, a worker of a pool_server) it
     141              :     // would block the drain it waits for, so it does not wait there: it logs an error, starts
     142              :     // the shutdown, and returns false at once (co_await shutdown(by) there instead)
     143              :     bool shutdown(std::chrono::milliseconds grace = std::chrono::seconds(30));
     144              : 
     145              :     // Stop the server right away: stop listening, end a shutdown in progress (its result is
     146              :     // false), close the connections still open (idle keep-alive, handling a request, or taken
     147              :     // over: WebSockets, event streams, tunnels...), and release wait(). The handlers still
     148              :     // running are cancelled with cancel_reason::shutdown, whatever their route declared (their
     149              :     // next cancellable await ends with operation_aborted), and are not waited for: each
     150              :     // connection is closed on its own thread, after its return, and what a handler sends
     151              :     // afterwards is dropped. A handler still running keeps what the server serves with (routes,
     152              :     // hosts, middlewares, hooks, settings), so it may finish its current step against it; what
     153              :     // it captured itself is the application's (see ~server_base()). It does not block, so it may
     154              :     // be called from one of the server's handlers, on any thread. The connections it closed are
     155              :     // never served again, even if the server listens again (a server with its own io_context
     156              :     // stops running it too).
     157              :     //
     158              :     // The end of a shutdown does not call it: it may end on a thread of the server while the
     159              :     // server is being destroyed, so it stops the server without a virtual call (see
     160              :     // set_shutdown_stop()). An override (in a subclass of pool_server or server) runs only when
     161              :     // stop() is called, never at the end of a shutdown: what it releases after a shutdown, it
     162              :     // releases once shutdown() or wait() returned, or in its destructor
     163              :     virtual void stop();
     164              : 
     165              :     // Called once when a graceful shutdown starts, on the thread starting it, once the server
     166              :     // stopped listening: for the application to finish what it runs on connections taken over
     167              :     // (or anything else). What it throws is logged, and the shutdown goes on
     168              :     void on_shutdown(sync_callback<void()> callback);
     169              : 
     170              :     // Connections open by what they serve, and served since the server was created, over all
     171              :     // its listeners. It locks each list the connections are kept in (by io_context): meant to be
     172              :     // read now and then (e.g. by a monitoring endpoint), as counting costs nothing per request
     173              :     server_stats stats() const;
     174              : 
     175              :     // Whether it listens on any listener
     176              :     bool is_listening() const;
     177              : 
     178              :     // Where it listens, one entry per listener open, in the order they opened:
     179              :     // "http://0.0.0.0:8080", "https://[::1]:8443" or "unix:/run/x.sock", with the port the
     180              :     // system chose for a port 0. Empty while it does not listen
     181              :     std::vector<std::string> listening_on() const;
     182              : 
     183              :     // Wait until the server stops
     184              :     virtual void wait() = 0;
     185              : 
     186              : protected:
     187              :     // The listener of `options`, on the executors of the server: not opened yet
     188              :     virtual std::unique_ptr<asio::listener> create_listener(asio::listener_options options) = 0;
     189              : 
     190              :     // A listener opened and started: the server serves (e.g. its io_context may run)
     191        12192 :     virtual void listening() {}
     192              : 
     193              :     // Where a shutdown waits for the connections: an io_context running while the server
     194              :     // runs, or none if nothing runs the connections
     195              :     virtual boost::asio::io_context* shutdown_context() = 0;
     196              :     // Whether the calling thread serves connections of the server (shutdown(grace) cannot block
     197              :     // there)
     198              :     virtual bool in_server_thread() = 0;
     199              : 
     200              :     // A shutdown in progress (if any) ends: stop() calls it first. Waits for one ending on
     201              :     // another thread
     202              :     void end_shutdown();
     203              : 
     204              :     // What the end of a shutdown runs, once it stopped listening, to stop the server as stop()
     205              :     // does (e.g. releasing wait()), instead of calling stop(): on the thread it ends on, where
     206              :     // a subclass may be in its destructor already. pool_server and server set it in their
     207              :     // constructors, and call stop() in their destructors, which waits for a shutdown ending on
     208              :     // another thread (so it never runs once their members are gone). None: the end of a
     209              :     // shutdown only stops listening
     210              :     void set_shutdown_stop(sync_callback<void()> stop);
     211              : 
     212              : private:
     213              :     struct shutdown_state;
     214              :     // A listener of listen(), there from before it opens, so that stopping the listeners stops
     215              :     // it too, and another listen() on its endpoint is refused
     216              :     struct open_listener {
     217              :         std::shared_ptr<asio::listener> listener;   // (opened by listen() out of the lock)
     218              :         decltype(asio::listener_options::endpoint) endpoint;   // as asked for
     219              :         uint16_t port = 0;   // the TCP port asked for, then the one it listens on
     220              :         std::string url;     // once it listens (not listed before)
     221              :     };
     222              : 
     223              :     void serve(std::shared_ptr<asio::socket> socket);
     224              :     // The server, for the logs: "server on http://0.0.0.0:8080" ("server" while not listening)
     225              :     std::string log_name() const;
     226              :     // The shutdown in progress, or the one that ended (until the server listens again); else
     227              :     // one started, until `by`
     228              :     std::shared_ptr<shutdown_state> start_shutdown(std::chrono::steady_clock::time_point by);
     229              :     // With shutdown_mutex_ held: false while a shutdown is in progress; else the server serves
     230              :     // (again, after a shutdown that ended)
     231              :     bool reset_shutdown();
     232              : 
     233              :     // The connections served, for the statistics and the shutdown
     234              :     std::shared_ptr<connection_tracker> connections_ = std::make_shared<connection_tracker>();
     235              : 
     236              :     // The listeners open and opening, and the connection options (guarded by listeners_mutex_:
     237              :     // listen() and stop() may be called from any thread, and connections are accepted on the
     238              :     // server's). Taken after shutdown_mutex_ when both are
     239              :     mutable std::mutex listeners_mutex_;
     240              :     std::vector<open_listener> listeners_;
     241              :     std::shared_ptr<const connection_options> options_ = std::make_shared<const connection_options>();
     242              : 
     243              :     sync_callback<void()> on_shutdown_;
     244              :     // Set once, when the server is constructed (see set_shutdown_stop())
     245              :     sync_callback<void()> shutdown_stop_;
     246              :     // The last shutdown (guarded by its mutex)
     247              :     mutable std::mutex shutdown_mutex_;
     248              :     std::shared_ptr<shutdown_state> shutdown_;
     249              : };
     250              : 
     251              : } // namespace thinger::http
     252              : 
     253              : #endif // THINGER_HTTP_SERVER_BASE_HPP
        

Generated by: LCOV version 2.0-1