LCOV - code coverage report
Current view: top level - http/server - http_stream.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 98.1 % 106 104
Test Date: 2026-11-10 10:38:07 Functions: 91.5 % 47 43

            Line data    Source code
       1              : #ifndef HTTP_STREAM_HPP
       2              : #define HTTP_STREAM_HPP
       3              : 
       4              : #include <atomic>
       5              : #include <chrono>
       6              : #include <list>
       7              : #include <queue>
       8              : #include <memory>
       9              : #include <optional>
      10              : #include <functional>
      11              : #include <string>
      12              : #include <string_view>
      13              : #include <utility>
      14              : #include <boost/asio/any_io_executor.hpp>
      15              : #include <boost/asio/cancellation_signal.hpp>
      16              : #include "../common/http_frame.hpp"
      17              : #include "../../asio/async_event.hpp"
      18              : #include "../../asio/ssl/tls_session.hpp"
      19              : #include "../../util/types.hpp"
      20              : #include "upgrade.hpp"
      21              : 
      22              : namespace thinger::asio {
      23              :     class socket;
      24              : }
      25              : 
      26              : namespace thinger::http {
      27              : 
      28              :     class response_observer;
      29              :     class http_request;
      30              : 
      31              :     typedef uint32_t stream_id;
      32              : 
      33              :     // Why the scope of a request was cancelled (defined in request.hpp)
      34              :     enum class cancel_reason : uint8_t;
      35              : 
      36              :     namespace detail {
      37              : 
      38              :         // Why a connection closes after the response of a stream
      39              :         enum class close_reason : uint8_t {
      40              :             client_asked,       // the request said Connection: close (or its client sends no more)
      41              :             http10,             // an HTTP/1.0 request without keep-alive
      42              :             draining,           // the server shuts down
      43              :             continue_skipped,   // answered before the 100 Continue its client waits for
      44              :             body_unread,        // a request body left unread that could not be dropped
      45              :             body_error,         // a request (or its body) that could not be read: where it ends is unknown
      46              :             handler_failed,     // the handler failed once its response had started (or before routing)
      47              :             route_timeout,      // the route timed out with the request body still unread
      48              :             request_timeout,    // the request was not received within its deadline (answered 408)
      49              :             framing_by_close,   // a response body that ends when the connection closes
      50              :             aborted             // a streamed response aborted: its body is incomplete
      51              :         };
      52              : 
      53              :         // What happens to the connection once the response of a stream is written
      54              :         enum class after_response : uint8_t { keep_alive, close, take_over };
      55              : 
      56              :         // Interim response asking the client to send the body (RFC 9110 section 15.2.1)
      57              :         class continue_frame : public http_frame {
      58              :         public:
      59          156 :             continue_frame() { set_last_frame(false); }
      60              : 
      61          165 :             frame_kind kind() const override { return frame_kind::interim; }
      62              : 
      63            0 :             size_t get_size() override { return 0; }
      64              : 
      65              :             void to_buffer(std::vector<boost::asio::const_buffer>& buffer) const override;
      66              : 
      67              :             void log(const char* scope, const body_logging&) const override;
      68              :         };
      69              : 
      70              :     }
      71              : 
      72              :     class server_connection;
      73              : 
      74              :     // The connection a request came from (see request::connection()): empty for a request
      75              :     // without one, or once it is gone
      76              :     struct connection_info {
      77              :         std::string peer_ip;
      78              :         std::string peer_port;
      79              :         std::string local_port;
      80              :         bool secure = false;    // TLS
      81              :     };
      82              : 
      83              :     namespace detail {
      84              : 
      85              :         // Where a request reads its body from, and what it knows of its connection: the input
      86              :         // twin of response_sink, one per request, owned by it. A server connection reads its
      87              :         // client (connection_source, in connection_source.cpp); an in-memory dispatch reads
      88              :         // memory (memory_source)
      89              :         class request_source {
      90              :         public:
      91        27619 :             virtual ~request_source() = default;
      92              : 
      93              :             // Up to `size` bytes of what follows the request head (the body reader frames them):
      94              :             // none once nothing more comes, with the error why. The network source sends the
      95              :             // 100 Continue its client waits for first, and moves the connection deadline
      96              :             virtual awaitable<io_result> read_body_some(uint8_t* buffer, size_t size) = 0;
      97              : 
      98              :             virtual connection_info info() const = 0;
      99              : 
     100              :             // The TLS session of the connection: none without TLS or without a connection
     101            6 :             virtual std::optional<asio::tls_session> tls() const { return std::nullopt; }
     102              : 
     103              :             // Whether a request for `host` is misdirected on the connection (see
     104              :             // asio::socket::misdirected()): false without TLS or without a connection
     105           15 :             virtual bool misdirected(std::string_view /*host*/) const { return false; }
     106              : 
     107              :             // Keep the connection open at least until `time`, for a request with a timeout of
     108              :             // its own (see server_connection::hold_until()); nothing without a connection
     109           39 :             virtual void hold_until(std::chrono::steady_clock::time_point /*time*/) {}
     110              : 
     111              :             // The request deadline set by the route of the request (see route::request_timeout),
     112              :             // from the start of the request, zero for none (see
     113              :             // server_connection::set_request_deadline()); nothing without a connection
     114            0 :             virtual void set_request_deadline(std::chrono::milliseconds /*deadline*/) {}
     115              : 
     116              :             // The read deadline of the body (see request::set_read_deadline()), from the
     117              :             // thread reading it; nothing without a connection, as memory never waits
     118            3 :             virtual void set_read_deadline(std::chrono::steady_clock::time_point /*deadline*/) {}
     119              : 
     120              :             // The server connection, for the sink writing the response (none in memory)
     121            3 :             virtual std::weak_ptr<server_connection> connection() const { return {}; }
     122              : 
     123              :             // The io_context of the connection, for the sockets a reverse proxy opens to its
     124              :             // target (none in memory, or once the connection is gone)
     125            9 :             virtual boost::asio::io_context* io_context() const { return nullptr; }
     126              :         };
     127              : 
     128              :     }
     129              : 
     130              :     /**
     131              :      * A HTTP stream represents a communication channel inside a single HTTP connection. As a
     132              :      * single connection can be used for multiple HTTP request, each request will generate a new
     133              :      * HTTP stream. In HTTP 1.1, all requests in a connection must be answered in order, even if
     134              :      * the responses are generated in a different order. In HTTP 2.0, it is possible to answer
     135              :      * asynchronously to each single stream within a connection using a stream identifier.
     136              :      * TODO check HTTP 2.0 support.
     137              :      */
     138              :     class http_stream : public std::enable_shared_from_this<http_stream> {
     139              : 
     140              :     private:
     141              :         /**
     142              :          * Unique identifier within a http connection, generate for each request
     143              :          */
     144              :         stream_id stream_id_;
     145              : 
     146              :         /**
     147              :          * Queue for each HTTP frame composing a response. A response can be composed on several frames
     148              :          * i.e., while sending large files. A list, as a deque allocates its storage up front,
     149              :          * and most responses are a single frame
     150              :          */
     151              :         std::queue<std::shared_ptr<http_frame>, std::list<std::shared_ptr<http_frame>>> queue_;
     152              : 
     153              :         /**
     154              :          * Set when the response takes over the connection: receives the socket, and the
     155              :          * bytes read past the request, once the response is written and the read loop
     156              :          * stopped at this stream
     157              :          */
     158              :         takeover_handler takeover_;
     159              :         std::string takeover_buffer_;
     160              :         bool takes_over_ = false;
     161              :         bool takeover_response_written_ = false;
     162              :         bool takeover_reader_stopped_ = false;
     163              : 
     164              :         /**
     165              :          * Whether input of the request may still be unread on the connection: its body, until
     166              :          * read to the end, or the rest of a request that could not be parsed. A connection
     167              :          * closed after the response then closes gracefully (see server_connection::linger()).
     168              :          * Here, where it takes no room
     169              :          */
     170              :         bool input_pending_ = false;
     171              : 
     172              :         /**
     173              :          * Once the input is read: whether bytes of what follows the request (a pipelined
     174              :          * request) were already read past it. And the event set then, which the connection
     175              :          * waits for before it reads the socket itself (see server_connection::watch_peer()):
     176              :          * created by its first wait, set then if the input was read already
     177              :          */
     178              :         bool input_follows_ = false;
     179              :         std::optional<asio::async_event> input_read_;
     180              : 
     181              :         /**
     182              :          * Frames posted to the connection thread and not queued yet (frames are queued in
     183              :          * call order: none runs inline while one is posted)
     184              :          */
     185              :         std::atomic<unsigned> posted_frames_{0};
     186              : 
     187              :         /**
     188              :          * Whether the response has started: a frame was queued, or a takeover requested
     189              :          */
     190              :         bool responded_ = false;
     191              : 
     192              :         /**
     193              :          * Whether the last frame of the response was written (or failed to be)
     194              :          */
     195              :         bool response_ended_ = false;
     196              : 
     197              :         /**
     198              :          * Why the connection closes after the response: the first reason given, if any
     199              :          * (see disposition())
     200              :          */
     201              :         std::optional<detail::close_reason> close_reason_;
     202              : 
     203              :         /**
     204              :          * Told about the response (head, bytes written, end) while a response hook is set
     205              :          * (see http_application::on_response)
     206              :          */
     207              :         std::shared_ptr<response_observer> observer_;
     208              : 
     209              :         /**
     210              :          * Cancellation scope of the request (see cancel()). Everything but the reason belongs
     211              :          * to the executor of the stream: the one of its connection (or of its dispatch)
     212              :          */
     213              :         boost::asio::any_io_executor executor_;
     214              :         // none until cancelled; read from any thread (a handler may change executor)
     215              :         std::atomic<cancel_reason> cancelled_;
     216              :         // Set once cancelled, for request::client_gone(): created by its first wait, set then if
     217              :         // the scope was cancelled already
     218              :         std::optional<asio::async_event> cancel_event_;
     219              :         // Bound to the coroutine running the handler (see handler_slot()), unless that
     220              :         // coroutine is bound to a signal of its own, its owner's (see bind_handler())
     221              :         boost::asio::cancellation_signal handler_signal_;
     222              :         boost::asio::cancellation_signal* bound_signal_ = nullptr;
     223              :         // A coroutine about to run the handler is bound to the scope (see bind_handler())
     224              :         bool handler_bound_ = false;
     225              :         // The slot request::cancellation_slot() gives, for the awaits the handler binds to it
     226              :         boost::asio::cancellation_signal observer_signal_;
     227              :         // The reasons interrupting the handler, as bits 1 << reason (see route::cancel_on)
     228              :         uint8_t cancel_on_ = 0;
     229              :         // The handler is running: neither signal is emitted before or after it
     230              :         bool handler_running_ = false;
     231              :         // The handler signal was emitted
     232              :         bool handler_interrupted_ = false;
     233              :         // Emit it, on the executor of the stream
     234              :         void interrupt_handler();
     235              : 
     236              :     public:
     237              :         /**
     238              :          * 100 Continue of a request whose client waits for it before sending the body
     239              :          * (Expect: 100-continue): none expected, pending until the body is first read, sent,
     240              :          * or skipped because the response started first (the body may never come)
     241              :          */
     242              :         enum class continue_state : uint8_t { none, pending, sent, skipped };
     243              : 
     244              :     private:
     245              :         continue_state continue_ = continue_state::none;
     246              : 
     247              :     public:
     248              :         /**
     249              :          * Stream of `request` on `executor` (that of its connection), whose own Connection
     250              :          * header and HTTP version seed what happens to the connection after its response
     251              :          */
     252              :         http_stream(stream_id stream_id, const http_request& request, boost::asio::any_io_executor executor);
     253              : 
     254              :         /**
     255              :          * Stream whose response closes the connection, for `reason` (e.g. a request that could
     256              :          * not be parsed)
     257              :          */
     258              :         http_stream(stream_id stream_id, detail::close_reason reason, boost::asio::any_io_executor executor);
     259              : 
     260        28066 :         virtual ~http_stream() {}
     261              : 
     262              :     public:
     263              : 
     264              :         size_t get_queue_size() const;
     265              : 
     266              :         bool empty_queue() const;
     267              : 
     268              :         std::shared_ptr<http_frame> current_frame() const;
     269              : 
     270              :         void pop_frame();
     271              : 
     272              :         void add_frame(std::shared_ptr<http_frame> frame);
     273              : 
     274              :         /**
     275              :          * Queue an interim response (100 Continue): the response has not started yet
     276              :          */
     277              :         void add_interim_frame(std::shared_ptr<http_frame> frame);
     278              : 
     279        44168 :         continue_state get_continue() const {
     280        44168 :             return continue_;
     281              :         }
     282              : 
     283          471 :         void set_continue(continue_state state) {
     284          471 :             continue_ = state;
     285          471 :         }
     286              : 
     287        55183 :         bool input_pending() const {
     288        55183 :             return input_pending_;
     289              :         }
     290              : 
     291              :         /**
     292              :          * Whether input of the request may still be unread on the connection. Once it is not
     293              :          * (read to its end, or none past the head), `follows` tells whether bytes of what
     294              :          * follows the request were read past it, and the event wait_input_read() waits for is
     295              :          * set. Where the body is read: on the executor of the stream
     296              :          */
     297        25960 :         void set_input_pending(bool pending, bool follows = false) {
     298        25960 :             input_pending_ = pending;
     299        25960 :             input_follows_ = follows;
     300        25960 :             if (!pending && input_read_) input_read_->set();
     301        25960 :         }
     302              : 
     303         1714 :         bool input_follows() const {
     304         1714 :             return input_follows_;
     305              :         }
     306              : 
     307              :         /**
     308              :          * The request read its input to its end (see set_input_pending()), from any thread (a
     309              :          * handler may read its body from another executor): on the executor of the stream,
     310              :          * right away if called there
     311              :          */
     312              :         void end_input(bool follows);
     313              : 
     314              :         /**
     315              :          * Wait until input of the request is no longer pending: false if the wait is cancelled
     316              :          */
     317         1328 :         awaitable<bool> wait_input_read() {
     318         1328 :             if (!input_read_) {
     319         1328 :                 input_read_.emplace(executor_);
     320         1328 :                 if (!input_pending_) input_read_->set();
     321              :             }
     322         1328 :             return input_read_->wait();
     323              :         }
     324              : 
     325              :         size_t get_queued_frames() const;
     326              : 
     327        23417 :         bool responded() const {
     328        23417 :             return responded_;
     329              :         }
     330              : 
     331        23111 :         void set_response_ended() {
     332        23111 :             response_ended_ = true;
     333        23111 :         }
     334              : 
     335         5789 :         bool response_ended() const {
     336         5789 :             return response_ended_;
     337              :         }
     338              : 
     339              :         /**
     340              :          * Take over the connection once the response of this stream is written: the
     341              :          * disposition is take_over from now on
     342              :          */
     343         4321 :         void set_takeover(takeover_handler handler) {
     344         4321 :             takeover_ = std::move(handler);
     345         4321 :             takes_over_ = true;
     346         4321 :             responded_ = true;
     347         4321 :         }
     348              : 
     349        50510 :         bool takes_over() const {
     350        50510 :             return takes_over_;
     351              :         }
     352              : 
     353              :         /**
     354              :          * What the takeover waits for, in either order: the response written, and the read
     355              :          * loop stopped (with the bytes it read past the request)
     356              :          */
     357         4312 :         void takeover_response_written() {
     358         4312 :             takeover_response_written_ = true;
     359         4312 :         }
     360              : 
     361         4315 :         void takeover_reader_stopped(std::string buffered) {
     362         4315 :             takeover_buffer_ = std::move(buffered);
     363         4315 :             takeover_reader_stopped_ = true;
     364         4315 :         }
     365              : 
     366              :         /**
     367              :          * Handler of the takeover once both are done, and the bytes read past the request;
     368              :          * the handler is empty before, and after it was released once
     369              :          */
     370         8627 :         std::pair<takeover_handler, std::string> release_takeover() {
     371         8627 :             if (!takeover_response_written_ || !takeover_reader_stopped_) return {};
     372         4312 :             return {std::exchange(takeover_, nullptr), std::move(takeover_buffer_)};
     373              :         }
     374              : 
     375           47 :         void frame_posted() {
     376           47 :             ++posted_frames_;
     377           47 :         }
     378              : 
     379           47 :         void posted_frame_queued() {
     380           47 :             --posted_frames_;
     381           47 :         }
     382              : 
     383        34776 :         bool has_posted_frames() const {
     384        34776 :             return posted_frames_ > 0;
     385              :         }
     386              : 
     387              :         stream_id id() const;
     388              : 
     389              :         /**
     390              :          * What happens to the connection once the response is written: taken over if a
     391              :          * takeover was set, else closed if a reason was given, else kept alive. It never goes
     392              :          * back to keep_alive, and a takeover stays one
     393              :          */
     394        37601 :         detail::after_response disposition() const {
     395        37601 :             if (takes_over_) return detail::after_response::take_over;
     396        37574 :             return close_reason_ ? detail::after_response::close : detail::after_response::keep_alive;
     397              :         }
     398              : 
     399              :         /**
     400              :          * Close the connection after the response, for `reason`; the first reason given stays.
     401              :          * A takeover still takes over: the reason only makes its head say Connection: close
     402              :          */
     403         8094 :         void must_close(detail::close_reason reason) {
     404         8094 :             if (!close_reason_) close_reason_ = reason;
     405         8094 :         }
     406              : 
     407              :         /**
     408              :          * Why the connection closes after the response, if it does (also under a takeover)
     409              :          */
     410        19844 :         std::optional<detail::close_reason> reason() const {
     411        19844 :             return close_reason_;
     412              :         }
     413              : 
     414              :         /**
     415              :          * Observer of the response, set before the response is answered (none without a
     416              :          * response hook)
     417              :          */
     418          411 :         void set_observer(std::shared_ptr<response_observer> observer) {
     419          411 :             observer_ = std::move(observer);
     420          411 :         }
     421              : 
     422        85757 :         response_observer* observer() const {
     423        85757 :             return observer_.get();
     424              :         }
     425              : 
     426              :         // --- Cancellation scope (see route::cancel_on, request::cancelled()) ---
     427              : 
     428              :         /**
     429              :          * Cancel the scope for `reason`, from any thread, once: the first reason stays. On the
     430              :          * executor of the stream, it then sets the event client_gone() waits for and, while the
     431              :          * handler runs, emits terminal on the observer signal, and on the handler signal if the
     432              :          * route declared the reason or it is shutdown. Later calls do nothing, except a shutdown
     433              :          * while the handler runs not interrupted yet: it emits both signals, and neither
     434              :          * cancelled() nor wait_cancelled() see it. For a stream owned by a shared_ptr (it stays
     435              :          * alive until then)
     436              :          */
     437              :         void cancel(cancel_reason reason);
     438              : 
     439              :         /**
     440              :          * Why the scope was cancelled: none until then
     441              :          */
     442         1095 :         cancel_reason cancelled() const {
     443         1095 :             return cancelled_.load(std::memory_order_acquire);
     444              :         }
     445              : 
     446              :         /**
     447              :          * Wait, on the executor of the stream, until the scope is cancelled: its reason, or
     448              :          * none if the wait itself is cancelled
     449              :          */
     450              :         awaitable<cancel_reason> wait_cancelled();
     451              : 
     452              :         /**
     453              :          * The reasons interrupting the handler, as bits 1 << reason (see route::get_cancel_on);
     454              :          * set before the handler runs
     455              :          */
     456        25959 :         void set_cancel_on(uint8_t reasons) {
     457        25959 :             cancel_on_ = reasons;
     458        25959 :         }
     459              : 
     460              :         /**
     461              :          * Slot of the handler signal, bound once to the coroutine running the handler (see
     462              :          * http_application::serve), and that of the observer signal, for request::cancellation_slot()
     463              :          */
     464         4287 :         boost::asio::cancellation_slot handler_slot() {
     465         4287 :             return handler_signal_.slot();
     466              :         }
     467              : 
     468              :         /**
     469              :          * The coroutine about to run the handler is bound to the scope: through handler_slot(),
     470              :          * or to `signal`, which its owner emits only for this stream while the handler runs
     471              :          * (that of the read loop of the connection, which runs every handler of its requests).
     472              :          * The handler then runs on that coroutine; until a coroutine is bound,
     473              :          * http_application::serve runs it on one of its own, bound to handler_slot()
     474              :          */
     475        27736 :         void bind_handler(boost::asio::cancellation_signal* signal = nullptr) {
     476        27736 :             handler_bound_ = true;
     477        27736 :             bound_signal_ = signal;
     478        27736 :         }
     479              : 
     480        31102 :         bool handler_bound() const {
     481        31102 :             return handler_bound_;
     482              :         }
     483              : 
     484          117 :         boost::asio::cancellation_slot observer_slot() {
     485          117 :             return observer_signal_.slot();
     486              :         }
     487              : 
     488              :         /**
     489              :          * The handler starts and ends (on the executor of the stream): the signals are emitted
     490              :          * only in between, so a cancellation after its end is a no-op. Their slots are left as
     491              :          * they are: what they hold (the cancellation state of the coroutine that ran the
     492              :          * handler) goes with the stream
     493              :          */
     494        31102 :         void handler_started() {
     495        31102 :             handler_running_ = true;
     496        31102 :         }
     497              : 
     498        26439 :         void handler_ended() {
     499        26439 :             handler_running_ = false;
     500        26439 :         }
     501              : 
     502          772 :         bool handler_running() const {
     503          772 :             return handler_running_;
     504              :         }
     505              : 
     506              :         /**
     507              :          * Whether the handler signal was emitted: the handler was interrupted
     508              :          */
     509        26448 :         bool handler_interrupted() const {
     510        26448 :             return handler_interrupted_;
     511              :         }
     512              : 
     513              :         /**
     514              :          * The connection is done with the stream (its response ended, or the connection is
     515              :          * gone): what it kept for the response goes now, not with a request that may keep the
     516              :          * stream longer (the observer, or a takeover handler, may hold that request)
     517              :          */
     518        23893 :         void detach() {
     519        23893 :             observer_.reset();
     520        23893 :             takeover_ = nullptr;
     521        23893 :             queue_ = {};
     522        23893 :         }
     523              :     };
     524              : 
     525              : }
     526              : 
     527              : #endif
        

Generated by: LCOV version 2.0-1