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

            Line data    Source code
       1              : #ifndef THINGER_SERVER_HTTP_SERVER_CONNECTION_HPP
       2              : #define THINGER_SERVER_HTTP_SERVER_CONNECTION_HPP
       3              : 
       4              : #include <deque>
       5              : #include <atomic>
       6              : #include "request_factory.hpp"
       7              : #include "../common/http_frame.hpp"
       8              : #include "../common/http_request.hpp"
       9              : #include "../common/http_response.hpp"
      10              : #include "http_stream.hpp"
      11              : #include "connection_tracker.hpp"
      12              : #include "../../asio/async_event.hpp"
      13              : #include "../../asio/watchdog.hpp"
      14              : #include "../../util/types.hpp"
      15              : 
      16              : namespace thinger::http {
      17              : 
      18              : class request;
      19              : struct connection_options;
      20              : 
      21              : namespace detail {
      22              :     class connection_source;
      23              : }
      24              : 
      25              : class server_connection : public std::enable_shared_from_this<server_connection>, public tracked_connection {
      26              : 
      27              :     static constexpr size_t MAX_BUFFER_SIZE = 4096;
      28              : 
      29              : public:
      30              :     // A connection served with the settings of `options` (its server's, see
      31              :     // server_base::set_connection_options()): the defaults if null
      32              :     explicit server_connection(std::shared_ptr<asio::socket> socket,
      33              :                                std::shared_ptr<const connection_options> options = nullptr);
      34              :     virtual ~server_connection();
      35              : 
      36              :     // Count the connection among those of a server (its statistics, and its shutdown), before
      37              :     // start(). A connection taken over then counts as such, while its socket lives
      38              :     void track(const std::shared_ptr<connection_tracker>& tracker);
      39              : 
      40              :     // Start processing requests (spawns the read loop coroutine), closed once idle for the
      41              :     // idle_timeout of its options, or at the deadlines of a request (see connection_options).
      42              :     // Closed right away if the server it is tracked by shuts down
      43              :     void start();
      44              : 
      45              :     // Release the socket for upgrades (WebSocket, etc.)
      46              :     std::shared_ptr<asio::socket> release_socket();
      47              : 
      48              :     // Take over the connection from a stream: send `response`, then, once its request
      49              :     // has been handled, stop serving HTTP and hand the socket to `handler` (can be called
      50              :     // from any thread). If the connection was closed meanwhile (e.g. the client left while
      51              :     // the response was pending), nothing is sent and the handler is released without
      52              :     // being called.
      53              :     void take_over(std::shared_ptr<http_stream> stream, std::shared_ptr<http_frame> response,
      54              :                    takeover_handler handler);
      55              : 
      56              :     // Release this instance without touching the socket
      57              :     void release();
      58              : 
      59              :     // Get the raw socket
      60              :     std::shared_ptr<asio::socket> get_socket();
      61              : 
      62              :     // Queue a response frame, in call order (can be called from any thread)
      63              :     void handle_stream(std::shared_ptr<http_stream> stream, std::shared_ptr<http_frame> frame);
      64              : 
      65              :     // Send the 100 Continue the client of `stream` waits for before sending the body (Expect:
      66              :     // 100-continue), as the body is about to be read: once, in order with the responses, and
      67              :     // only while its response has not started (on the connection thread)
      68              :     void send_continue(http_stream& stream);
      69              : 
      70              :     // Update connection timeout
      71              :     void update_connection_timeout(std::chrono::seconds timeout);
      72              : 
      73              :     // Extend the connection deadline after some activity outside the read loop, such as
      74              :     // reading the request body (on the connection thread)
      75              :     void extend_timeout();
      76              : 
      77              :     // Do not close the connection as idle before `time`: a request with a timeout of its own
      78              :     // (see route::timeout) may wait for its response longer than the idle timeout. The
      79              :     // deadlines of a request still apply (on the connection thread)
      80              :     void hold_until(std::chrono::steady_clock::time_point time);
      81              : 
      82              :     // Whether the connection closes gracefully (see linger()): nothing more is read from it but
      83              :     // by itself (on the connection thread)
      84        43848 :     bool lingering() const { return lingering_; }
      85              : 
      86        23792 :     void set_handler(std::function<awaitable<void>(std::shared_ptr<request>)> handler) {
      87        23792 :         handler_ = std::move(handler);
      88        23792 :     }
      89              : 
      90              : private:
      91              :     // The source of a request read here, for its stream (see detail::request_source)
      92              :     friend class detail::connection_source;
      93              :     std::unique_ptr<detail::request_source> make_source(http_stream& stream);
      94              : 
      95              :     // Read requests, one after another, and hand each to the handler
      96              :     awaitable<void> read_loop();
      97              : 
      98              :     // The parsed request, queued for its response, with the bytes read past its headers
      99              :     std::pair<std::shared_ptr<http_stream>, std::shared_ptr<request>> start_request(
     100              :         const std::shared_ptr<server_connection>& self, const uint8_t* read_ahead, size_t read_ahead_size);
     101              : 
     102              :     // Answer a request that cannot be parsed, or whose header section is too large
     103              :     void reject_request();
     104              : 
     105              :     // The request took the connection over: stop reading, and hand it what was read past it
     106              :     void hand_over_reader(http_stream& stream, request& req);
     107              : 
     108              :     // Move what was read past the request to the buffer, for the next request; false if it
     109              :     // does not fit
     110              :     bool keep_pipelined(request& req, size_t& buffered);
     111              : 
     112              :     // The connection ended without being taken over
     113              :     void stop_reading();
     114              : 
     115              :     // Once reading stops after a request whose response ended: close gracefully if its body
     116              :     // still comes
     117              :     void linger_unread(const http_stream& stream);
     118              : 
     119              :     // The server shuts down: close once idle, or after the response in progress (which tells
     120              :     // the client); or close right away. At the deadline, before the close, the handlers still
     121              :     // running are cancelled (cancel_reason::shutdown)
     122              :     void shutdown_drain() override;
     123              :     void shutdown_cancel() override;
     124              :     void shutdown_close() override;
     125              : 
     126              :     // A frame of `stream` is queued while frame_watch_ is set. Kept out of queue_frame(), which
     127              :     // only tests the flags on the way of every frame
     128              :     [[gnu::noinline, gnu::cold]] void watch_frame(http_stream& stream);
     129              : 
     130              :     // Wait until the response of a stream has started, or the connection is closed
     131              :     awaitable<void> wait_response(const http_stream& stream);
     132              : 
     133              :     // Requests read are bounded by the responses still to write out: no next request is read
     134              :     // while max_queued_responses wait in the queue, or the front one waits for the socket (a
     135              :     // client that does not read). Node pauses its parser so, Go reads one request at a time
     136              :     static constexpr size_t max_queued_responses = 16;
     137        47008 :     bool output_backed_up() const { return writing_ || request_queue_.size() >= max_queued_responses; }
     138              :     // The read loop waits for the output before the next request. A head begun is read to its
     139              :     // end first, within its deadline, which a pause would eat
     140        42573 :     bool reading_paused() const { return output_backed_up() && !reading_head_; }
     141              :     // Wait until the output is no longer backed up, or the connection stops; woken as a response
     142              :     // leaves the queue, or a write that waited completes
     143              :     awaitable<void> wait_output();
     144        20460 :     void output_progressed() { if (output_waited_) [[unlikely]] reader_wake_->set(); }
     145              : 
     146              :     // The peer watcher: the only reader of the socket while a request is handled, and until
     147              :     // its response starts (the read loop reads it otherwise). Once the body of the request
     148              :     // was read, it waits for the socket to be readable: data (the next request) stays there,
     149              :     // a half-close makes the connection close after the response, and anything else (a reset)
     150              :     // cancels the scope of the request (client_gone) and closes the connection. A socket that
     151              :     // cannot wait (e.g. one defined by an application) is not watched
     152              :     awaitable<void> watch_loop();
     153              :     awaitable<void> watch_peer(http_stream& stream);
     154              :     // Watch the client for `stream`; stop it (on its own, but for a read in progress over
     155              :     // TLS, see watch_reading_); stop it for good, as the read loop stops
     156              :     void watch(const std::shared_ptr<http_stream>& stream);
     157              :     void unwatch();
     158              :     void stop_watcher();
     159              :     // Wake the watcher for the stream it watches: started then, the first time
     160              :     void wake_watcher();
     161              :     // Whether the watcher still watches for `stream`
     162              :     bool watching(const http_stream& stream) const;
     163              : 
     164              :     // Queue a response frame (on the connection executor)
     165              :     void queue_frame(const std::shared_ptr<http_stream>& stream, std::shared_ptr<http_frame> frame);
     166              : 
     167              :     // Once a frame of `stream` is written (or its write failed, with `ec`): close the
     168              :     // connection or hand it over when its response ends, as required
     169              :     void frame_written(http_stream& stream, const std::shared_ptr<http_frame>& frame,
     170              :                        const boost::system::error_code& ec);
     171              : 
     172              :     // A frame of a stream whose response is observed (see http_application::on_response) is
     173              :     // about to be written
     174              :     [[gnu::noinline, gnu::cold]] static void observe_frame(http_stream& stream, http_frame& frame);
     175              : 
     176              :     // Write the queued frames, in order
     177              :     void process_output_queue();
     178              : 
     179              :     // Handle stock error responses
     180              :     void handle_stock_error(std::shared_ptr<http_stream> stream, http_response::status status);
     181              : 
     182              :     // Close connection
     183              :     void close();
     184              : 
     185              :     // Close the connection once the response of `stream` ended: gracefully if input of its
     186              :     // request may still be arriving (see linger()), else at once
     187              :     void close_after(const http_stream& stream);
     188              : 
     189              :     // Close gracefully (RFC 9112 section 9.6): stop sending, drop what keeps arriving (up to
     190              :     // the linger limits of its options, see connection_options), then close. At once if the
     191              :     // socket cannot half-close, or either limit is zero; briefly if the server shuts down
     192              :     void linger();
     193              :     awaitable<void> drop_input(size_t max_bytes, std::chrono::milliseconds timeout);
     194              : 
     195              :     // Hand the socket over once the response is written and the read loop has stopped
     196              :     void complete_takeover(http_stream& stream);
     197              : 
     198              :     // The deadlines of the connection (server_connection_deadlines.cpp), all on timeout_timer_,
     199              :     // armed at the earliest of those that apply: idle (from the last activity, not before the
     200              :     // hold); while the head of a request is read, the header deadline, and while its body is,
     201              :     // the request deadline (both from the start of the request, see connection_options); while
     202              :     // a response waits for the socket, the write deadline instead of idle (from its last
     203              :     // progress). On expiry, the one that passed acts (see deadline_expired()); none passed (the
     204              :     // head completed, the body was read, or the write went on, meanwhile), it is armed again
     205              :     using clock = std::chrono::steady_clock;
     206              :     void arm_deadline();
     207              :     clock::time_point next_deadline() const;
     208              :     void deadline_expired();
     209              :     // Idle, or the write deadline while a response waits for the socket (see write_timeout)
     210              :     clock::time_point idle_deadline() const;
     211              :     // A frame waits for the socket (see process_output_queue()): its write deadline starts
     212              :     void write_started();
     213              :     // Whether `buffers` are written in slices, each one moving the write deadline (see
     214              :     // asio::socket::write_slices()): a write deadline applies and they exceed one
     215              :     bool write_in_slices(const std::vector<boost::asio::const_buffer>& buffers) const;
     216              :     // The deadline of the head being read, if any applies (false otherwise)
     217              :     bool head_deadline(clock::time_point& deadline) const;
     218              :     // Bytes of a request head are about to be parsed (`pipelined`: read with the request before
     219              :     // it): the first ones start the request, at the read that got them, or now for a pipelined
     220              :     // one. A read leaving its head incomplete arms its header deadline (see head_incomplete())
     221              :     void head_started(bool pipelined);
     222              :     void head_incomplete();
     223              :     // The head of a request was not complete at its deadline: 408, or a silent close if no
     224              :     // byte of it arrived
     225              :     void head_timed_out();
     226              :     // The deadline of the body of the request in progress, while it is not read to its end, if
     227              :     // any applies (false otherwise)
     228              :     bool body_deadline(clock::time_point& deadline) const;
     229              :     // The request deadline of the request of `stream`, set by its route (see
     230              :     // route::request_timeout), from the start of the request; zero for none
     231              :     void set_request_deadline(const http_stream& stream, std::chrono::milliseconds deadline);
     232              :     // The deadline the handler set for reading the body of the request of `stream` (only compared:
     233              :     // it may be gone), replacing its request deadline (see request::set_read_deadline()); the end
     234              :     // of time removes it
     235              :     void set_read_deadline(const http_stream* stream, clock::time_point deadline);
     236              :     // Whether a read deadline is set and has passed: a read then fails at once
     237              :     // (read_deadline_expired)
     238              :     bool read_deadline_passed() const;
     239              :     // A read of the body starts and ends, bound to read_cancel_, which the watchdog emits as its
     240              :     // deadline passes, whenever it was set: nothing else on the socket is cancelled, and the
     241              :     // same read goes on when the deadline only moves. `caller` is the cancellation of the
     242              :     // coroutine reading (its `||`, the scope of the request), forwarded to the read while it waits
     243              :     void body_read_started(boost::asio::cancellation_slot caller);
     244              :     void body_read_ended(boost::asio::cancellation_slot caller, io_result& result);
     245              :     // The body of the request of `stream` was not read to its end at its deadline: its scope is
     246              :     // cancelled (cancel_reason::timeout), and it is answered 408 unless its response started,
     247              :     // or another one is being written; the connection is closed after it (or at once). What
     248              :     // the handler reads or writes afterwards fails
     249              :     void request_timed_out(http_stream& stream);
     250              :     // Answer `status` with a stock reply on a stream of its own, closing the connection after
     251              :     // it (gracefully if `input_pending`)
     252              :     void answer_and_close(http_response::status status, detail::close_reason reason, bool input_pending);
     253              : 
     254              : private:
     255              :     std::shared_ptr<asio::socket> socket_;
     256              : 
     257              :     // The connection is closed when a deadline passes (see arm_deadline()): activity moves the
     258              :     // idle one later (see extend_timeout()), a request holds it (see hold_until())
     259              :     asio::watchdog timeout_timer_;
     260              : 
     261              :     // Wakes the read loop waiting for a response to start, or for the responses to write out
     262              :     // (see wait_output()); set as it stops too. Created by its first wait
     263              :     std::optional<asio::async_event> reader_wake_;
     264              : 
     265              :     // Bound to the read loop, which runs the handlers: the scope of the request being handled
     266              :     // emits it to interrupt its handler (see http_stream::bind_handler())
     267              :     boost::asio::cancellation_signal handler_signal_;
     268              : 
     269              :     // The peer watcher (see watch_peer()): the stream it watches for (none while idle), set
     270              :     // by the read loop, which wakes it (see wake_watcher()); the slot of its coroutine, emitted
     271              :     // to end its wait; set once a read of it ends. Its events are created as it starts
     272              :     std::shared_ptr<http_stream> watched_;
     273              :     std::optional<asio::async_event> watch_wake_;
     274              :     boost::asio::cancellation_signal watch_stop_;
     275              :     std::optional<asio::async_event> watch_idle_;
     276              : 
     277              :     // Rare cases where a queued frame matters, all tested at once on the way of every frame:
     278              :     // the read loop waits for a response to start, the connection drains, or the request in
     279              :     // progress waits for its 100 Continue
     280              :     enum : uint8_t { watch_response_start = 1, watch_draining = 2, watch_continue = 4 };
     281              :     uint8_t frame_watch_{0};
     282              : 
     283              :     // Stream of the request the read loop is handling, none while it waits for a request
     284              :     http_stream* in_progress_{nullptr};
     285              :     // The idle timeout (from the options, see update_connection_timeout())
     286              :     std::chrono::seconds timeout_;
     287              :     // The last activity (a read, a frame written), the idle deadline counting from it; the
     288              :     // connection is never closed as idle before the hold (see hold_until())
     289              :     clock::time_point last_activity_{};
     290              :     clock::time_point hold_{};
     291              :     // The start of the request being read: its first byte, the start of serving the connection
     292              :     // for the first one, or the start of parsing a pipelined one
     293              :     clock::time_point request_start_{};
     294              :     // The last progress of the frame waiting for the socket (see write_started())
     295              :     clock::time_point write_progress_{};
     296              :     // The request deadline of the request in progress while its body is read: the options',
     297              :     // or its route's (zero: none)
     298        23792 :     std::chrono::milliseconds request_deadline_{0};
     299              :     // The read deadline its handler set (see set_read_deadline()), and the signal ending the body
     300              :     // read that waits (see body_read_started())
     301              :     clock::time_point read_deadline_{};
     302              :     boost::asio::cancellation_signal read_cancel_;
     303              : 
     304              :     uint8_t buffer_[MAX_BUFFER_SIZE];
     305              :     request_factory request_parser_;
     306              : 
     307              :     // Queue for HTTP pipelining (only used from the connection thread, as everything that
     308              :     // reads or writes the connection)
     309              :     std::deque<std::shared_ptr<http_stream>> request_queue_;
     310              : 
     311              :     std::function<awaitable<void>(std::shared_ptr<request>)> handler_;
     312              : 
     313              :     // The settings of its server, shared by its connections
     314              :     std::shared_ptr<const connection_options> options_;
     315              : 
     316              :     // Buffers of the frame being written (kept, so they are not allocated for every frame)
     317              :     std::vector<boost::asio::const_buffer> write_buffers_;
     318              : 
     319              :     // State
     320              :     bool writing_{false};
     321              :     bool running_{false};
     322              :     // The read loop stopped without handing the connection over: it cannot be taken over
     323              :     bool reader_stopped_{false};
     324              :     // Closing gracefully (see linger()). Where it takes no room
     325              :     bool lingering_{false};
     326              :     // The watcher waits (for the body to be read, or the socket to be readable): unwatch()
     327              :     // ends the wait. Or it reads (peer_closed(), which may wait for a TLS record)
     328              :     bool watch_waiting_{false};
     329              :     bool watch_reading_{false};
     330              :     // The head of a request is being read, incomplete: its header deadline applies
     331              :     bool reading_head_{false};
     332              :     // A frame waits for the socket, within its write deadline (see write_started())
     333              :     bool write_waits_{false};
     334              :     // The read loop waits for the output (see wait_output())
     335              :     bool output_waited_{false};
     336              :     // Whether the request in progress has a read deadline, a read of its body waits for the
     337              :     // socket, and it was ended at that deadline
     338              :     bool read_bounded_{false};
     339              :     bool read_waits_{false};
     340              :     bool read_expired_{false};
     341              :     stream_id request_id_{0};
     342              : };
     343              : 
     344              : }
     345              : 
     346              : #endif
        

Generated by: LCOV version 2.0-1