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
|