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

            Line data    Source code
       1              : // The request sent by the reverse proxy to the target: its head, and its body streamed
       2              : #include "proxy_exchange.hpp"
       3              : #include "../body_reader.hpp"
       4              : #include <charconv>
       5              : 
       6              : namespace thinger::http::proxy_detail {
       7              : 
       8         7872 : awaitable<io_result> proxy_exchange::read_target(uint8_t* buffer, size_t size) {
       9              :     auto result = co_await target_socket_->read_some(buffer, size);
      10              :     if (!std::get<0>(result)) touch();
      11              :     co_return result;
      12        15744 : }
      13              : 
      14         4849 : awaitable<bool> proxy_exchange::write_target(const std::vector<boost::asio::const_buffer>& buffers) {
      15              :     auto [ec, bytes] = co_await target_socket_->write(buffers);
      16              :     if (ec) co_return false;
      17              :     touch();
      18              :     co_return true;
      19         9698 : }
      20              : 
      21          606 : void proxy_exchange::request_done() {
      22          606 :     request_sent_ = true;
      23          606 : }
      24              : 
      25           18 : void proxy_exchange::close_client_after_response() {
      26           18 :     if (auto stream = req_.get_http_stream()) stream->must_close(detail::close_reason::body_error);
      27           18 : }
      28              : 
      29           12 : void proxy_exchange::reject_large_body() {
      30           12 :     close_client_after_response();
      31           36 :     fail(http_response::status::payload_too_large, "Payload Too Large", "request body too large");
      32           12 : }
      33              : 
      34          633 : awaitable<bool> proxy_exchange::send_head() {
      35              :     std::vector<boost::asio::const_buffer> buffers{boost::asio::buffer(request_head_)};
      36              :     if (body_buffered_) buffers.push_back(boost::asio::buffer(incoming_->get_body()));
      37              :     bool written = co_await write_target(buffers);
      38              :     if (body_buffered_) result_.request_bytes = incoming_->get_body().size();
      39              :     co_return written;
      40         1266 : }
      41              : 
      42         4180 : void proxy_exchange::frame_part(std::vector<boost::asio::const_buffer>& buffers, std::string& size_line,
      43              :                                 const uint8_t* data, size_t size) const {
      44         4180 :     static const std::string crlf = "\r\n";
      45         4180 :     buffers.clear();
      46         4180 :     if (body_mode_ == body_mode::chunked) {
      47              :         char hex[20];
      48         1846 :         auto [end, ec] = std::to_chars(hex, hex + sizeof(hex), size, 16);
      49         1846 :         size_line.assign(hex, end);
      50         1846 :         size_line += crlf;
      51         1846 :         buffers.push_back(boost::asio::buffer(size_line));
      52              :     }
      53         4180 :     buffers.push_back(boost::asio::buffer(data, size));
      54         4180 :     if (body_mode_ == body_mode::chunked) buffers.push_back(boost::asio::buffer(crlf));
      55         4180 : }
      56              : 
      57          156 : awaitable<void> proxy_exchange::stream_body() {
      58              :     // The body goes at the pace of the target: each part is read from the client once the
      59              :     // target took the previous one
      60              :     std::vector<uint8_t> buffer(buffer_size);
      61              :     std::vector<boost::asio::const_buffer> buffers;
      62              :     std::string size_line;
      63              :     bool target_open = true;
      64              :     while (target_open) {
      65              :         size_t bytes = co_await req_.read_some(buffer.data(), buffer.size());
      66              :         if (bytes == 0) break;
      67              :         touch();
      68              :         result_.request_bytes += bytes;
      69              :         if (options_.max_body_size && result_.request_bytes > options_.max_body_size) {
      70              :             reject_large_body();
      71              :             stopped_ = stop_reason::request_failed;
      72              :             close_target();
      73              :             co_return;
      74              :         }
      75              :         frame_part(buffers, size_line, buffer.data(), bytes);
      76              :         target_open = co_await write_target(buffers);
      77              :     }
      78              : 
      79              :     // Cancelled once the response head arrived: the rest of the body is not relayed
      80              :     auto state = co_await boost::asio::this_coro::cancellation_state;
      81              :     if (state.cancelled() != boost::asio::cancellation_type::none) co_return;
      82              : 
      83              :     // A body the client did not send whole (or malformed, too large): the server answers
      84              :     // it and closes the connection; the target never gets its end
      85              :     if (req_.get_body_error() != body_error::none) {
      86              :         result_.error = "invalid or incomplete request body";
      87              :         LOG_WARNING("reverse proxy {}: {}", describe(), result_.error);
      88              :         stopped_ = stop_reason::request_failed;
      89              :         close_target();
      90              :         co_return;
      91              :     }
      92              : 
      93              :     if (target_open && body_mode_ == body_mode::chunked) {
      94              :         static const std::string last_chunk = "0\r\n\r\n";
      95              :         target_open = co_await write_target({boost::asio::buffer(last_chunk)});
      96              :     }
      97              :     // If the target stopped reading it (it may have answered early), what is left of
      98              :     // the body is not read: the server discards it, or closes the connection
      99              :     if (target_open && !req_.has_pending_body()) request_done();
     100              : 
     101              :     // The response ends the exchange, not the body: its head ends this wait (see exchange())
     102              :     co_await head_read_->wait();
     103          312 : }
     104              : 
     105              : }
        

Generated by: LCOV version 2.0-1