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

            Line data    Source code
       1              : #ifndef THINGER_HTTP_SERVER_REVERSE_PROXY_EXCHANGE_HPP
       2              : #define THINGER_HTTP_SERVER_REVERSE_PROXY_EXCHANGE_HPP
       3              : 
       4              : // Internal to the reverse proxy (see reverse_proxy.hpp): shared by its source files only
       5              : 
       6              : #include "../reverse_proxy.hpp"
       7              : #include "../request.hpp"
       8              : #include "../response.hpp"
       9              : #include "../../client/response_factory.hpp"
      10              : #include "../../../asio/async_event.hpp"
      11              : #include "../../../asio/sockets/tcp_socket.hpp"
      12              : #include "../../../asio/watchdog.hpp"
      13              : #include <boost/asio/steady_timer.hpp>
      14              : #include <chrono>
      15              : #include <memory>
      16              : #include <optional>
      17              : #include <string>
      18              : #include <string_view>
      19              : #include <vector>
      20              : 
      21              : namespace thinger::http::proxy_detail {
      22              : 
      23              :     using clock = std::chrono::steady_clock;
      24              : 
      25              :     // Bytes read or written at once on each side
      26              :     inline constexpr size_t buffer_size = 16 * 1024;
      27              : 
      28              :     // --- Headers (proxy_request.cpp) ---
      29              : 
      30              :     // Path and query sent in the request line: an absolute path, without whitespace,
      31              :     // control characters or a fragment
      32              :     bool is_request_target(std::string_view target);
      33              : 
      34              :     // Headers that can be sent as they are: valid names and values
      35              :     bool has_valid_headers(const headers& message);
      36              : 
      37              :     // Names listed by the Connection headers: hop-by-hop headers of this message
      38              :     std::vector<std::string> connection_options(const headers& message);
      39              : 
      40              :     bool is_listed(const std::vector<std::string>& options, std::string_view name);
      41              : 
      42              :     // Whether a header goes from one hop to the next: not hop-by-hop, nor listed in Connection
      43              :     bool is_end_to_end(std::string_view name, const std::vector<std::string>& connection);
      44              : 
      45              :     // Remove the headers of the framing and the connection, set by the proxy
      46              :     void remove_framing_headers(headers& message);
      47              : 
      48              :     // Copy the status and end-to-end headers of a response of the target, but its
      49              :     // Content-Length (the framing is set by the response to the client)
      50              :     void copy_end_to_end(const http_response& head, http_response& out);
      51              : 
      52              :     bool is_ip_address(const std::string& value);
      53              : 
      54              :     // Tunnel between a client and a target after a protocol upgrade, ended once nothing was
      55              :     // forwarded for idle_timeout (zero for never, see proxy_options::tunnel_idle_timeout)
      56              :     // (proxy_tunnel.cpp)
      57              :     awaitable<void> run_tunnel(std::shared_ptr<asio::socket> client, std::shared_ptr<asio::socket> target,
      58              :                                std::string to_client, std::string to_target,
      59              :                                std::chrono::milliseconds idle_timeout);
      60              : 
      61              :     // A request forwarded to a target, and its response. Its parts are defined by concern:
      62              :     // the request (proxy_request.cpp), its body (proxy_body.cpp), the response
      63              :     // (proxy_response.cpp), the tunnel of an upgrade (proxy_tunnel.cpp), and the connection
      64              :     // and timeouts (reverse_proxy.cpp).
      65              :     class proxy_exchange {
      66              :     public:
      67          675 :         proxy_exchange(reverse_proxy& proxy, request& req, response& res, const proxy_target& target,
      68              :                        const proxy_options& options)
      69          675 :             : proxy_(proxy), req_(req), res_(res), target_(target), options_(options),
      70          675 :               incoming_(req.get_http_request()) {}
      71              : 
      72              :         awaitable<proxy_result> run();
      73              : 
      74              :     private:
      75              :         enum class body_mode { none, length, chunked };
      76              : 
      77              :         // Build the request for the target; false if it cannot be forwarded (answered)
      78              :         bool prepare();
      79              :         bool check_method();
      80              :         bool check_upgrade(const std::vector<std::string>& connection);
      81              :         std::string target_path() const;
      82              :         std::string target_host() const;
      83              :         void copy_request_headers(http_request& outgoing, const std::vector<std::string>& connection) const;
      84              :         bool check_outgoing(const http_request& outgoing);
      85              :         bool frame_body(http_request& outgoing);
      86              :         void add_connection_headers(http_request& outgoing) const;
      87              : 
      88              :         // Forwarding headers: those received from a trusted proxy, and this hop's
      89              :         void add_forwarding_headers(http_request& outgoing) const;
      90              :         void add_x_forwarded(http_request& outgoing, const std::string& peer, bool keep_received) const;
      91              :         void add_forwarded(http_request& outgoing, const std::string& peer, bool keep_received) const;
      92              : 
      93              :         // Connect to the target within the connect timeout; false if it failed (answered)
      94              :         awaitable<bool> connect();
      95              :         awaitable<boost::system::error_code> connect_target();
      96              :         awaitable<boost::system::error_code> start_tls(std::shared_ptr<asio::tcp_socket> tcp);
      97              : 
      98              :         // Send the request, read the response head, then relay the response or tunnel
      99              :         awaitable<void> exchange();
     100              :         awaitable<bool> send_head();
     101              :         awaitable<bool> read_head_while_streaming();
     102              :         awaitable<void> stream_body();
     103              :         void frame_part(std::vector<boost::asio::const_buffer>& buffers, std::string& size_line,
     104              :                         const uint8_t* data, size_t size) const;
     105              :         awaitable<bool> read_head();
     106              :         void request_done();
     107              :         awaitable<void> relay_response();
     108              :         void upgrade();
     109              : 
     110              :         // Relay the response body, as framed by the target
     111              :         std::optional<size_t> content_length() const;
     112              :         std::optional<size_t> advertised_length() const;
     113              :         awaitable<bool> relay_body();
     114              :         awaitable<bool> relay_length(size_t length);
     115              :         awaitable<bool> relay_chunked();
     116              :         awaitable<bool> relay_until_close();
     117              : 
     118              :         // End the exchange once the idle or total timeout passes (see deadline_), or once the
     119              :         // scope of the request is cancelled (see request::client_gone())
     120              :         bool has_deadline() const;
     121              :         std::optional<clock::time_point> next_deadline() const;
     122              :         void start_deadline();
     123              :         awaitable<void> stop_at_deadline();
     124              :         awaitable<void> watch_client();
     125              :         void stop_for_timeout();
     126              :         void finish_stopped();
     127              : 
     128              :         // I/O with the target and the client, tracking activity for the idle timeout
     129              :         awaitable<io_result> read_target(uint8_t* buffer, size_t size);
     130              :         awaitable<bool> write_target(const std::vector<boost::asio::const_buffer>& buffers);
     131              :         awaitable<bool> send_to_client(std::string data);
     132              :         void touch();
     133              : 
     134              :         // Answer an error to the client, or abort the response if it already started
     135              :         void fail(http_response::status status, const std::string& message, const std::string& detail);
     136              :         void reject_large_body();
     137              :         // The client connection is closed after the response: the rest of its request is not read
     138              :         void close_client_after_response();
     139              : 
     140              :         void close_target();
     141              :         boost::asio::io_context* io_context();
     142              :         std::string describe() const;
     143              : 
     144              :         reverse_proxy& proxy_;
     145              :         request& req_;
     146              :         response& res_;
     147              :         const proxy_target& target_;
     148              :         const proxy_options& options_;
     149              :         std::shared_ptr<http_request> incoming_;
     150              :         boost::asio::any_io_executor executor_;
     151              : 
     152              :         // Why the exchange was stopped before it ended: its failures are a consequence
     153              :         enum class stop_reason { none, timeout, client_left, request_failed };
     154              : 
     155              :         // Request for the target
     156              :         std::string request_head_;
     157              :         method outgoing_method_ = method::UNKNOWN;
     158              :         body_mode body_mode_ = body_mode::none;
     159              :         bool body_buffered_ = false;
     160              :         bool upgrade_requested_ = false;
     161              :         bool request_sent_ = false;
     162              : 
     163              :         std::shared_ptr<asio::socket> target_socket_;
     164              :         bool tls_config_error_ = false;
     165              : 
     166              :         // Bytes read from the target and not consumed yet
     167              :         std::string pending_;
     168              : 
     169              :         // Response head of the target
     170              :         std::shared_ptr<http_response> head_;
     171              :         int status_code_ = 0;
     172              :         response_factory::body_framing body_framing_ = response_factory::body_framing::none;
     173              : 
     174              :         bool response_started_ = false;
     175              :         // The whole response is queued: the connection may close after it (not the client leaving)
     176              :         bool response_ended_ = false;
     177              :         stop_reason stopped_ = stop_reason::none;
     178              :         clock::time_point start_;
     179              :         clock::time_point last_activity_;
     180              : 
     181              :         // On the executor of the exchange, from run(). The events are declared before the
     182              :         // watchdog, which sets one: it goes first.
     183              :         //
     184              :         // Set once read_head_while_streaming() ends, however it ends: stream_body() waits for it
     185              :         // once the body is sent (the response ends the exchange, not the body)
     186              :         std::optional<asio::async_event> head_read_;
     187              :         // Set once the whole response is written (or failed to be): watch_client() waits for it
     188              :         // when the client goes after the response was queued whole
     189              :         std::optional<asio::async_event> response_written_;
     190              :         // Set by deadline_ once the idle or total timeout passes
     191              :         std::optional<asio::async_event> deadline_passed_;
     192              :         // The earliest of the idle and total deadlines, moved by touch(); none without timeouts
     193              :         std::optional<asio::watchdog> deadline_;
     194              : 
     195              :         proxy_result result_;
     196              :     };
     197              : 
     198              : }
     199              : 
     200              : #endif // THINGER_HTTP_SERVER_REVERSE_PROXY_EXCHANGE_HPP
        

Generated by: LCOV version 2.0-1