/proc/self/cwd/source/common/http/codec_client.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/common/http/codec_client.h" |
2 | | |
3 | | #include <cstdint> |
4 | | #include <memory> |
5 | | |
6 | | #include "envoy/http/codec.h" |
7 | | |
8 | | #include "source/common/common/enum_to_int.h" |
9 | | #include "source/common/config/utility.h" |
10 | | #include "source/common/http/exception.h" |
11 | | #include "source/common/http/http1/codec_impl.h" |
12 | | #include "source/common/http/http2/codec_impl.h" |
13 | | #include "source/common/http/status.h" |
14 | | #include "source/common/http/utility.h" |
15 | | |
16 | | #ifdef ENVOY_ENABLE_QUIC |
17 | | #include "source/common/quic/client_codec_impl.h" |
18 | | #endif |
19 | | |
20 | | namespace Envoy { |
21 | | namespace Http { |
22 | | |
23 | | CodecClient::CodecClient(CodecType type, Network::ClientConnectionPtr&& connection, |
24 | | Upstream::HostDescriptionConstSharedPtr host, |
25 | | Event::Dispatcher& dispatcher) |
26 | | : type_(type), host_(host), connection_(std::move(connection)), |
27 | 25.7k | idle_timeout_(host_->cluster().idleTimeout()) { |
28 | 25.7k | if (type_ != CodecType::HTTP3) { |
29 | | // Make sure upstream connections process data and then the FIN, rather than processing |
30 | | // TCP disconnects immediately. (see https://github.com/envoyproxy/envoy/issues/1679 for |
31 | | // details) |
32 | 25.7k | connection_->detectEarlyCloseWhenReadDisabled(false); |
33 | 25.7k | } |
34 | 25.7k | connection_->addConnectionCallbacks(*this); |
35 | 25.7k | connection_->addReadFilter(Network::ReadFilterSharedPtr{new CodecReadFilter(*this)}); |
36 | | |
37 | 25.7k | if (idle_timeout_) { |
38 | 1.32k | idle_timer_ = dispatcher.createTimer([this]() -> void { onIdleTimeout(); }); |
39 | 1.32k | enableIdleTimer(); |
40 | 1.32k | } |
41 | | |
42 | | // We just universally set no delay on connections. Theoretically we might at some point want |
43 | | // to make this configurable. |
44 | 25.7k | connection_->noDelay(true); |
45 | 25.7k | } |
46 | | |
47 | 25.7k | void CodecClient::connect() { |
48 | 25.7k | ASSERT(!connect_called_); |
49 | 25.7k | connect_called_ = true; |
50 | 25.7k | ASSERT(codec_ != nullptr); |
51 | | // In general, codecs are handed new not-yet-connected connections, but in the |
52 | | // case of ALPN, the codec may be handed an already connected connection. |
53 | 25.7k | if (!connection_->connecting()) { |
54 | 20.6k | ASSERT(connection_->state() == Network::Connection::State::Open); |
55 | 20.6k | connected_ = true; |
56 | 20.6k | } else { |
57 | 5.07k | ENVOY_CONN_LOG(debug, "connecting", *connection_); |
58 | 5.07k | connection_->connect(); |
59 | 5.07k | } |
60 | 25.7k | } |
61 | | |
62 | 22.9k | void CodecClient::close(Network::ConnectionCloseType type) { connection_->close(type); } |
63 | | |
64 | 28.4k | void CodecClient::deleteRequest(ActiveRequest& request) { |
65 | 28.4k | connection_->dispatcher().deferredDelete(request.removeFromList(active_requests_)); |
66 | 28.4k | if (codec_client_callbacks_) { |
67 | 3.38k | codec_client_callbacks_->onStreamDestroy(); |
68 | 3.38k | } |
69 | 28.4k | if (numActiveRequests() == 0) { |
70 | 28.4k | enableIdleTimer(); |
71 | 28.4k | } |
72 | 28.4k | } |
73 | | |
74 | 28.4k | RequestEncoder& CodecClient::newStream(ResponseDecoder& response_decoder) { |
75 | 28.4k | ActiveRequestPtr request(new ActiveRequest(*this, response_decoder)); |
76 | 28.4k | request->setEncoder(codec_->newStream(*request)); |
77 | 28.4k | LinkedList::moveIntoList(std::move(request), active_requests_); |
78 | | |
79 | 28.4k | auto upstream_info = connection_->streamInfo().upstreamInfo(); |
80 | 28.4k | upstream_info->setUpstreamNumStreams(upstream_info->upstreamNumStreams() + 1); |
81 | | |
82 | 28.4k | disableIdleTimer(); |
83 | 28.4k | return *active_requests_.front(); |
84 | 28.4k | } |
85 | | |
86 | 31.5k | void CodecClient::onEvent(Network::ConnectionEvent event) { |
87 | 31.5k | if (event == Network::ConnectionEvent::Connected) { |
88 | 5.85k | ENVOY_CONN_LOG(debug, "connected", *connection_); |
89 | 5.85k | connected_ = true; |
90 | 5.85k | return; |
91 | 5.85k | } |
92 | | |
93 | 25.7k | if (event == Network::ConnectionEvent::RemoteClose) { |
94 | 958 | remote_closed_ = true; |
95 | 958 | } |
96 | | |
97 | | // HTTP/1 can signal end of response by disconnecting. We need to handle that case. |
98 | 25.7k | if (type_ == CodecType::HTTP1 && event == Network::ConnectionEvent::RemoteClose && |
99 | 25.7k | !active_requests_.empty()) { |
100 | 325 | Buffer::OwnedImpl empty; |
101 | 325 | onData(empty); |
102 | 325 | } |
103 | | |
104 | 25.7k | if (event == Network::ConnectionEvent::RemoteClose || |
105 | 25.7k | event == Network::ConnectionEvent::LocalClose) { |
106 | 25.7k | ENVOY_CONN_LOG(debug, "disconnect. resetting {} pending requests", *connection_, |
107 | 25.7k | active_requests_.size()); |
108 | 25.7k | disableIdleTimer(); |
109 | 25.7k | idle_timer_.reset(); |
110 | 25.7k | StreamResetReason reason = event == Network::ConnectionEvent::RemoteClose |
111 | 25.7k | ? StreamResetReason::RemoteConnectionFailure |
112 | 25.7k | : StreamResetReason::LocalConnectionFailure; |
113 | 25.7k | if (connected_) { |
114 | 25.7k | reason = StreamResetReason::ConnectionTermination; |
115 | 25.7k | if (protocol_error_) { |
116 | 89 | reason = StreamResetReason::ProtocolError; |
117 | 89 | connection_->streamInfo().setResponseFlag(StreamInfo::ResponseFlag::UpstreamProtocolError); |
118 | 89 | } |
119 | 25.7k | } |
120 | 31.8k | while (!active_requests_.empty()) { |
121 | | // Fake resetting all active streams so that reset() callbacks get invoked. |
122 | 6.09k | active_requests_.front()->getStream().resetStream(reason); |
123 | 6.09k | } |
124 | 25.7k | } |
125 | 25.7k | } |
126 | | |
127 | 8.14k | void CodecClient::responsePreDecodeComplete(ActiveRequest& request) { |
128 | 8.14k | ENVOY_CONN_LOG(debug, "response complete", *connection_); |
129 | 8.14k | if (codec_client_callbacks_) { |
130 | 2.26k | codec_client_callbacks_->onStreamPreDecodeComplete(); |
131 | 2.26k | } |
132 | 8.14k | request.decode_complete_ = true; |
133 | 8.14k | if (request.encode_complete_ || !request.wait_encode_complete_) { |
134 | 8.05k | completeRequest(request); |
135 | 8.05k | } else { |
136 | 96 | ENVOY_CONN_LOG(debug, "waiting for encode to complete", *connection_); |
137 | 96 | } |
138 | 8.14k | } |
139 | | |
140 | 28.3k | void CodecClient::requestEncodeComplete(ActiveRequest& request) { |
141 | 28.3k | ENVOY_CONN_LOG(debug, "encode complete", *connection_); |
142 | 28.3k | request.encode_complete_ = true; |
143 | 28.3k | if (request.decode_complete_) { |
144 | 0 | completeRequest(request); |
145 | 0 | } |
146 | 28.3k | } |
147 | | |
148 | 8.05k | void CodecClient::completeRequest(ActiveRequest& request) { |
149 | 8.05k | deleteRequest(request); |
150 | | |
151 | | // HTTP/2 can send us a reset after a complete response if the request was not complete. Users |
152 | | // of CodecClient will deal with the premature response case and we should not handle any |
153 | | // further reset notification. |
154 | 8.05k | request.removeEncoderCallbacks(); |
155 | 8.05k | } |
156 | | |
157 | 20.4k | void CodecClient::onReset(ActiveRequest& request, StreamResetReason reason) { |
158 | 20.4k | ENVOY_CONN_LOG(debug, "request reset", *connection_); |
159 | 20.4k | if (codec_client_callbacks_) { |
160 | 1.21k | codec_client_callbacks_->onStreamReset(reason); |
161 | 1.21k | } |
162 | | |
163 | 20.4k | deleteRequest(request); |
164 | 20.4k | } |
165 | | |
166 | 11.6k | void CodecClient::onData(Buffer::Instance& data) { |
167 | 11.6k | const Status status = codec_->dispatch(data); |
168 | | |
169 | 11.6k | if (!status.ok()) { |
170 | 691 | ENVOY_CONN_LOG(debug, "Error dispatching received data: {}", *connection_, status.message()); |
171 | | |
172 | | // Don't count 408 responses where we have no active requests as protocol errors |
173 | 691 | if (!isPrematureResponseError(status) || |
174 | 691 | (!active_requests_.empty() || |
175 | 691 | getPrematureResponseHttpCode(status) != Code::RequestTimeout)) { |
176 | 691 | host_->cluster().trafficStats()->upstream_cx_protocol_error_.inc(); |
177 | 691 | protocol_error_ = true; |
178 | 691 | } |
179 | 691 | close(); |
180 | 691 | } |
181 | | |
182 | | // All data should be consumed at this point if the connection remains open. |
183 | 11.6k | ASSERT(data.length() == 0 || connection_->state() != Network::Connection::State::Open, |
184 | 11.6k | absl::StrCat("extraneous bytes after response complete: ", data.length())); |
185 | 11.6k | } |
186 | | |
187 | 28.4k | Status CodecClient::ActiveRequest::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { |
188 | | #ifdef ENVOY_ENABLE_UHV |
189 | | if (header_validator_) { |
190 | | bool failure = false; |
191 | | std::string failure_details; |
192 | | auto transformation_result = header_validator_->transformRequestHeaders(headers); |
193 | | if (!transformation_result.status.ok()) { |
194 | | ENVOY_CONN_LOG(debug, "Request header transformation failed: {}\n{}", *parent_.connection_, |
195 | | transformation_result.status.details(), headers); |
196 | | failure = true; |
197 | | failure_details = std::string(transformation_result.status.details()); |
198 | | } else { |
199 | | // Validate header map after request encoder transformations |
200 | | const ::Envoy::Http::HeaderValidator::ValidationResult validation_result = |
201 | | header_validator_->validateRequestHeaders( |
202 | | transformation_result.new_headers ? *transformation_result.new_headers : headers); |
203 | | if (!validation_result.ok()) { |
204 | | ENVOY_CONN_LOG(debug, "Request header validation failed: {}\n{}", *parent_.connection_, |
205 | | validation_result.details(), |
206 | | transformation_result.new_headers ? *transformation_result.new_headers |
207 | | : headers); |
208 | | failure = true; |
209 | | failure_details = std::string(validation_result.details()); |
210 | | } |
211 | | } |
212 | | if (failure) { |
213 | | return absl::InvalidArgumentError( |
214 | | absl::StrCat("header validation failed: ", failure_details)); |
215 | | } |
216 | | return RequestEncoderWrapper::encodeHeaders( |
217 | | transformation_result.new_headers ? *transformation_result.new_headers : headers, |
218 | | end_stream); |
219 | | } |
220 | | #endif |
221 | 28.4k | return RequestEncoderWrapper::encodeHeaders(headers, end_stream); |
222 | 28.4k | } |
223 | | |
224 | 9.78k | void CodecClient::ActiveRequest::decodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream) { |
225 | | #ifdef ENVOY_ENABLE_UHV |
226 | | if (header_validator_) { |
227 | | const ::Envoy::Http::HeaderValidator::ValidationResult validation_result = |
228 | | header_validator_->validateResponseHeaders(*headers); |
229 | | bool failure = !validation_result.ok(); |
230 | | std::string failure_details(validation_result.details()); |
231 | | if (!failure) { |
232 | | const ::Envoy::Http::ClientHeaderValidator::TransformationResult transformation_result = |
233 | | header_validator_->transformResponseHeaders(*headers); |
234 | | failure = !transformation_result.ok(); |
235 | | failure_details = std::string(validation_result.details()); |
236 | | } |
237 | | if (failure) { |
238 | | ENVOY_CONN_LOG(debug, "Response header validation failed: {}\n{}", *parent_.connection_, |
239 | | failure_details, *headers); |
240 | | if ((parent_.codec_->protocol() == Protocol::Http2 && |
241 | | !parent_.host_->cluster() |
242 | | .http2Options() |
243 | | .override_stream_error_on_invalid_http_message() |
244 | | .value()) || |
245 | | (parent_.codec_->protocol() == Protocol::Http3 && |
246 | | !parent_.host_->cluster() |
247 | | .http3Options() |
248 | | .override_stream_error_on_invalid_http_message() |
249 | | .value())) { |
250 | | parent_.protocol_error_ = true; |
251 | | parent_.close(); |
252 | | } else { |
253 | | inner_encoder_->getStream().resetStream(StreamResetReason::ProtocolError); |
254 | | } |
255 | | return; |
256 | | } |
257 | | } |
258 | | #endif |
259 | 9.78k | ResponseDecoderWrapper::decodeHeaders(std::move(headers), end_stream); |
260 | 9.78k | } |
261 | | |
262 | | CodecClientProd::CodecClientProd(CodecType type, Network::ClientConnectionPtr&& connection, |
263 | | Upstream::HostDescriptionConstSharedPtr host, |
264 | | Event::Dispatcher& dispatcher, |
265 | | Random::RandomGenerator& random_generator, |
266 | | const Network::TransportSocketOptionsConstSharedPtr& options) |
267 | | : NoConnectCodecClientProd(type, std::move(connection), host, dispatcher, random_generator, |
268 | 5.07k | options) { |
269 | 5.07k | connect(); |
270 | 5.07k | } |
271 | | |
272 | | NoConnectCodecClientProd::NoConnectCodecClientProd( |
273 | | CodecType type, Network::ClientConnectionPtr&& connection, |
274 | | Upstream::HostDescriptionConstSharedPtr host, Event::Dispatcher& dispatcher, |
275 | | Random::RandomGenerator& random_generator, |
276 | | const Network::TransportSocketOptionsConstSharedPtr& options) |
277 | 5.07k | : CodecClient(type, std::move(connection), host, dispatcher) { |
278 | 5.07k | switch (type) { |
279 | 2.70k | case CodecType::HTTP1: { |
280 | | // If the transport socket indicates this is being proxied, inform the HTTP/1.1 codec. It will |
281 | | // send fully qualified URLs iff the underlying transport is plaintext. |
282 | 2.70k | bool proxied = false; |
283 | 2.70k | if (options && options->http11ProxyInfo().has_value()) { |
284 | 0 | proxied = true; |
285 | 0 | } |
286 | 2.70k | codec_ = std::make_unique<Http1::ClientConnectionImpl>( |
287 | 2.70k | *connection_, host->cluster().http1CodecStats(), *this, host->cluster().http1Settings(), |
288 | 2.70k | host->cluster().maxResponseHeadersCount(), proxied); |
289 | 2.70k | break; |
290 | 0 | } |
291 | 2.37k | case CodecType::HTTP2: |
292 | 2.37k | codec_ = std::make_unique<Http2::ClientConnectionImpl>( |
293 | 2.37k | *connection_, *this, host->cluster().http2CodecStats(), random_generator, |
294 | 2.37k | host->cluster().http2Options(), Http::DEFAULT_MAX_REQUEST_HEADERS_KB, |
295 | 2.37k | host->cluster().maxResponseHeadersCount(), Http2::ProdNghttp2SessionFactory::get()); |
296 | 2.37k | break; |
297 | 0 | case CodecType::HTTP3: { |
298 | 0 | #ifdef ENVOY_ENABLE_QUIC |
299 | 0 | auto& quic_session = dynamic_cast<Quic::EnvoyQuicClientSession&>(*connection_); |
300 | 0 | codec_ = std::make_unique<Quic::QuicHttpClientConnectionImpl>( |
301 | 0 | quic_session, *this, host->cluster().http3CodecStats(), host->cluster().http3Options(), |
302 | 0 | Http::DEFAULT_MAX_REQUEST_HEADERS_KB, host->cluster().maxResponseHeadersCount()); |
303 | | // Initialize the session after max request header size is changed in above http client |
304 | | // connection creation. |
305 | 0 | quic_session.Initialize(); |
306 | 0 | break; |
307 | | #else |
308 | | // Should be blocked by configuration checking at an earlier point. |
309 | | PANIC("unexpected"); |
310 | | #endif |
311 | 0 | } |
312 | 5.07k | } |
313 | 5.07k | } |
314 | | |
315 | | } // namespace Http |
316 | | } // namespace Envoy |