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 : }
|