aws_smithy_http_client/client/
connect.rs1use crate::client::connect;
6use crate::proxy;
7use aws_smithy_runtime_api::box_error::BoxError;
8use http_1x::{Extensions, HeaderMap, HeaderValue, Uri};
9use hyper::rt::{Read, ReadBufCursor, Write};
10use hyper_util::client::legacy::connect::{Connected, Connection};
11use hyper_util::client::proxy::matcher::Matcher;
12use pin_project_lite::pin_project;
13use std::fmt;
14use std::future::Future;
15use std::io;
16use std::io::IoSlice;
17use std::pin::Pin;
18use std::sync::Arc;
19use std::task::{Context, Poll};
20
21pub(crate) trait AsyncConn:
22 Read + Write + Connection + Send + Sync + Unpin + 'static
23{
24}
25
26impl<T: Read + Write + Connection + Send + Sync + Unpin + 'static> AsyncConn for T {}
27
28pub(crate) type BoxConn = Box<dyn AsyncConn>;
29
30pub(crate) type Connecting = Pin<Box<dyn Future<Output = Result<Conn, BoxError>> + Send>>;
32
33#[derive(Clone)]
35pub(crate) struct ProxyAuthorization(HeaderValue);
36
37impl fmt::Debug for ProxyAuthorization {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.write_str("ProxyAuthorization(** redacted **)")
40 }
41}
42
43impl ProxyAuthorization {
44 pub(crate) fn new(value: HeaderValue) -> Self {
45 Self(value)
46 }
47
48 pub(crate) fn apply(&self, headers: &mut HeaderMap) {
50 headers
51 .entry(http_1x::header::PROXY_AUTHORIZATION)
52 .or_insert_with(|| self.0.clone());
53 }
54}
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61#[non_exhaustive]
62pub enum ConnectPath {
63 Direct,
65 ForwardProxy,
67 ProxyTunnel,
69}
70
71impl ConnectPath {
72 pub fn is_proxied(&self) -> bool {
74 !matches!(self, Self::Direct)
75 }
76}
77
78#[derive(Clone, Debug)]
80pub(crate) enum ConnectPathInner {
81 Direct,
83 ForwardProxy {
85 authorization: Option<ProxyAuthorization>,
86 },
87 ProxyTunnel,
89}
90
91impl ConnectPathInner {
92 pub(crate) fn forward_proxy(authorization: Option<HeaderValue>) -> Self {
93 Self::ForwardProxy {
94 authorization: authorization.map(ProxyAuthorization::new),
95 }
96 }
97
98 pub(crate) fn from_connected(connected: &Connected, extras: &Extensions) -> Self {
100 extras
101 .get::<Self>()
102 .cloned()
103 .or_else(|| extras.get::<ConnectPath>().copied().map(Self::from_public))
104 .unwrap_or_else(|| {
105 if connected.is_proxied() {
106 Self::forward_proxy(None)
107 } else {
108 Self::Direct
109 }
110 })
111 }
112
113 pub(crate) fn public(&self) -> ConnectPath {
115 match self {
116 Self::Direct => ConnectPath::Direct,
117 Self::ForwardProxy { .. } => ConnectPath::ForwardProxy,
118 Self::ProxyTunnel => ConnectPath::ProxyTunnel,
119 }
120 }
121
122 fn from_public(path: ConnectPath) -> Self {
124 match path {
125 ConnectPath::Direct => Self::Direct,
126 ConnectPath::ForwardProxy => Self::forward_proxy(None),
127 ConnectPath::ProxyTunnel => Self::ProxyTunnel,
128 }
129 }
130
131 pub(crate) fn uses_absolute_form(&self) -> bool {
133 matches!(self, Self::ForwardProxy { .. })
134 }
135
136 pub(crate) fn apply_proxy_authorization(&self, headers: &mut HeaderMap) {
138 if let Self::ForwardProxy {
139 authorization: Some(authorization),
140 } = self
141 {
142 authorization.apply(headers);
143 }
144 }
145}
146
147pin_project! {
148 pub(crate) struct Conn {
149 #[pin]
150 pub(super)inner: BoxConn,
151 pub(super) connect_path: ConnectPathInner,
152 }
153}
154
155impl Connection for Conn {
156 fn connected(&self) -> Connected {
157 self.inner
158 .connected()
159 .proxy(self.connect_path.uses_absolute_form())
160 .extra(self.connect_path.clone())
161 }
162}
163
164impl Read for Conn {
165 fn poll_read(
166 self: Pin<&mut Self>,
167 cx: &mut Context<'_>,
168 buf: ReadBufCursor<'_>,
169 ) -> Poll<io::Result<()>> {
170 let this = self.project();
171 Read::poll_read(this.inner, cx, buf)
172 }
173}
174
175impl Write for Conn {
176 fn poll_write(
177 self: Pin<&mut Self>,
178 cx: &mut Context<'_>,
179 buf: &[u8],
180 ) -> Poll<Result<usize, io::Error>> {
181 let this = self.project();
182 Write::poll_write(this.inner, cx, buf)
183 }
184
185 fn poll_write_vectored(
186 self: Pin<&mut Self>,
187 cx: &mut Context<'_>,
188 bufs: &[IoSlice<'_>],
189 ) -> Poll<Result<usize, io::Error>> {
190 let this = self.project();
191 Write::poll_write_vectored(this.inner, cx, bufs)
192 }
193
194 fn is_write_vectored(&self) -> bool {
195 self.inner.is_write_vectored()
196 }
197
198 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
199 let this = self.project();
200 Write::poll_flush(this.inner, cx)
201 }
202
203 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
204 let this = self.project();
205 Write::poll_shutdown(this.inner, cx)
206 }
207}
208
209#[derive(Debug, Clone)]
214pub(crate) struct HttpProxyConnector<C> {
215 inner: C,
216 proxy_matcher: Option<Arc<Matcher>>,
217}
218
219impl<C> HttpProxyConnector<C> {
220 pub(crate) fn new(inner: C, proxy_config: proxy::ProxyConfig) -> Self {
221 let proxy_matcher = if proxy_config.is_disabled() {
222 None
223 } else {
224 Some(Arc::new(proxy_config.into_hyper_util_matcher()))
225 };
226 Self {
227 inner,
228 proxy_matcher,
229 }
230 }
231}
232
233impl<C> tower::Service<Uri> for HttpProxyConnector<C>
234where
235 C: tower::Service<Uri> + Clone + Send + 'static,
236 C::Response: hyper::rt::Read
237 + hyper::rt::Write
238 + hyper_util::client::legacy::connect::Connection
239 + Send
240 + Sync
241 + Unpin
242 + 'static,
243 C::Future: Send + 'static,
244 C::Error: Into<BoxError>,
245{
246 type Response = connect::Conn;
247 type Error = BoxError;
248 type Future = connect::Connecting;
249
250 fn poll_ready(
251 &mut self,
252 cx: &mut std::task::Context<'_>,
253 ) -> std::task::Poll<Result<(), Self::Error>> {
254 self.inner.poll_ready(cx).map_err(Into::into)
255 }
256
257 fn call(&mut self, dst: Uri) -> Self::Future {
258 let proxy_intercept = self
259 .proxy_matcher
260 .as_ref()
261 .and_then(|matcher| matcher.intercept(&dst));
262
263 if let Some(intercept) = proxy_intercept {
264 let proxy_uri = intercept.uri().clone();
266 let connect_path = ConnectPathInner::forward_proxy(intercept.basic_auth().cloned());
267 let fut = self.inner.call(proxy_uri);
268 Box::pin(async move {
269 let conn = fut.await.map_err(Into::into)?;
270 Ok(connect::Conn {
271 inner: Box::new(conn),
272 connect_path,
273 })
274 })
275 } else {
276 let fut = self.inner.call(dst);
278 Box::pin(async move {
279 let conn = fut.await.map_err(Into::into)?;
280 Ok(connect::Conn {
281 inner: Box::new(conn),
282 connect_path: ConnectPathInner::Direct,
283 })
284 })
285 }
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn custom_connector_path_extra_preserves_public_classification() {
295 for path in [
296 ConnectPath::Direct,
297 ConnectPath::ForwardProxy,
298 ConnectPath::ProxyTunnel,
299 ] {
300 let connected = Connected::new().extra(path);
301 let mut extras = Extensions::new();
302 connected.get_extras(&mut extras);
303
304 assert_eq!(
305 path,
306 ConnectPathInner::from_connected(&connected, &extras).public()
307 );
308 }
309 }
310
311 #[test]
312 fn generic_proxy_metadata_maps_to_forward_proxy() {
313 let connected = Connected::new().proxy(true);
314 let mut extras = Extensions::new();
315 connected.get_extras(&mut extras);
316
317 assert_eq!(
318 ConnectPath::ForwardProxy,
319 ConnectPathInner::from_connected(&connected, &extras).public()
320 );
321 }
322}