AWS SDK

AWS SDK

rev. 5268f3bc6a0eb20fb020dfdb4b4f92f6181f2eed

Files changed:

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/tests/common/h2.rs

@@ -65,65 +124,125 @@
   85     85   
        error: String,
   86     86   
    },
   87     87   
    H2Ready {
   88     88   
        connection_id: H2ConnectionId,
   89     89   
    },
   90     90   
    StreamAccepted {
   91     91   
        connection_id: H2ConnectionId,
   92     92   
        stream_id: u32,
   93     93   
        method: Method,
   94     94   
        path: String,
          95  +
        proxy_authorization: Option<String>,
   95     96   
    },
   96     97   
    ResponseCompleted {
   97     98   
        connection_id: H2ConnectionId,
   98     99   
        stream_id: u32,
   99    100   
    },
  100    101   
    ResetSent {
  101    102   
        connection_id: H2ConnectionId,
  102    103   
        stream_id: u32,
  103    104   
        reason: Reason,
  104    105   
    },
@@ -384,385 +444,446 @@
  404    405   
impl H2ServerBuilder {
  405    406   
    pub(crate) fn connections(mut self, connections: H2ConnectionPlan) -> Self {
  406    407   
        self.connections = Some(connections);
  407    408   
        self
  408    409   
    }
  409    410   
  410    411   
    pub(crate) async fn start(self) -> Result<H2TestServer, H2HarnessError> {
  411    412   
        let connections = self
  412    413   
            .connections
  413    414   
            .ok_or_else(|| H2HarnessError::new("an H2 connection plan is required"))?;
  414         -
        let tls_acceptor = tls::server_tls_acceptor(&[b"h2"])
         415  +
        let tls_acceptor = tls::SERVER_IDENTITY
         416  +
            .acceptor(&[b"h2"])
  415    417   
            .map_err(|err| H2HarnessError::new(format!("failed to configure TLS: {err}")))?;
  416    418   
        let listener = TcpListener::bind("127.0.0.1:0")
  417    419   
            .await
  418    420   
            .map_err(|err| H2HarnessError::new(format!("failed to bind H2 listener: {err}")))?;
  419    421   
        let listen_addr = listener.local_addr().map_err(|err| {
  420    422   
            H2HarnessError::new(format!("failed to read H2 listener address: {err}"))
  421    423   
        })?;
  422    424   
        let state = Arc::new(SharedState::new());
  423    425   
        let (shutdown, shutdown_rx) = watch::channel(false);
  424    426   
        let listener_state = state.clone();
@@ -739,741 +803,810 @@
  759    761   
                    }
  760    762   
                    None => control_open = false,
  761    763   
                }
  762    764   
            }
  763    765   
            accepted = connection.accept() => {
  764    766   
                match accepted {
  765    767   
                    Some(Ok((request, mut respond))) => {
  766    768   
                        let stream_id = respond.stream_id().as_u32();
  767    769   
                        let method = request.method().clone();
  768    770   
                        let path = request.uri().path().to_string();
         771  +
                        let proxy_authorization = request
         772  +
                            .headers()
         773  +
                            .get(http_1x::header::PROXY_AUTHORIZATION)
         774  +
                            .map(|value| value.to_str().unwrap_or_default().to_string());
  769    775   
                        state.record_event(H2Event::StreamAccepted {
  770    776   
                            connection_id,
  771    777   
                            stream_id,
  772    778   
                            method,
  773    779   
                            path: path.clone(),
         780  +
                            proxy_authorization,
  774    781   
                        });
  775    782   
                        let Some(stream_script) = script.script_for(&path) else {
  776    783   
                            respond.send_reset(Reason::PROTOCOL_ERROR);
  777    784   
                            return Err(H2HarnessError::new(format!(
  778    785   
                                "connection {connection_id:?} received unscripted H2 path {path:?}"
  779    786   
                            )));
  780    787   
                        };
  781    788   
                        let held_request = match &stream_script {
  782    789   
                            H2StreamScript::RespondBeforeReceivingRequestBody { receive, .. } => {
  783    790   
                                Some((request.into_body(), receive.clone()))

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/tests/common/proxy.rs

@@ -0,1 +0,480 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
//! HTTP proxy and tunneled-origin support for integration tests.
           7  +
           8  +
#[cfg(any(
           9  +
    feature = "rustls-aws-lc",
          10  +
    feature = "rustls-aws-lc-fips",
          11  +
    feature = "rustls-ring",
          12  +
    feature = "s2n-tls"
          13  +
))]
          14  +
use aws_smithy_runtime_api::client::dns::{DnsFuture, ResolveDns, ResolveDnsError};
          15  +
use base64::Engine;
          16  +
use http_1x::{Request, Response, StatusCode};
          17  +
use hyper::body::Incoming;
          18  +
use hyper::service::service_fn;
          19  +
use hyper_util::rt::TokioIo;
          20  +
use std::collections::HashMap;
          21  +
use std::convert::Infallible;
          22  +
#[cfg(any(
          23  +
    feature = "rustls-aws-lc",
          24  +
    feature = "rustls-aws-lc-fips",
          25  +
    feature = "rustls-ring",
          26  +
    feature = "s2n-tls"
          27  +
))]
          28  +
use std::net::IpAddr;
          29  +
use std::net::SocketAddr;
          30  +
use std::sync::{Arc, Mutex};
          31  +
use tokio::net::TcpListener;
          32  +
use tokio::sync::oneshot;
          33  +
          34  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          35  +
use http_body_util::Full;
          36  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          37  +
use hyper::body::Bytes;
          38  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          39  +
use tokio::io::copy_bidirectional;
          40  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          41  +
use tokio::net::TcpStream;
          42  +
          43  +
/// One request observed by a test server.
          44  +
#[derive(Clone, Debug)]
          45  +
pub(crate) struct RecordedRequest {
          46  +
    pub(crate) method: String,
          47  +
    pub(crate) uri: String,
          48  +
    pub(crate) headers: HashMap<String, String>,
          49  +
}
          50  +
          51  +
impl RecordedRequest {
          52  +
    fn from_request(request: &Request<Incoming>) -> Self {
          53  +
        Self {
          54  +
            method: request.method().to_string(),
          55  +
            uri: request.uri().to_string(),
          56  +
            headers: request
          57  +
                .headers()
          58  +
                .iter()
          59  +
                .map(|(name, value)| {
          60  +
                    (
          61  +
                        name.to_string(),
          62  +
                        value.to_str().unwrap_or_default().to_string(),
          63  +
                    )
          64  +
                })
          65  +
                .collect(),
          66  +
        }
          67  +
    }
          68  +
}
          69  +
          70  +
/// DNS resolver that maps one expected hostname to a test server.
          71  +
#[cfg(any(
          72  +
    feature = "rustls-aws-lc",
          73  +
    feature = "rustls-aws-lc-fips",
          74  +
    feature = "rustls-ring",
          75  +
    feature = "s2n-tls"
          76  +
))]
          77  +
#[derive(Clone, Debug)]
          78  +
pub(crate) struct RecordingDnsResolver {
          79  +
    hostname: String,
          80  +
    address: IpAddr,
          81  +
    lookups: Arc<Mutex<Vec<String>>>,
          82  +
}
          83  +
          84  +
#[cfg(any(
          85  +
    feature = "rustls-aws-lc",
          86  +
    feature = "rustls-aws-lc-fips",
          87  +
    feature = "rustls-ring",
          88  +
    feature = "s2n-tls"
          89  +
))]
          90  +
impl RecordingDnsResolver {
          91  +
    fn new(hostname: impl Into<String>, address: IpAddr) -> Self {
          92  +
        Self {
          93  +
            hostname: hostname.into(),
          94  +
            address,
          95  +
            lookups: Arc::new(Mutex::new(Vec::new())),
          96  +
        }
          97  +
    }
          98  +
          99  +
    pub(crate) fn lookups(&self) -> Vec<String> {
         100  +
        self.lookups
         101  +
            .lock()
         102  +
            .expect("DNS lookup log is not poisoned")
         103  +
            .clone()
         104  +
    }
         105  +
}
         106  +
         107  +
#[cfg(any(
         108  +
    feature = "rustls-aws-lc",
         109  +
    feature = "rustls-aws-lc-fips",
         110  +
    feature = "rustls-ring",
         111  +
    feature = "s2n-tls"
         112  +
))]
         113  +
impl ResolveDns for RecordingDnsResolver {
         114  +
    fn resolve_dns<'a>(&'a self, name: &'a str) -> DnsFuture<'a> {
         115  +
        self.lookups
         116  +
            .lock()
         117  +
            .expect("DNS lookup log is not poisoned")
         118  +
            .push(name.to_string());
         119  +
        if name == self.hostname {
         120  +
            DnsFuture::ready(Ok(vec![self.address]))
         121  +
        } else {
         122  +
            DnsFuture::ready(Err(ResolveDnsError::new(std::io::Error::other(format!(
         123  +
                "unexpected DNS lookup for {name}"
         124  +
            )))))
         125  +
        }
         126  +
    }
         127  +
}
         128  +
         129  +
/// HTTP/1 server used as either a forward proxy or a direct origin.
         130  +
#[derive(Debug)]
         131  +
pub(crate) struct MockHttpServer {
         132  +
    connections: Arc<()>,
         133  +
    addr: SocketAddr,
         134  +
    shutdown: Option<oneshot::Sender<()>>,
         135  +
    requests: Arc<Mutex<Vec<RecordedRequest>>>,
         136  +
}
         137  +
         138  +
impl MockHttpServer {
         139  +
    pub(crate) async fn new<F>(handler: F) -> Self
         140  +
    where
         141  +
        F: Fn(RecordedRequest) -> Response<String> + Send + Sync + 'static,
         142  +
    {
         143  +
        let listener = TcpListener::bind("127.0.0.1:0")
         144  +
            .await
         145  +
            .expect("test server should bind");
         146  +
        let addr = listener.local_addr().expect("listener has an address");
         147  +
        let (shutdown, mut shutdown_rx) = oneshot::channel();
         148  +
        let requests = Arc::new(Mutex::new(Vec::new()));
         149  +
        let server_requests = requests.clone();
         150  +
        let connections = Arc::new(());
         151  +
        let server_connections = connections.clone();
         152  +
        let handler = Arc::new(handler);
         153  +
         154  +
        tokio::spawn(async move {
         155  +
            loop {
         156  +
                tokio::select! {
         157  +
                    accepted = listener.accept() => {
         158  +
                        let Ok((stream, _)) = accepted else {
         159  +
                            break;
         160  +
                        };
         161  +
                        let handler = handler.clone();
         162  +
                        let requests = server_requests.clone();
         163  +
                        let connection = server_connections.clone();
         164  +
                        tokio::spawn(async move {
         165  +
                            let _connection = connection;
         166  +
                            let service = service_fn(move |request: Request<Incoming>| {
         167  +
                                let handler = handler.clone();
         168  +
                                let requests = requests.clone();
         169  +
                                async move {
         170  +
                                    let request = RecordedRequest::from_request(&request);
         171  +
                                    requests
         172  +
                                        .lock()
         173  +
                                        .expect("request log is not poisoned")
         174  +
                                        .push(request.clone());
         175  +
                                    Ok::<_, Infallible>(handler(request))
         176  +
                                }
         177  +
                            });
         178  +
                            if let Err(error) = hyper::server::conn::http1::Builder::new()
         179  +
                                .serve_connection(TokioIo::new(stream), service)
         180  +
                                .await
         181  +
                            {
         182  +
                                tracing::debug!(%error, "test HTTP server connection ended");
         183  +
                            }
         184  +
                        });
         185  +
                    }
         186  +
                    _ = &mut shutdown_rx => break,
         187  +
                }
         188  +
            }
         189  +
        });
         190  +
         191  +
        Self {
         192  +
            connections,
         193  +
            addr,
         194  +
            shutdown: Some(shutdown),
         195  +
            requests,
         196  +
        }
         197  +
    }
         198  +
         199  +
    pub(crate) async fn with_response(status: StatusCode, body: &str) -> Self {
         200  +
        let body = body.to_string();
         201  +
        Self::new(move |_| {
         202  +
            Response::builder()
         203  +
                .status(status)
         204  +
                .body(body.clone())
         205  +
                .expect("valid response")
         206  +
        })
         207  +
        .await
         208  +
    }
         209  +
         210  +
    pub(crate) async fn with_auth_validation(user: &str, password: &str) -> Self {
         211  +
        let expected = basic_authorization(user, password);
         212  +
        Self::new(move |request| {
         213  +
            if request.headers.get("proxy-authorization") == Some(&expected) {
         214  +
                Response::builder()
         215  +
                    .status(StatusCode::OK)
         216  +
                    .body("authenticated".to_string())
         217  +
                    .expect("valid response")
         218  +
            } else {
         219  +
                Response::builder()
         220  +
                    .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
         221  +
                    .header("proxy-authenticate", "Basic realm=\"proxy\"")
         222  +
                    .body("authentication required".to_string())
         223  +
                    .expect("valid response")
         224  +
            }
         225  +
        })
         226  +
        .await
         227  +
    }
         228  +
         229  +
    pub(crate) fn addr(&self) -> SocketAddr {
         230  +
        self.addr
         231  +
    }
         232  +
         233  +
    #[cfg(any(
         234  +
        feature = "rustls-aws-lc",
         235  +
        feature = "rustls-aws-lc-fips",
         236  +
        feature = "rustls-ring",
         237  +
        feature = "s2n-tls"
         238  +
    ))]
         239  +
    pub(crate) fn dns_resolver(&self, hostname: impl Into<String>) -> RecordingDnsResolver {
         240  +
        RecordingDnsResolver::new(hostname, self.addr.ip())
         241  +
    }
         242  +
         243  +
    pub(crate) fn requests(&self) -> Vec<RecordedRequest> {
         244  +
        self.requests
         245  +
            .lock()
         246  +
            .expect("request log is not poisoned")
         247  +
            .clone()
         248  +
    }
         249  +
         250  +
    pub(crate) fn connection_count(&self) -> usize {
         251  +
        // The server and this handle each retain one reference.
         252  +
        Arc::strong_count(&self.connections)
         253  +
            .checked_sub(2)
         254  +
            .expect("server references remain")
         255  +
    }
         256  +
}
         257  +
         258  +
impl Drop for MockHttpServer {
         259  +
    fn drop(&mut self) {
         260  +
        if let Some(shutdown) = self.shutdown.take() {
         261  +
            let _ = shutdown.send(());
         262  +
        }
         263  +
    }
         264  +
}
         265  +
         266  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         267  +
#[derive(Debug)]
         268  +
pub(crate) struct MockConnectProxy {
         269  +
    addr: SocketAddr,
         270  +
    shutdown: Option<oneshot::Sender<()>>,
         271  +
    requests: Arc<Mutex<Vec<RecordedRequest>>>,
         272  +
}
         273  +
         274  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         275  +
impl MockConnectProxy {
         276  +
    /// Starts a proxy that accepts CONNECT and relays bytes to one fixed origin.
         277  +
    pub(crate) async fn relay_to(
         278  +
        origin: SocketAddr,
         279  +
        expected_authorization: Option<String>,
         280  +
    ) -> Self {
         281  +
        let listener = TcpListener::bind("127.0.0.1:0")
         282  +
            .await
         283  +
            .expect("CONNECT proxy should bind");
         284  +
        let addr = listener.local_addr().expect("listener has an address");
         285  +
        let (shutdown, mut shutdown_rx) = oneshot::channel();
         286  +
        let requests = Arc::new(Mutex::new(Vec::new()));
         287  +
        let server_requests = requests.clone();
         288  +
         289  +
        tokio::spawn(async move {
         290  +
            loop {
         291  +
                tokio::select! {
         292  +
                    accepted = listener.accept() => {
         293  +
                        let Ok((stream, _)) = accepted else {
         294  +
                            break;
         295  +
                        };
         296  +
                        let requests = server_requests.clone();
         297  +
                        let expected_authorization = expected_authorization.clone();
         298  +
                        tokio::spawn(async move {
         299  +
                            let service = service_fn(move |mut request: Request<Incoming>| {
         300  +
                                let requests = requests.clone();
         301  +
                                let expected_authorization = expected_authorization.clone();
         302  +
                                async move {
         303  +
                                    let recorded = RecordedRequest::from_request(&request);
         304  +
                                    requests
         305  +
                                        .lock()
         306  +
                                        .expect("request log is not poisoned")
         307  +
                                        .push(recorded.clone());
         308  +
         309  +
                                    if recorded.method != "CONNECT" {
         310  +
                                        return Ok::<_, Infallible>(empty_response(
         311  +
                                            StatusCode::METHOD_NOT_ALLOWED,
         312  +
                                        ));
         313  +
                                    }
         314  +
                                    if expected_authorization.as_ref()
         315  +
                                        != recorded.headers.get("proxy-authorization")
         316  +
                                    {
         317  +
                                        return Ok(empty_response(
         318  +
                                            StatusCode::PROXY_AUTHENTICATION_REQUIRED,
         319  +
                                        ));
         320  +
                                    }
         321  +
         322  +
                                    let upgraded = hyper::upgrade::on(&mut request);
         323  +
                                    tokio::spawn(async move {
         324  +
                                        let Ok(upgraded) = upgraded.await else {
         325  +
                                            return;
         326  +
                                        };
         327  +
                                        let Ok(mut upstream) = TcpStream::connect(origin).await else {
         328  +
                                            return;
         329  +
                                        };
         330  +
                                        let mut downstream = TokioIo::new(upgraded);
         331  +
                                        let _ = copy_bidirectional(&mut downstream, &mut upstream).await;
         332  +
                                    });
         333  +
         334  +
                                    Ok(empty_response(StatusCode::OK))
         335  +
                                }
         336  +
                            });
         337  +
                            let connection = hyper::server::conn::http1::Builder::new()
         338  +
                                .serve_connection(TokioIo::new(stream), service)
         339  +
                                .with_upgrades();
         340  +
                            if let Err(error) = connection.await {
         341  +
                                tracing::debug!(%error, "CONNECT proxy connection ended");
         342  +
                            }
         343  +
                        });
         344  +
                    }
         345  +
                    _ = &mut shutdown_rx => break,
         346  +
                }
         347  +
            }
         348  +
        });
         349  +
         350  +
        Self {
         351  +
            addr,
         352  +
            shutdown: Some(shutdown),
         353  +
            requests,
         354  +
        }
         355  +
    }
         356  +
         357  +
    pub(crate) fn addr(&self) -> SocketAddr {
         358  +
        self.addr
         359  +
    }
         360  +
         361  +
    pub(crate) fn requests(&self) -> Vec<RecordedRequest> {
         362  +
        self.requests
         363  +
            .lock()
         364  +
            .expect("request log is not poisoned")
         365  +
            .clone()
         366  +
    }
         367  +
}
         368  +
         369  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         370  +
impl Drop for MockConnectProxy {
         371  +
    fn drop(&mut self) {
         372  +
        if let Some(shutdown) = self.shutdown.take() {
         373  +
            let _ = shutdown.send(());
         374  +
        }
         375  +
    }
         376  +
}
         377  +
         378  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         379  +
#[derive(Debug)]
         380  +
pub(crate) struct MockTlsOrigin {
         381  +
    addr: SocketAddr,
         382  +
    shutdown: Option<oneshot::Sender<()>>,
         383  +
    requests: Arc<Mutex<Vec<RecordedRequest>>>,
         384  +
}
         385  +
         386  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         387  +
impl MockTlsOrigin {
         388  +
    pub(crate) async fn new(body: &str) -> Self {
         389  +
        let listener = TcpListener::bind("127.0.0.1:0")
         390  +
            .await
         391  +
            .expect("TLS origin should bind");
         392  +
        let addr = listener.local_addr().expect("listener has an address");
         393  +
        let acceptor = super::tls::SERVER_IDENTITY
         394  +
            .acceptor(&[b"http/1.1"])
         395  +
            .expect("test TLS configuration should load");
         396  +
        let (shutdown, mut shutdown_rx) = oneshot::channel();
         397  +
        let requests = Arc::new(Mutex::new(Vec::new()));
         398  +
        let server_requests = requests.clone();
         399  +
        let body = body.to_string();
         400  +
         401  +
        tokio::spawn(async move {
         402  +
            loop {
         403  +
                tokio::select! {
         404  +
                    accepted = listener.accept() => {
         405  +
                        let Ok((stream, _)) = accepted else {
         406  +
                            break;
         407  +
                        };
         408  +
                        let acceptor = acceptor.clone();
         409  +
                        let requests = server_requests.clone();
         410  +
                        let body = body.clone();
         411  +
                        tokio::spawn(async move {
         412  +
                            let Ok(stream) = acceptor.accept(stream).await else {
         413  +
                                return;
         414  +
                            };
         415  +
                            let service = service_fn(move |request: Request<Incoming>| {
         416  +
                                let requests = requests.clone();
         417  +
                                let body = body.clone();
         418  +
                                async move {
         419  +
                                    requests
         420  +
                                        .lock()
         421  +
                                        .expect("request log is not poisoned")
         422  +
                                        .push(RecordedRequest::from_request(&request));
         423  +
                                    Ok::<_, Infallible>(Response::new(Full::new(Bytes::from(body))))
         424  +
                                }
         425  +
                            });
         426  +
                            if let Err(error) = hyper::server::conn::http1::Builder::new()
         427  +
                                .serve_connection(TokioIo::new(stream), service)
         428  +
                                .await
         429  +
                            {
         430  +
                                tracing::debug!(%error, "TLS origin connection ended");
         431  +
                            }
         432  +
                        });
         433  +
                    }
         434  +
                    _ = &mut shutdown_rx => break,
         435  +
                }
         436  +
            }
         437  +
        });
         438  +
         439  +
        Self {
         440  +
            addr,
         441  +
            shutdown: Some(shutdown),
         442  +
            requests,
         443  +
        }
         444  +
    }
         445  +
         446  +
    pub(crate) fn addr(&self) -> SocketAddr {
         447  +
        self.addr
         448  +
    }
         449  +
         450  +
    pub(crate) fn requests(&self) -> Vec<RecordedRequest> {
         451  +
        self.requests
         452  +
            .lock()
         453  +
            .expect("request log is not poisoned")
         454  +
            .clone()
         455  +
    }
         456  +
}
         457  +
         458  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         459  +
impl Drop for MockTlsOrigin {
         460  +
    fn drop(&mut self) {
         461  +
        if let Some(shutdown) = self.shutdown.take() {
         462  +
            let _ = shutdown.send(());
         463  +
        }
         464  +
    }
         465  +
}
         466  +
         467  +
pub(crate) fn basic_authorization(user: &str, password: &str) -> String {
         468  +
    format!(
         469  +
        "Basic {}",
         470  +
        base64::prelude::BASE64_STANDARD.encode(format!("{user}:{password}"))
         471  +
    )
         472  +
}
         473  +
         474  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         475  +
fn empty_response(status: StatusCode) -> Response<Full<Bytes>> {
         476  +
    Response::builder()
         477  +
        .status(status)
         478  +
        .body(Full::default())
         479  +
        .expect("valid response")
         480  +
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/tests/common/tls.rs

@@ -1,1 +68,93 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
    6         -
//! TLS setup shared by integration-test servers and clients.
           6  +
//! TLS identity and configuration shared by integration tests.
    7      7   
    8         -
use aws_smithy_http_client::tls::{TlsContext, TrustStore};
           8  +
use aws_smithy_http_client::tls::{TlsContext, TlsContextBuilder, TrustStore};
    9      9   
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
   10     10   
use std::fs;
   11     11   
use std::io;
   12     12   
use std::sync::Arc;
   13     13   
use tokio_rustls::{rustls, rustls::ServerConfig, TlsAcceptor};
   14     14   
   15         -
const SERVER_CERT_PATH: &str = "tests/server.pem";
   16         -
const SERVER_KEY_PATH: &str = "tests/server.rsa";
          15  +
/// PEM-backed identity used by integration-test TLS servers.
          16  +
///
          17  +
/// The certificate contains `localhost` and `sdktest.com` as subject
          18  +
/// alternative names.
          19  +
pub(crate) const SERVER_IDENTITY: TestTlsIdentity =
          20  +
    TestTlsIdentity::from_pem("tests/server.pem", "tests/server.rsa");
   17     21   
   18         -
pub(crate) fn server_tls_acceptor(alpn_protocols: &[&[u8]]) -> io::Result<TlsAcceptor> {
   19         -
    // Set the process-wide crypto provider used by the test server.
   20         -
    let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
          22  +
/// Certificate and private-key material for one test TLS identity.
          23  +
#[derive(Clone, Copy, Debug)]
          24  +
pub(crate) struct TestTlsIdentity {
          25  +
    certificate_path: &'static str,
          26  +
    private_key_path: &'static str,
          27  +
}
   21     28   
   22         -
    let certs = load_certs(SERVER_CERT_PATH)?;
   23         -
    let key = load_private_key(SERVER_KEY_PATH)?;
   24         -
    let mut server_config = ServerConfig::builder()
   25         -
        .with_no_client_auth()
   26         -
        .with_single_cert(certs, key)
   27         -
        .map_err(|err| error(err.to_string()))?;
   28         -
    server_config.alpn_protocols = alpn_protocols
   29         -
        .iter()
   30         -
        .map(|protocol| protocol.to_vec())
   31         -
        .collect();
          29  +
impl TestTlsIdentity {
          30  +
    const fn from_pem(certificate_path: &'static str, private_key_path: &'static str) -> Self {
          31  +
        Self {
          32  +
            certificate_path,
          33  +
            private_key_path,
          34  +
        }
          35  +
    }
   32     36   
   33         -
    Ok(TlsAcceptor::from(Arc::new(server_config)))
   34         -
}
          37  +
    /// Builds a server acceptor using this identity and ALPN protocol list.
          38  +
    pub(crate) fn acceptor(&self, alpn_protocols: &[&[u8]]) -> io::Result<TlsAcceptor> {
          39  +
        // The test server uses one process-wide rustls crypto provider.
          40  +
        let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
   35     41   
   36         -
pub(crate) fn server_tls_context() -> TlsContext {
   37         -
    TlsContext::builder()
   38         -
        .with_trust_store(server_trust_store())
   39         -
        .build()
   40         -
        .expect("failed to build TlsContext with test server certificate")
   41         -
}
          42  +
        let certs = load_certs(self.certificate_path)?;
          43  +
        let key = load_private_key(self.private_key_path)?;
          44  +
        let mut server_config = ServerConfig::builder()
          45  +
            .with_no_client_auth()
          46  +
            .with_single_cert(certs, key)
          47  +
            .map_err(|err| error(err.to_string()))?;
          48  +
        server_config.alpn_protocols = alpn_protocols
          49  +
            .iter()
          50  +
            .map(|protocol| protocol.to_vec())
          51  +
            .collect();
          52  +
          53  +
        Ok(TlsAcceptor::from(Arc::new(server_config)))
          54  +
    }
          55  +
          56  +
    /// Returns a client TLS context builder that trusts this identity.
          57  +
    pub(crate) fn client_context_builder(&self) -> TlsContextBuilder {
          58  +
        TlsContext::builder().with_trust_store(self.trust_store())
          59  +
    }
          60  +
          61  +
    /// Builds a client TLS context that trusts this identity.
          62  +
    pub(crate) fn client_context(&self) -> TlsContext {
          63  +
        self.client_context_builder()
          64  +
            .build()
          65  +
            .expect("test TLS identity produces a valid client context")
          66  +
    }
   42     67   
   43         -
/// A [`TrustStore`] containing only the test server certificate.
   44         -
pub(crate) fn server_trust_store() -> TrustStore {
   45         -
    let pem_contents =
   46         -
        fs::read(SERVER_CERT_PATH).expect("failed to read server certificate for test TLS context");
   47         -
    TrustStore::empty().with_pem_certificate(pem_contents)
          68  +
    fn trust_store(&self) -> TrustStore {
          69  +
        let pem_contents =
          70  +
            fs::read(self.certificate_path).expect("failed to read test TLS identity certificate");
          71  +
        TrustStore::empty().with_pem_certificate(pem_contents)
          72  +
    }
   48     73   
}
   49     74   
   50     75   
fn error(err: String) -> io::Error {
   51     76   
    io::Error::other(err)
   52     77   
}
   53     78   
   54     79   
fn load_certs(filename: &str) -> io::Result<Vec<CertificateDer<'static>>> {
   55     80   
    let certfile = fs::File::open(filename)
   56     81   
        .map_err(|err| error(format!("failed to open {filename}: {err}")))?;
   57     82   
    let mut reader = io::BufReader::new(certfile);

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/tests/connection_pool_test.rs

@@ -16,16 +75,108 @@
   36     36   
    SharedHttpClient::new(
   37     37   
        Client::from_partition(pool, partition).expect("declared partition should resolve"),
   38     38   
    )
   39     39   
}
   40     40   
   41     41   
/// Builds an operation connector with the shared test runtime components.
   42     42   
fn connector(client: &SharedHttpClient) -> SharedHttpConnector {
   43     43   
    test_client::connector(client)
   44     44   
}
   45     45   
          46  +
#[tokio::test]
          47  +
async fn custom_dns_resolver_is_used_for_explicit_partition_connections() {
          48  +
    const HOST: &str = "partition-dns.test";
          49  +
          50  +
    let harness = ConnectionTestHarness::builder()
          51  +
        .endpoint(
          52  +
            IP1,
          53  +
            Http1Script::responses([Http1Response::ok().body("custom dns")]),
          54  +
        )
          55  +
        .dns(HOST, [IP1])
          56  +
        .build()
          57  +
        .await
          58  +
        .expect("harness should start");
          59  +
    let partition = PartitionId::from_index(1);
          60  +
    let pool = ConnectionPool::builder()
          61  +
        .partitions([Partition::new(partition, TokioDriverSpawner::current())])
          62  +
        .dns_resolver(harness.dns_resolver())
          63  +
        .build_http()
          64  +
        .expect("valid pool");
          65  +
    let client = shared_client(&pool, partition);
          66  +
    let connector = connector(&client);
          67  +
    let url = format!("http://{HOST}:{}/custom-dns", harness.port());
          68  +
          69  +
    let (status, body) = test_client::get_and_collect(&connector, &url).await;
          70  +
    assert_eq!((status, body.as_slice()), (200, b"custom dns".as_slice()));
          71  +
    assert_eq!(1, harness.dns_lookup_count());
          72  +
          73  +
    drop(connector);
          74  +
    drop(client);
          75  +
    drop(pool);
          76  +
    harness.shutdown().await.expect("clean harness shutdown");
          77  +
}
          78  +
   46     79   
#[tokio::test]
   47     80   
async fn bounded_waiter_proceeds_after_the_active_h1_returns() {
   48     81   
    let body_gate = ManualGate::new();
   49     82   
    let harness = ConnectionTestHarness::builder()
   50     83   
        .endpoint(
   51     84   
            IP1,
   52     85   
            Http1Script::responses([
   53     86   
                Http1Response::ok().body_plan(BodyPlan::split_at_gate(
   54     87   
                    "first-",
   55     88   
                    body_gate.waiter(),

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/tests/h1_connection_behavior_test.rs

@@ -1,1 +114,85 @@
   21     21   
   22     22   
// This inline module form selectively includes only the `client` submodule from
   23     23   
// `tests/common/`. Using `mod common;` (which goes through `common/mod.rs`) would
   24     24   
// also pull in the `h2` and `tls` submodules, generating dead-code warnings under
   25     25   
// `--all-features` since those modules contain items only the h2 and TLS tests use.
   26     26   
mod common {
   27     27   
    pub(crate) mod client;
   28     28   
}
   29     29   
   30     30   
use aws_smithy_async::assert_elapsed;
   31         -
use aws_smithy_http_client::pool::{Client as PoolClient, ConnectionPool};
   32     31   
use aws_smithy_http_client::test_util::wire::connection::{
   33     32   
    BodyPlan, ConnectionCloseReason, ConnectionEvent, ConnectionId, ConnectionScript,
   34     33   
    ConnectionTestHarness, EndpointPlan, HarnessError, Http1Response, Http1Script, ManualGate,
   35     34   
    SocketScript,
   36     35   
};
   37         -
use aws_smithy_http_client::Builder;
   38     36   
use aws_smithy_runtime_api::client::connection::{
   39     37   
    CaptureSmithyConnection, ConnectionMetadata as SmithyConnectionMetadata,
   40     38   
};
   41     39   
use aws_smithy_runtime_api::client::http::{
   42     40   
    HttpConnectorSettings, SharedHttpClient, SharedHttpConnector,
   43     41   
};
   44     42   
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
   45     43   
use aws_smithy_types::body::SdkBody;
   46     44   
use aws_smithy_types::retry::ErrorKind;
   47     45   
use common::client as test_client;
   48         -
use common::client::{BackendConfig, HyperUtilLegacyPool};
          46  +
use common::client::{
          47  +
    BackendConfig, HttpClientBackend, HyperUtilLegacyPool, PartitionedConnectionPool,
          48  +
};
   49     49   
use http_body_util::BodyExt;
   50     50   
use std::collections::HashSet;
   51     51   
use std::net::{IpAddr, Ipv4Addr};
   52     52   
use std::time::Duration;
   53     53   
   54     54   
const IP1: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
   55     55   
   56         -
trait HttpClientBackend {
   57         -
    fn build(&self, config: BackendConfig) -> SharedHttpClient;
   58         -
}
   59         -
   60         -
impl HttpClientBackend for HyperUtilLegacyPool {
   61         -
    fn build(&self, config: BackendConfig) -> SharedHttpClient {
   62         -
        let mut builder = Builder::new();
   63         -
        if let Some(pool_idle_timeout) = config.pool_idle_timeout {
   64         -
            builder = builder.pool_idle_timeout(pool_idle_timeout);
   65         -
        }
   66         -
        builder.build_http()
   67         -
    }
   68         -
}
   69         -
   70         -
#[derive(Clone, Copy, Debug)]
   71         -
struct PartitionedConnectionPool;
   72         -
   73         -
impl HttpClientBackend for PartitionedConnectionPool {
   74         -
    fn build(&self, config: BackendConfig) -> SharedHttpClient {
   75         -
        let mut builder = ConnectionPool::builder();
   76         -
        if let Some(pool_idle_timeout) = config.pool_idle_timeout {
   77         -
            builder = builder.idle_timeout(pool_idle_timeout);
   78         -
        }
   79         -
        let pool = builder.build_http().expect("valid connection-pool config");
   80         -
        let client = PoolClient::new(&pool).expect("anonymous partition exists");
   81         -
        SharedHttpClient::new(client)
   82         -
    }
   83         -
}
   84         -
   85     56   
fn request_with_body(url: &str, body: &[u8]) -> HttpRequest {
   86     57   
    let mut request = HttpRequest::new(SdkBody::from(body.to_vec()));
   87     58   
    request.set_method("POST").expect("valid HTTP method");
   88     59   
    request.set_uri(url).expect("valid HTTP URI");
   89     60   
    request
   90     61   
        .headers_mut()
   91     62   
        .insert("content-length", body.len().to_string());
   92     63   
    request
   93     64   
}
   94     65   
@@ -230,201 +289,261 @@
  250    221   
                EndpointPlan::queue([
  251    222   
                    Http1Script::responses([Http1Response::ok().body("first")]),
  252    223   
                    Http1Script::responses([Http1Response::ok().body("second")]),
  253    224   
                ]),
  254    225   
            )
  255    226   
            .build()
  256    227   
            .await
  257    228   
            .expect("harness should start");
  258    229   
        let client = backend.build(BackendConfig {
  259    230   
            pool_idle_timeout: Some(idle_timeout),
         231  +
            ..Default::default()
  260    232   
        });
  261    233   
        let connector = test_client::connector(&client);
  262    234   
  263    235   
        let (status, body) =
  264    236   
            test_client::get_and_collect(&connector, &harness.endpoint_url()).await;
  265    237   
        assert_eq!((status, body.as_slice()), (200, b"first".as_slice()));
  266    238   
        let first_connection = http1_request_connection_ids(&harness)[0];
  267    239   
  268    240   
        harness
  269    241   
            .wait_for_event(test_client::WAIT, |event| {
@@ -298,270 +357,330 @@
  318    290   
                        "body",
  319    291   
                    )),
  320    292   
                    Http1Response::ok().body("second"),
  321    293   
                ]),
  322    294   
            )
  323    295   
            .build()
  324    296   
            .await
  325    297   
            .expect("harness should start");
  326    298   
        let client = backend.build(BackendConfig {
  327    299   
            pool_idle_timeout: Some(idle_timeout),
         300  +
            ..Default::default()
  328    301   
        });
  329    302   
        let connector = test_client::connector(&client);
  330    303   
  331    304   
        let first_response = test_client::send_request(
  332    305   
            &connector,
  333    306   
            HttpRequest::get(harness.endpoint_url()).expect("valid HTTP request"),
  334    307   
        )
  335    308   
        .await
  336    309   
        .expect("first request should return response headers");
  337    310   
@@ -2008,1981 +2106,2133 @@
 2028   2001   
    }
 2029   2002   
 2030   2003   
    #[tokio::test]
 2031   2004   
    async fn test_request_is_not_written_before_the_previous_response_completes_with_partitioned_connection_pool(
 2032   2005   
    ) {
 2033   2006   
        request_is_not_written_before_the_previous_response_completes(&PartitionedConnectionPool)
 2034   2007   
            .await;
 2035   2008   
    }
 2036   2009   
}
 2037   2010   
 2038         -
// A DNS resolver can only be installed through `Builder::build_with_resolver`, which is
 2039         -
// available once a TLS provider is selected, so these tests need a TLS feature even though
 2040         -
// they speak plaintext HTTP. `CryptoMode::Ring` requires `rustls-ring` specifically.
 2041         -
#[cfg(feature = "rustls-ring")]
        2011  +
// The legacy backend's custom-resolver constructor requires a TLS provider even though this
        2012  +
// contract speaks plaintext HTTP.
        2013  +
#[cfg(any(
        2014  +
    feature = "rustls-aws-lc",
        2015  +
    feature = "rustls-aws-lc-fips",
        2016  +
    feature = "rustls-ring",
        2017  +
    feature = "s2n-tls"
        2018  +
))]
 2042   2019   
mod dns_resolution {
 2043   2020   
    use super::*;
 2044         -
    use aws_smithy_http_client::tls;
        2021  +
    use aws_smithy_runtime_api::client::dns::SharedDnsResolver;
        2022  +
        2023  +
    /// A configured resolver supplies the address used for an origin connection.
        2024  +
    async fn custom_dns_resolver_is_used_for_origin(backend: &dyn HttpClientBackend) {
        2025  +
        const HOST: &str = "custom-dns.test";
        2026  +
        2027  +
        let harness = ConnectionTestHarness::builder()
        2028  +
            .endpoint(
        2029  +
                IP1,
        2030  +
                Http1Script::responses([Http1Response::ok().body("custom dns")]),
        2031  +
            )
        2032  +
            .dns(HOST, [IP1])
        2033  +
            .build()
        2034  +
            .await
        2035  +
            .expect("harness should start");
        2036  +
        let client = backend.build(BackendConfig {
        2037  +
            dns_resolver: Some(SharedDnsResolver::new(harness.dns_resolver())),
        2038  +
            ..Default::default()
        2039  +
        });
        2040  +
        let connector = test_client::connector(&client);
        2041  +
        let url = format!("http://{HOST}:{}/custom-dns", harness.port());
        2042  +
        2043  +
        let (status, body) = test_client::get_and_collect(&connector, &url).await;
        2044  +
        assert_eq!((status, body.as_slice()), (200, b"custom dns".as_slice()));
        2045  +
        assert_eq!(1, harness.dns_lookup_count());
        2046  +
        assert_eq!(
        2047  +
            vec![(
        2048  +
                "/custom-dns".to_string(),
        2049  +
                Some(format!("{HOST}:{}", harness.port()))
        2050  +
            )],
        2051  +
            harness.http_requests()
        2052  +
        );
        2053  +
        2054  +
        shutdown_harness(harness, connector, client)
        2055  +
            .await
        2056  +
            .expect("clean harness shutdown");
        2057  +
    }
        2058  +
        2059  +
    #[tokio::test]
        2060  +
    async fn test_custom_dns_resolver_is_used_for_origin_with_hyper_util_legacy_pool() {
        2061  +
        custom_dns_resolver_is_used_for_origin(&HyperUtilLegacyPool).await;
        2062  +
    }
        2063  +
        2064  +
    #[tokio::test]
        2065  +
    async fn test_custom_dns_resolver_is_used_for_origin_with_partitioned_connection_pool() {
        2066  +
        custom_dns_resolver_is_used_for_origin(&PartitionedConnectionPool).await;
        2067  +
    }
 2045   2068   
 2046   2069   
    /// A hostname with no DNS entry fails the request at resolution, before any TCP
 2047   2070   
    /// connection is attempted.
 2048         -
    async fn unresolvable_hostname_fails_before_connect() {
        2071  +
    async fn unresolvable_hostname_fails_before_connect(backend: &dyn HttpClientBackend) {
 2049   2072   
        // The endpoint is never connected to. It exists because a harness requires at
 2050   2073   
        // least one endpoint, and it supplies the port used to build the request URL.
 2051   2074   
        let harness = ConnectionTestHarness::builder()
 2052   2075   
            .endpoint(IP1, SocketScript::new().await_client_close())
 2053   2076   
            .build()
 2054   2077   
            .await
 2055   2078   
            .expect("harness should start");
 2056         -
        let client = Builder::new()
 2057         -
            .tls_provider(tls::Provider::Rustls(
 2058         -
                tls::rustls_provider::CryptoMode::Ring,
 2059         -
            ))
 2060         -
            .build_with_resolver(harness.dns_resolver());
        2079  +
        let client = backend.build(BackendConfig {
        2080  +
            dns_resolver: Some(SharedDnsResolver::new(harness.dns_resolver())),
        2081  +
            ..Default::default()
        2082  +
        });
 2061   2083   
        let connector = test_client::connector(&client);
 2062   2084   
 2063   2085   
        let url = format!("http://unknown.test:{}/", harness.port());
 2064   2086   
        let error = test_client::send_request(
 2065   2087   
            &connector,
 2066   2088   
            HttpRequest::get(&url).expect("valid HTTP request"),
 2067   2089   
        )
 2068   2090   
        .await
 2069   2091   
        .expect_err("an unresolvable hostname must fail the request");
 2070   2092   
        assert!(
 2071   2093   
            error.is_io(),
 2072   2094   
            "expected ConnectorError::io for a DNS failure, got {error:?}"
 2073   2095   
        );
 2074   2096   
        let chain = error_chain_display(&error);
 2075   2097   
        assert!(
 2076   2098   
            chain.contains("dns error"),
 2077   2099   
            "the error should identify DNS resolution as the cause, got: {chain}"
 2078   2100   
        );
 2079   2101   
 2080   2102   
        assert_eq!(
 2081   2103   
            harness.tcp_accepted_count(),
 2082   2104   
            0,
 2083   2105   
            "no TCP connection should be attempted when DNS resolution fails"
 2084   2106   
        );
 2085   2107   
        let lookups = harness
 2086   2108   
            .events()
 2087   2109   
            .into_iter()
 2088   2110   
            .filter(
 2089   2111   
                |event| matches!(event, ConnectionEvent::DnsLookup { hostname } if hostname == "unknown.test"),
 2090   2112   
            )
 2091   2113   
            .count();
 2092   2114   
        assert_eq!(
 2093   2115   
            lookups, 1,
 2094   2116   
            "the failed lookup should be recorded exactly once"
 2095   2117   
        );
 2096   2118   
 2097   2119   
        shutdown_harness(harness, connector, client)
 2098   2120   
            .await
 2099   2121   
            .expect("clean harness shutdown");
 2100   2122   
    }
 2101   2123   
 2102   2124   
    #[tokio::test]
 2103   2125   
    async fn test_unresolvable_hostname_fails_before_connect_with_hyper_util_legacy_pool() {
 2104         -
        unresolvable_hostname_fails_before_connect().await;
        2126  +
        unresolvable_hostname_fails_before_connect(&HyperUtilLegacyPool).await;
        2127  +
    }
        2128  +
        2129  +
    #[tokio::test]
        2130  +
    async fn test_unresolvable_hostname_fails_before_connect_with_partitioned_connection_pool() {
        2131  +
        unresolvable_hostname_fails_before_connect(&PartitionedConnectionPool).await;
 2105   2132   
    }
 2106   2133   
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/tests/h2_connection_behavior_test.rs

@@ -1,1 +130,81 @@
   11     11   
#![cfg(all(feature = "wire-mock", feature = "rustls-aws-lc"))]
   12     12   
   13     13   
mod common;
   14     14   
   15     15   
use aws_smithy_http_client::pool::{
   16     16   
    Client as PoolClient, ConnectionPool, ConnectionReuseScope, Partition, PartitionId,
   17     17   
    TokioDriverSpawner,
   18     18   
};
   19     19   
use aws_smithy_http_client::test_util::wire::connection::{ConnectionCloseReason, ManualGate};
   20     20   
use aws_smithy_http_client::tls;
   21         -
use aws_smithy_http_client::Builder;
   22     21   
use aws_smithy_runtime_api::client::connection::{
   23     22   
    CaptureSmithyConnection, ConnectionMetadata as SmithyConnectionMetadata,
   24     23   
};
   25     24   
use aws_smithy_runtime_api::client::http::{SharedHttpClient, SharedHttpConnector};
   26     25   
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
   27     26   
use bytes::Bytes;
   28     27   
use common::client as test_client;
   29         -
use common::client::{BackendConfig, HyperUtilLegacyPool};
          28  +
use common::client::{
          29  +
    BackendConfig, HttpsClientBackend, HyperUtilLegacyPool, PartitionedConnectionPool,
          30  +
};
   30     31   
use common::h2::{
   31     32   
    H2BodyPlan, H2ConnectionId, H2ConnectionPlan, H2ConnectionScript, H2Event, H2Response,
   32     33   
    H2StreamScript, H2TestServer,
   33     34   
};
   34     35   
use common::tls as test_tls;
   35     36   
use h2::Reason;
   36     37   
use http_body_util::BodyExt;
   37     38   
use std::error::Error;
   38     39   
   39         -
trait HttpsClientBackend {
   40         -
    fn build_https(
   41         -
        &self,
   42         -
        config: BackendConfig,
   43         -
        provider: tls::Provider,
   44         -
        tls_context: tls::TlsContext,
   45         -
    ) -> SharedHttpClient;
   46         -
}
   47         -
   48         -
impl HttpsClientBackend for HyperUtilLegacyPool {
   49         -
    fn build_https(
   50         -
        &self,
   51         -
        config: BackendConfig,
   52         -
        provider: tls::Provider,
   53         -
        tls_context: tls::TlsContext,
   54         -
    ) -> SharedHttpClient {
   55         -
        let mut builder = Builder::new();
   56         -
        if let Some(pool_idle_timeout) = config.pool_idle_timeout {
   57         -
            builder = builder.pool_idle_timeout(pool_idle_timeout);
   58         -
        }
   59         -
        builder
   60         -
            .tls_provider(provider)
   61         -
            .tls_context(tls_context)
   62         -
            .build_https()
   63         -
    }
   64         -
}
   65         -
   66         -
#[derive(Clone, Copy, Debug)]
   67         -
struct PartitionedConnectionPool;
   68         -
   69         -
impl HttpsClientBackend for PartitionedConnectionPool {
   70         -
    fn build_https(
   71         -
        &self,
   72         -
        config: BackendConfig,
   73         -
        provider: tls::Provider,
   74         -
        tls_context: tls::TlsContext,
   75         -
    ) -> SharedHttpClient {
   76         -
        let mut builder = ConnectionPool::builder().tls_provider(provider);
   77         -
        if let Some(pool_idle_timeout) = config.pool_idle_timeout {
   78         -
            builder = builder.idle_timeout(pool_idle_timeout);
   79         -
        }
   80         -
        let pool = builder
   81         -
            .tls_context(tls_context)
   82         -
            .build_https()
   83         -
            .expect("valid connection-pool config");
   84         -
        let client = PoolClient::new(&pool).expect("anonymous partition exists");
   85         -
        SharedHttpClient::new(client)
   86         -
    }
   87         -
}
   88         -
   89     40   
fn rustls_aws_lc() -> tls::Provider {
   90     41   
    tls::Provider::Rustls(tls::rustls_provider::CryptoMode::AwsLc)
   91     42   
}
   92     43   
   93     44   
fn h2_client_with_provider(
   94     45   
    backend: &dyn HttpsClientBackend,
   95     46   
    provider: tls::Provider,
   96     47   
) -> SharedHttpClient {
   97     48   
    backend.build_https(
   98     49   
        BackendConfig::default(),
   99     50   
        provider,
  100         -
        test_tls::server_tls_context(),
          51  +
        test_tls::SERVER_IDENTITY.client_context(),
  101     52   
    )
  102     53   
}
  103     54   
  104     55   
fn h2_client(backend: &dyn HttpsClientBackend) -> SharedHttpClient {
  105     56   
    h2_client_with_provider(backend, rustls_aws_lc())
  106     57   
}
  107     58   
  108     59   
fn stream_connection_ids(server: &H2TestServer, path: &str) -> Vec<H2ConnectionId> {
  109     60   
    server
  110     61   
        .events()
@@ -771,722 +833,785 @@
  791    742   
            finish: finished,
  792    743   
            complete: false,
  793    744   
        };
  794    745   
        (finish, SdkBody::from_body_1_x(body))
  795    746   
    }
  796    747   
  797    748   
    fn client_with_idle_timeout(backend: &dyn HttpsClientBackend) -> SharedHttpClient {
  798    749   
        backend.build_https(
  799    750   
            BackendConfig {
  800    751   
                pool_idle_timeout: Some(IDLE_TIMEOUT),
         752  +
                ..Default::default()
  801    753   
            },
  802    754   
            rustls_aws_lc(),
  803         -
            test_tls::server_tls_context(),
         755  +
            test_tls::SERVER_IDENTITY.client_context(),
  804    756   
        )
  805    757   
    }
  806    758   
  807    759   
    /// An idle H2 connection is closed after its configured timeout and then replaced.
  808    760   
    async fn idle_connection_is_evicted_after_timeout(backend: &dyn HttpsClientBackend) {
  809    761   
        let script =
  810    762   
            H2ConnectionScript::new().fallback(H2StreamScript::respond(H2Response::ok("ok")));
  811    763   
        let server = H2TestServer::builder()
  812    764   
            .connections(H2ConnectionPlan::queue([script.clone(), script]))
  813    765   
            .start()
@@ -998,950 +1058,1010 @@
 1018    970   
    use super::*;
 1019    971   
    use std::time::Duration;
 1020    972   
 1021    973   
    fn partitioned_clients(
 1022    974   
        scope: ConnectionReuseScope,
 1023    975   
    ) -> (ConnectionPool, SharedHttpConnector, SharedHttpConnector) {
 1024    976   
        let first = PartitionId::from_index(1);
 1025    977   
        let second = PartitionId::from_index(2);
 1026    978   
        let pool = ConnectionPool::builder()
 1027    979   
            .tls_provider(rustls_aws_lc())
 1028         -
            .tls_context(test_tls::server_tls_context())
         980  +
            .tls_context(test_tls::SERVER_IDENTITY.client_context())
 1029    981   
            .partitions([
 1030    982   
                Partition::new(first, TokioDriverSpawner::current()),
 1031    983   
                Partition::new(second, TokioDriverSpawner::current()),
 1032    984   
            ])
 1033    985   
            .connection_reuse_scope(scope)
 1034    986   
            .max_connections_per_host(1)
 1035    987   
            .build_https()
 1036    988   
            .expect("valid partitioned HTTPS pool");
 1037    989   
        let first_client = SharedHttpClient::new(
 1038    990   
            PoolClient::from_partition(&pool, first).expect("first partition should resolve"),

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/tests/proxy_tests.rs

@@ -1,1 +1200,1004 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
    6         -
//! Integration tests for proxy functionality
           6  +
//! Proxy behavior contracts shared by the legacy and partitioned connection pools.
    7      7   
//!
    8         -
//! These tests verify that proxy configuration works end-to-end with real HTTP requests
    9         -
//! using mock proxy servers.
           8  +
//! Each contract is backend-neutral and has an explicit runner for every client
           9  +
//! implementation that must preserve the behavior.
          10  +
   10     11   
#![cfg(feature = "default-client")]
   11     12   
   12         -
use aws_smithy_async::time::SystemTimeSource;
          13  +
mod common {
          14  +
    #[allow(dead_code)]
          15  +
    pub(crate) mod client;
          16  +
    #[cfg(all(
          17  +
        feature = "wire-mock",
          18  +
        any(feature = "rustls-ring", feature = "s2n-tls")
          19  +
    ))]
          20  +
    #[allow(dead_code)]
          21  +
    pub(crate) mod h2;
          22  +
    pub(crate) mod proxy;
          23  +
    #[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          24  +
    pub(crate) mod tls;
          25  +
}
          26  +
          27  +
use aws_smithy_http_client::proxy::ProxyConfig;
   13     28   
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
   14     29   
use aws_smithy_http_client::tls;
   15         -
use aws_smithy_http_client::{proxy::ProxyConfig, Connector};
   16         -
use aws_smithy_runtime_api::client::http::{
   17         -
    http_client_fn, HttpClient, HttpConnector, HttpConnectorSettings, SharedHttpConnector,
          30  +
use aws_smithy_runtime_api::box_error::BoxError;
          31  +
#[cfg(any(
          32  +
    feature = "rustls-aws-lc",
          33  +
    feature = "rustls-aws-lc-fips",
          34  +
    feature = "rustls-ring",
          35  +
    feature = "s2n-tls"
          36  +
))]
          37  +
use aws_smithy_runtime_api::client::dns::{
          38  +
    DnsFuture, ResolveDns, ResolveDnsError, SharedDnsResolver,
   18     39   
};
          40  +
use aws_smithy_runtime_api::client::http::{HttpConnector, SharedHttpClient, SharedHttpConnector};
   19     41   
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
   20         -
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
   21         -
use base64::Engine;
   22         -
use http_1x::{Request, Response, StatusCode};
          42  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          43  +
use common::client::HttpsClientBackend;
          44  +
use common::client::{
          45  +
    self as test_client, BackendConfig, HttpClientBackend, HyperUtilLegacyPool,
          46  +
    PartitionedConnectionPool,
          47  +
};
          48  +
#[cfg(all(
          49  +
    feature = "wire-mock",
          50  +
    any(feature = "rustls-ring", feature = "s2n-tls")
          51  +
))]
          52  +
use common::h2::{
          53  +
    H2ConnectionPlan, H2ConnectionScript, H2Event, H2Response, H2StreamScript, H2TestServer,
          54  +
};
          55  +
use common::proxy::{basic_authorization, MockHttpServer};
          56  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          57  +
use common::proxy::{MockConnectProxy, MockTlsOrigin};
          58  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          59  +
use common::tls as test_tls;
          60  +
use http_1x::{Response, StatusCode};
   23     61   
use http_body_util::BodyExt;
   24         -
use hyper::body::Incoming;
   25         -
use hyper::service::service_fn;
   26         -
use hyper_util::rt::TokioIo;
   27         -
use std::collections::HashMap;
   28         -
use std::convert::Infallible;
   29         -
use std::net::SocketAddr;
   30         -
use std::sync::{Arc, Mutex};
          62  +
use std::future::Future;
   31     63   
use std::time::Duration;
   32         -
use tokio::net::TcpListener;
   33         -
use tokio::sync::oneshot;
   34         -
   35         -
// ================================================================================================
   36         -
// Test Utilities (Mock Proxy Server)
   37         -
// ================================================================================================
   38         -
   39         -
/// Mock HTTP server that acts as a proxy endpoint for testing
   40         -
#[derive(Debug)]
   41         -
struct MockProxyServer {
   42         -
    conn_count: Arc<()>,
   43         -
    addr: SocketAddr,
   44         -
    shutdown_tx: Option<oneshot::Sender<()>>,
   45         -
    request_log: Arc<Mutex<Vec<RecordedRequest>>>,
   46         -
}
   47         -
   48         -
/// A recorded request received by the mock proxy server
   49         -
#[derive(Debug, Clone)]
   50         -
struct RecordedRequest {
   51         -
    method: String,
   52         -
    uri: String,
   53         -
    headers: HashMap<String, String>,
   54         -
}
   55         -
   56         -
impl MockProxyServer {
   57         -
    /// Create a new mock proxy server with a custom request handler
   58         -
    async fn new<F>(handler: F) -> Self
   59         -
    where
   60         -
        F: Fn(RecordedRequest) -> Response<String> + Send + Sync + 'static,
   61         -
    {
   62         -
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
   63         -
        let addr = listener.local_addr().unwrap();
   64         -
        let (shutdown_tx, shutdown_rx) = oneshot::channel();
   65         -
        let request_log = Arc::new(Mutex::new(Vec::new()));
   66         -
        let request_log_clone = request_log.clone();
   67         -
        let conn_count = Arc::new(());
   68         -
        let server_conn_count = conn_count.clone();
   69         -
   70         -
        let handler = Arc::new(handler);
   71         -
   72         -
        tokio::spawn(async move {
   73         -
            let mut shutdown_rx = shutdown_rx;
   74         -
   75         -
            loop {
   76         -
                tokio::select! {
   77         -
                    result = listener.accept() => {
   78         -
                        match result {
   79         -
                            Ok((stream, _)) => {
   80         -
                                let io = TokioIo::new(stream);
   81         -
                                let handler = handler.clone();
   82         -
                                let request_log = request_log_clone.clone();
   83         -
   84         -
                                let stream_conn_count = server_conn_count.clone();
   85         -
                                tokio::spawn(async move {
   86         -
                                    let _stream_conn_count = stream_conn_count;
   87         -
                                    let service = service_fn(move |req: Request<Incoming>| {
   88         -
                                        let handler = handler.clone();
   89         -
                                        let request_log = request_log.clone();
   90         -
   91         -
                                        async move {
   92         -
                                            // Record the request
   93         -
                                            let recorded = RecordedRequest {
   94         -
                                                method: req.method().to_string(),
   95         -
                                                uri: req.uri().to_string(),
   96         -
                                                headers: req.headers().iter()
   97         -
                                                    .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
   98         -
                                                    .collect(),
   99         -
                                            };
  100         -
  101         -
                                            request_log.lock().unwrap().push(recorded.clone());
  102         -
  103         -
                                            // Call the handler
  104         -
                                            let response = handler(recorded);
  105         -
  106         -
                                            // Convert to hyper response
  107         -
                                            let (parts, body) = response.into_parts();
  108         -
                                            let hyper_response = Response::from_parts(parts, body);
  109         -
  110         -
                                            Ok::<_, Infallible>(hyper_response)
  111         -
                                        }
  112         -
                                    });
  113         -
  114         -
                                    if let Err(err) = hyper::server::conn::http1::Builder::new()
  115         -
                                        .serve_connection(io, service)
  116         -
                                        .await
  117         -
                                    {
  118         -
                                        eprintln!("Mock proxy server connection error: {}", err);
  119         -
                                    }
  120         -
                                });
  121         -
                            }
  122         -
                            Err(_) => break,
  123         -
                        }
  124         -
                    }
  125         -
                    _ = &mut shutdown_rx => {
  126         -
                        break;
  127         -
                    }
  128         -
                }
  129         -
            }
  130         -
        });
  131         -
  132         -
        Self {
  133         -
            addr,
  134         -
            shutdown_tx: Some(shutdown_tx),
  135         -
            request_log,
  136         -
            conn_count,
  137         -
        }
          64  +
          65  +
struct TestClient {
          66  +
    _client: SharedHttpClient,
          67  +
    connector: SharedHttpConnector,
          68  +
}
          69  +
          70  +
fn http_client(backend: &dyn HttpClientBackend, config: BackendConfig) -> TestClient {
          71  +
    let client = backend.build(config);
          72  +
    let connector = test_client::connector(&client);
          73  +
    TestClient {
          74  +
        _client: client,
          75  +
        connector,
  138     76   
    }
          77  +
}
  139     78   
  140         -
    /// Return the number of active connections to this server
  141         -
    fn conn_count(&self) -> usize {
  142         -
        // 1 reference for the struct MockProxyServer, 1 reference for the
  143         -
        // socket task.
  144         -
        Arc::strong_count(&self.conn_count)
  145         -
            .checked_sub(2)
  146         -
            .expect("de-count 2 refs")
          79  +
fn proxy_backend_config(proxy_config: ProxyConfig) -> BackendConfig {
          80  +
    BackendConfig {
          81  +
        proxy_config: Some(proxy_config),
          82  +
        ..Default::default()
  147     83   
    }
          84  +
}
  148     85   
  149         -
    /// Create a simple mock proxy that returns a fixed response
  150         -
    async fn with_response(status: StatusCode, body: &str) -> Self {
  151         -
        let body = body.to_string();
  152         -
        Self::new(move |_req| {
  153         -
            Response::builder()
  154         -
                .status(status)
  155         -
                .body(body.clone())
  156         -
                .unwrap()
  157         -
        })
  158         -
        .await
          86  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
          87  +
fn https_client(
          88  +
    backend: &dyn HttpsClientBackend,
          89  +
    config: BackendConfig,
          90  +
    provider: tls::Provider,
          91  +
    tls_context: tls::TlsContext,
          92  +
) -> TestClient {
          93  +
    let client = backend.build_https(config, provider, tls_context);
          94  +
    let connector = test_client::connector(&client);
          95  +
    TestClient {
          96  +
        _client: client,
          97  +
        connector,
  159     98   
    }
          99  +
}
  160    100   
  161         -
    /// Create a mock proxy that validates basic authentication
  162         -
    async fn with_auth_validation(expected_user: &str, expected_pass: &str) -> Self {
  163         -
        let expected_auth = format!(
  164         -
            "Basic {}",
  165         -
            base64::prelude::BASE64_STANDARD.encode(format!("{}:{}", expected_user, expected_pass))
  166         -
        );
         101  +
async fn send_request(
         102  +
    client: &TestClient,
         103  +
    request: HttpRequest,
         104  +
) -> Result<(StatusCode, String), BoxError> {
         105  +
    let response = client.connector.call(request).await?;
         106  +
    let status = StatusCode::from_u16(response.status().as_u16())?;
         107  +
    let body = response.into_body().collect().await?.to_bytes();
         108  +
    Ok((status, String::from_utf8(body.to_vec())?))
         109  +
}
  167    110   
  168         -
        Self::new(move |req| {
  169         -
            if let Some(auth_header) = req.headers.get("proxy-authorization") {
  170         -
                if auth_header == &expected_auth {
  171         -
                    Response::builder()
  172         -
                        .status(StatusCode::OK)
  173         -
                        .body("authenticated".to_string())
  174         -
                        .unwrap()
  175         -
                } else {
  176         -
                    Response::builder()
  177         -
                        .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
  178         -
                        .body("invalid credentials".to_string())
  179         -
                        .unwrap()
  180         -
                }
  181         -
            } else {
  182         -
                Response::builder()
  183         -
                    .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
  184         -
                    .header("proxy-authenticate", "Basic realm=\"proxy\"")
  185         -
                    .body("authentication required".to_string())
  186         -
                    .unwrap()
  187         -
            }
  188         -
        })
  189         -
        .await
  190         -
    }
         111  +
async fn get(client: &TestClient, uri: &str) -> Result<(StatusCode, String), BoxError> {
         112  +
    send_request(client, HttpRequest::get(uri)?).await
         113  +
}
  191    114   
  192         -
    /// Get the address this server is listening on
  193         -
    fn addr(&self) -> SocketAddr {
  194         -
        self.addr
  195         -
    }
         115  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         116  +
#[derive(Clone, Debug)]
         117  +
struct FailingDns;
  196    118   
  197         -
    /// Get all requests received by this server
  198         -
    fn requests(&self) -> Vec<RecordedRequest> {
  199         -
        self.request_log.lock().unwrap().clone()
         119  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         120  +
impl ResolveDns for FailingDns {
         121  +
    fn resolve_dns<'a>(&'a self, name: &'a str) -> DnsFuture<'a> {
         122  +
        DnsFuture::ready(Err(ResolveDnsError::new(std::io::Error::other(format!(
         123  +
            "intentional DNS failure for {name}"
         124  +
        )))))
  200    125   
    }
  201    126   
}
  202    127   
  203         -
impl Drop for MockProxyServer {
  204         -
    fn drop(&mut self) {
  205         -
        if let Some(tx) = self.shutdown_tx.take() {
  206         -
            let _ = tx.send(());
  207         -
        }
  208         -
    }
         128  +
#[cfg(all(
         129  +
    feature = "wire-mock",
         130  +
    any(feature = "rustls-ring", feature = "s2n-tls")
         131  +
))]
         132  +
async fn h2_forward_proxy_applies_configured_authentication(
         133  +
    backend: &dyn HttpsClientBackend,
         134  +
    provider: tls::Provider,
         135  +
) {
         136  +
    let server = H2TestServer::builder()
         137  +
        .connections(H2ConnectionPlan::queue([H2ConnectionScript::new().route(
         138  +
            "/protected",
         139  +
            H2StreamScript::respond(H2Response::ok("authenticated over H2")),
         140  +
        )]))
         141  +
        .start()
         142  +
        .await
         143  +
        .expect("H2 forward proxy should start");
         144  +
    let config = ProxyConfig::http(server.url("/"))
         145  +
        .expect("valid proxy URI")
         146  +
        .with_basic_auth("h2user", "h2pass");
         147  +
    let client = https_client(
         148  +
        backend,
         149  +
        proxy_backend_config(config),
         150  +
        provider,
         151  +
        test_tls::SERVER_IDENTITY.client_context(),
         152  +
    );
         153  +
         154  +
    assert_eq!(
         155  +
        (StatusCode::OK, "authenticated over H2".to_string()),
         156  +
        get(&client, "http://origin.test/protected")
         157  +
            .await
         158  +
            .expect("H2 forward-proxy request should succeed")
         159  +
    );
         160  +
    let expected = basic_authorization("h2user", "h2pass");
         161  +
    let authorization = server.events().into_iter().find_map(|event| match event {
         162  +
        H2Event::StreamAccepted {
         163  +
            path,
         164  +
            proxy_authorization,
         165  +
            ..
         166  +
        } if path == "/protected" => proxy_authorization,
         167  +
        _ => None,
         168  +
    });
         169  +
    assert_eq!(Some(expected), authorization);
         170  +
         171  +
    drop(client);
         172  +
    server
         173  +
        .shutdown()
         174  +
        .await
         175  +
        .expect("clean H2 forward-proxy shutdown");
         176  +
}
         177  +
         178  +
#[cfg(all(feature = "wire-mock", feature = "rustls-ring"))]
         179  +
#[tokio::test]
         180  +
async fn test_h2_forward_proxy_auth_with_rustls_and_hyper_util_legacy_pool() {
         181  +
    h2_forward_proxy_applies_configured_authentication(
         182  +
        &HyperUtilLegacyPool,
         183  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         184  +
    )
         185  +
    .await;
         186  +
}
         187  +
         188  +
#[cfg(all(feature = "wire-mock", feature = "rustls-ring"))]
         189  +
#[tokio::test]
         190  +
async fn test_h2_forward_proxy_auth_with_rustls_and_partitioned_connection_pool() {
         191  +
    h2_forward_proxy_applies_configured_authentication(
         192  +
        &PartitionedConnectionPool,
         193  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         194  +
    )
         195  +
    .await;
         196  +
}
         197  +
         198  +
#[cfg(all(feature = "wire-mock", feature = "s2n-tls"))]
         199  +
#[tokio::test]
         200  +
async fn test_h2_forward_proxy_auth_with_s2n_tls_and_hyper_util_legacy_pool() {
         201  +
    h2_forward_proxy_applies_configured_authentication(&HyperUtilLegacyPool, tls::Provider::S2nTls)
         202  +
        .await;
         203  +
}
         204  +
         205  +
#[cfg(all(feature = "wire-mock", feature = "s2n-tls"))]
         206  +
#[tokio::test]
         207  +
async fn test_h2_forward_proxy_auth_with_s2n_tls_and_partitioned_connection_pool() {
         208  +
    h2_forward_proxy_applies_configured_authentication(
         209  +
        &PartitionedConnectionPool,
         210  +
        tls::Provider::S2nTls,
         211  +
    )
         212  +
    .await;
         213  +
}
         214  +
         215  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         216  +
async fn https_proxy_dns_failure_remains_retryable_io(
         217  +
    backend: &dyn HttpsClientBackend,
         218  +
    provider: tls::Provider,
         219  +
) {
         220  +
    let client = https_client(
         221  +
        backend,
         222  +
        BackendConfig {
         223  +
            proxy_config: Some(
         224  +
                ProxyConfig::all("http://proxy.invalid:8080").expect("valid proxy URI"),
         225  +
            ),
         226  +
            dns_resolver: Some(SharedDnsResolver::new(FailingDns)),
         227  +
            ..Default::default()
         228  +
        },
         229  +
        provider,
         230  +
        tls::TlsContext::default(),
         231  +
    );
         232  +
         233  +
    let error = test_client::send_request(
         234  +
        &client.connector,
         235  +
        HttpRequest::get("https://origin.test/protected").expect("valid request"),
         236  +
    )
         237  +
    .await
         238  +
    .expect_err("proxy DNS failure must fail the request");
         239  +
    assert!(
         240  +
        error.is_io(),
         241  +
        "HTTPS proxy DNS failure should remain retryable I/O, got {error:?}"
         242  +
    );
         243  +
}
         244  +
         245  +
#[cfg(feature = "rustls-ring")]
         246  +
#[tokio::test]
         247  +
async fn test_https_proxy_dns_failure_with_rustls_and_hyper_util_legacy_pool() {
         248  +
    https_proxy_dns_failure_remains_retryable_io(
         249  +
        &HyperUtilLegacyPool,
         250  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         251  +
    )
         252  +
    .await;
         253  +
}
         254  +
         255  +
#[cfg(feature = "rustls-ring")]
         256  +
#[tokio::test]
         257  +
async fn test_https_proxy_dns_failure_with_rustls_and_partitioned_connection_pool() {
         258  +
    https_proxy_dns_failure_remains_retryable_io(
         259  +
        &PartitionedConnectionPool,
         260  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         261  +
    )
         262  +
    .await;
         263  +
}
         264  +
         265  +
#[cfg(feature = "s2n-tls")]
         266  +
#[tokio::test]
         267  +
async fn test_https_proxy_dns_failure_with_s2n_tls_and_hyper_util_legacy_pool() {
         268  +
    https_proxy_dns_failure_remains_retryable_io(&HyperUtilLegacyPool, tls::Provider::S2nTls).await;
         269  +
}
         270  +
         271  +
#[cfg(feature = "s2n-tls")]
         272  +
#[tokio::test]
         273  +
async fn test_https_proxy_dns_failure_with_s2n_tls_and_partitioned_connection_pool() {
         274  +
    https_proxy_dns_failure_remains_retryable_io(&PartitionedConnectionPool, tls::Provider::S2nTls)
         275  +
        .await;
  209    276   
}
  210    277   
  211         -
/// Utility for running tests with specific environment variables
  212    278   
#[allow(clippy::await_holding_lock)]
  213    279   
async fn with_env_vars<F, Fut, R>(vars: &[(&str, &str)], test: F) -> R
  214    280   
where
  215    281   
    F: FnOnce() -> Fut,
  216         -
    Fut: std::future::Future<Output = R>,
         282  +
    Fut: Future<Output = R>,
  217    283   
{
  218         -
    // Use a static mutex to serialize environment variable tests
  219    284   
    static ENV_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
  220         -
    let _guard = ENV_MUTEX.lock().unwrap();
  221         -
  222         -
    // Save original environment
  223         -
    let original_vars: Vec<_> = vars
         285  +
    let _guard = ENV_MUTEX.lock().expect("environment lock is not poisoned");
         286  +
    let previous: Vec<_> = vars
  224    287   
        .iter()
  225         -
        .map(|(key, _)| (*key, std::env::var(key)))
         288  +
        .map(|(name, _)| (*name, std::env::var(name)))
  226    289   
        .collect();
  227    290   
  228         -
    // Set test environment variables
  229         -
    for (key, value) in vars {
  230         -
        std::env::set_var(key, value);
         291  +
    for (name, value) in vars {
         292  +
        std::env::set_var(name, value);
  231    293   
    }
  232         -
  233         -
    // Run the test
  234    294   
    let result = test().await;
  235         -
  236         -
    // Restore original environment
  237         -
    for (key, original_value) in original_vars {
  238         -
        match original_value {
  239         -
            Ok(val) => std::env::set_var(key, val),
  240         -
            Err(_) => std::env::remove_var(key),
         295  +
    for (name, value) in previous {
         296  +
        match value {
         297  +
            Ok(value) => std::env::set_var(name, value),
         298  +
            Err(_) => std::env::remove_var(name),
  241    299   
        }
  242    300   
    }
  243         -
  244    301   
    result
  245    302   
}
  246    303   
  247         -
/// Helper function to make HTTP requests through a proxy-configured connector
  248         -
async fn make_http_request_through_proxy(
  249         -
    proxy_config: ProxyConfig,
  250         -
    target_url: &str,
  251         -
) -> Result<(StatusCode, String), Box<dyn std::error::Error + Send + Sync>> {
  252         -
    make_http_request_through_proxy_with_pool_timeout(
  253         -
        proxy_config,
  254         -
        Some(Duration::from_secs(90)),
  255         -
        target_url,
  256         -
    )
  257         -
    .await
  258         -
    .map(|(status, res, _client)| (status, res))
  259         -
}
  260         -
  261         -
/// Helper function to make HTTP requests through a proxy-configured connector
  262         -
async fn make_http_request_through_proxy_with_pool_timeout(
  263         -
    proxy_config: ProxyConfig,
  264         -
    pool_idle_timeout: Option<Duration>,
  265         -
    target_url: &str,
  266         -
) -> Result<(StatusCode, String, SharedHttpConnector), Box<dyn std::error::Error + Send + Sync>> {
  267         -
    // Create an HttpClient using http_client_fn with proxy-configured connector
  268         -
    let http_client = http_client_fn(move |settings, _components| {
  269         -
        let connector = Connector::builder()
  270         -
            .proxy_config(proxy_config.clone())
  271         -
            .pool_idle_timeout(pool_idle_timeout)
  272         -
            .connector_settings(settings.clone())
  273         -
            .build_http();
  274         -
  275         -
        aws_smithy_runtime_api::client::http::SharedHttpConnector::new(connector)
  276         -
    });
         304  +
async fn http_forward_proxy_uses_absolute_form(backend: &dyn HttpClientBackend) {
         305  +
    let proxy = MockHttpServer::new(|request| {
         306  +
        assert_eq!("GET", request.method);
         307  +
        assert_eq!("http://api.example.com/v1/data", request.uri);
         308  +
        assert_eq!(
         309  +
            Some(&"api.example.com".to_string()),
         310  +
            request.headers.get("host")
         311  +
        );
         312  +
        Response::builder()
         313  +
            .status(StatusCode::OK)
         314  +
            .body("proxied".to_string())
         315  +
            .expect("valid response")
         316  +
    })
         317  +
    .await;
         318  +
    let config = ProxyConfig::http(format!("http://{}", proxy.addr())).expect("valid proxy");
         319  +
    let client = http_client(backend, proxy_backend_config(config));
  277    320   
  278         -
    // Set up runtime components (following smoke_test_client pattern)
  279         -
    let connector_settings = HttpConnectorSettings::builder().build();
  280         -
    let runtime_components = RuntimeComponentsBuilder::for_tests()
  281         -
        .with_time_source(Some(SystemTimeSource::new()))
  282         -
        .build()
  283         -
        .unwrap();
         321  +
    assert_eq!(
         322  +
        (StatusCode::OK, "proxied".to_string()),
         323  +
        get(&client, "http://api.example.com/v1/data")
         324  +
            .await
         325  +
            .expect("proxy request succeeds")
         326  +
    );
         327  +
    assert_eq!(1, proxy.requests().len());
         328  +
}
  284    329   
  285         -
    // Get the HTTP connector from the client
  286         -
    let http_connector = http_client.http_connector(&connector_settings, &runtime_components);
         330  +
#[tokio::test]
         331  +
async fn test_http_forward_proxy_uses_absolute_form_with_hyper_util_legacy_pool() {
         332  +
    http_forward_proxy_uses_absolute_form(&HyperUtilLegacyPool).await;
         333  +
}
  287    334   
  288         -
    // Create and make the HTTP request
  289         -
    let request = HttpRequest::get(target_url)
  290         -
        .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
         335  +
#[tokio::test]
         336  +
async fn test_http_forward_proxy_uses_absolute_form_with_partitioned_connection_pool() {
         337  +
    http_forward_proxy_uses_absolute_form(&PartitionedConnectionPool).await;
         338  +
}
         339  +
         340  +
#[cfg(any(
         341  +
    feature = "rustls-aws-lc",
         342  +
    feature = "rustls-aws-lc-fips",
         343  +
    feature = "rustls-ring",
         344  +
    feature = "s2n-tls"
         345  +
))]
         346  +
async fn custom_dns_resolver_is_used_for_proxy_connections(backend: &dyn HttpClientBackend) {
         347  +
    const PROXY_HOST: &str = "proxy.test";
         348  +
         349  +
    let proxy = MockHttpServer::with_response(StatusCode::OK, "proxied through custom DNS").await;
         350  +
    let resolver = proxy.dns_resolver(PROXY_HOST);
         351  +
    let proxy_config = ProxyConfig::http(format!("http://{PROXY_HOST}:{}", proxy.addr().port()))
         352  +
        .expect("valid proxy URI");
         353  +
    let client = http_client(
         354  +
        backend,
         355  +
        BackendConfig {
         356  +
            dns_resolver: Some(SharedDnsResolver::new(resolver.clone())),
         357  +
            ..proxy_backend_config(proxy_config)
         358  +
        },
         359  +
    );
  291    360   
  292         -
    let response = http_connector.call(request).await?;
         361  +
    assert_eq!(
         362  +
        (StatusCode::OK, "proxied through custom DNS".to_string()),
         363  +
        get(&client, "http://origin.test/custom-dns-proxy")
         364  +
            .await
         365  +
            .expect("proxy request succeeds")
         366  +
    );
         367  +
    assert_eq!(vec![PROXY_HOST.to_string()], resolver.lookups());
         368  +
    assert_eq!(
         369  +
        "http://origin.test/custom-dns-proxy",
         370  +
        proxy.requests()[0].uri
         371  +
    );
         372  +
}
  293    373   
  294         -
    // Extract status and body
  295         -
    let status = response.status();
  296         -
    let body_bytes = response.into_body().collect().await?.to_bytes();
  297         -
    let body_string = String::from_utf8(body_bytes.to_vec())?;
         374  +
#[cfg(any(
         375  +
    feature = "rustls-aws-lc",
         376  +
    feature = "rustls-aws-lc-fips",
         377  +
    feature = "rustls-ring",
         378  +
    feature = "s2n-tls"
         379  +
))]
         380  +
#[tokio::test]
         381  +
async fn test_custom_dns_resolver_is_used_for_proxy_connections_with_hyper_util_legacy_pool() {
         382  +
    custom_dns_resolver_is_used_for_proxy_connections(&HyperUtilLegacyPool).await;
         383  +
}
  298    384   
  299         -
    Ok((status.into(), body_string, http_connector))
         385  +
#[cfg(any(
         386  +
    feature = "rustls-aws-lc",
         387  +
    feature = "rustls-aws-lc-fips",
         388  +
    feature = "rustls-ring",
         389  +
    feature = "s2n-tls"
         390  +
))]
         391  +
#[tokio::test]
         392  +
async fn test_custom_dns_resolver_is_used_for_proxy_connections_with_partitioned_connection_pool() {
         393  +
    custom_dns_resolver_is_used_for_proxy_connections(&PartitionedConnectionPool).await;
  300    394   
}
  301    395   
  302         -
// test the pool idle timeout. The test is in this file because it has a convenient
  303         -
// infrastructure for making a server that answers smithy requests.
  304         -
#[tokio::test(start_paused = false)]
  305         -
// can't set start_paused due to <https://github.com/hyperium/hyper/issues/3950>
  306         -
async fn test_http_proxy_connection_pool_timeout() {
  307         -
    const TIMEOUT: Duration = Duration::from_secs(10);
         396  +
async fn configured_proxy_authentication_is_applied(backend: &dyn HttpClientBackend) {
         397  +
    let proxy = MockHttpServer::with_auth_validation("testuser", "testpass").await;
         398  +
    let config = ProxyConfig::http(format!("http://{}", proxy.addr()))
         399  +
        .expect("valid proxy")
         400  +
        .with_basic_auth("testuser", "testpass");
         401  +
    let client = http_client(backend, proxy_backend_config(config));
  308    402   
  309         -
    // Create a mock proxy server that validates the request was routed through it
  310         -
    let mock_proxy = MockProxyServer::new(|req| {
  311         -
        // Validate that this looks like a proxy request
  312         -
        assert_eq!(req.method, "GET");
  313         -
        // For HTTP proxy, the URI should be the full target URL
  314         -
        assert_eq!(req.uri, "http://aws.amazon.com/api/data");
         403  +
    assert_eq!(
         404  +
        (StatusCode::OK, "authenticated".to_string()),
         405  +
        get(&client, "http://service.test/protected")
         406  +
            .await
         407  +
            .expect("authenticated proxy request succeeds")
         408  +
    );
         409  +
    assert_eq!(
         410  +
        Some(&basic_authorization("testuser", "testpass")),
         411  +
        proxy.requests()[0].headers.get("proxy-authorization")
         412  +
    );
         413  +
}
  315    414   
  316         -
        // Return a successful response that we can identify
  317         -
        Response::builder()
  318         -
            .status(StatusCode::OK)
  319         -
            .body("proxied response from mock server".to_string())
  320         -
            .unwrap()
  321         -
    })
  322         -
    .await;
  323         -
    // make sure that conn_count for an empty proxy is 0
  324         -
    assert_eq!(mock_proxy.conn_count(), 0);
  325         -
    tracing::info!("Start!");
  326         -
  327         -
    // Configure connector with HTTP proxy
  328         -
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr())).unwrap();
  329         -
  330         -
    // Make an HTTP request through the proxy - use safe domain
  331         -
    let target_url = "http://aws.amazon.com/api/data";
  332         -
    let start = tokio::time::Instant::now();
  333         -
    let result =
  334         -
        make_http_request_through_proxy_with_pool_timeout(proxy_config, Some(TIMEOUT), target_url)
  335         -
            .await;
  336         -
    // hold _connector to avoid the timer being dropped
  337         -
    let (status, body, _connector) = result.expect("HTTP request through proxy should succeed");
  338         -
    assert_eq!(status, StatusCode::OK);
  339         -
    assert_eq!(body, "proxied response from mock server");
  340         -
  341         -
    // Verify the mock proxy received the expected request
  342         -
    let requests = mock_proxy.requests();
  343         -
    assert_eq!(requests.len(), 1);
  344         -
    assert_eq!(requests[0].method, "GET");
  345         -
    assert_eq!(requests[0].uri, target_url);
  346         -
  347         -
    // after making a request, conn count is 1
  348         -
    assert_eq!(mock_proxy.conn_count(), 1);
  349         -
  350         -
    // sleep 1 second below the idle timeout, conn count is still 1
  351         -
    tokio::time::sleep_until(start + TIMEOUT - Duration::from_secs(1)).await;
  352         -
    assert_eq!(mock_proxy.conn_count(), 1);
  353         -
  354         -
    // sleep 2 more seconds, the connection should be disconnected, conn count should be 0
  355         -
    tokio::time::sleep(Duration::from_secs(3)).await;
  356         -
    assert_eq!(mock_proxy.conn_count(), 0);
         415  +
#[tokio::test]
         416  +
async fn test_configured_proxy_authentication_with_hyper_util_legacy_pool() {
         417  +
    configured_proxy_authentication_is_applied(&HyperUtilLegacyPool).await;
  357    418   
}
  358    419   
  359    420   
#[tokio::test]
  360         -
async fn test_http_proxy_basic_request() {
  361         -
    // Create a mock proxy server that validates the request was routed through it
  362         -
    let mock_proxy = MockProxyServer::new(|req| {
  363         -
        // Validate that this looks like a proxy request
  364         -
        assert_eq!(req.method, "GET");
  365         -
        // For HTTP proxy, the URI should be the full target URL
  366         -
        assert_eq!(req.uri, "http://aws.amazon.com/api/data");
  367         -
  368         -
        // Return a successful response that we can identify
         421  +
async fn test_configured_proxy_authentication_with_partitioned_connection_pool() {
         422  +
    configured_proxy_authentication_is_applied(&PartitionedConnectionPool).await;
         423  +
}
         424  +
         425  +
async fn caller_proxy_authorization_is_preserved(backend: &dyn HttpClientBackend) {
         426  +
    let caller_authorization = basic_authorization("caller", "credentials");
         427  +
    let expected = caller_authorization.clone();
         428  +
    let proxy = MockHttpServer::new(move |request| {
         429  +
        assert_eq!(Some(&expected), request.headers.get("proxy-authorization"));
  369    430   
        Response::builder()
  370    431   
            .status(StatusCode::OK)
  371         -
            .body("proxied response from mock server".to_string())
  372         -
            .unwrap()
         432  +
            .body("caller authorization".to_string())
         433  +
            .expect("valid response")
  373    434   
    })
  374    435   
    .await;
         436  +
    let config = ProxyConfig::http(format!("http://{}", proxy.addr()))
         437  +
        .expect("valid proxy")
         438  +
        .with_basic_auth("configured", "credentials");
         439  +
    let client = http_client(backend, proxy_backend_config(config));
         440  +
    let mut request = HttpRequest::get("http://service.test/caller-auth").expect("valid request");
         441  +
    request
         442  +
        .headers_mut()
         443  +
        .insert("proxy-authorization", caller_authorization);
  375    444   
  376         -
    // Configure connector with HTTP proxy
  377         -
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr())).unwrap();
  378         -
  379         -
    // Make an HTTP request through the proxy - use safe domain
  380         -
    let target_url = "http://aws.amazon.com/api/data";
  381         -
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
  382         -
  383         -
    let (status, body) = result.expect("HTTP request through proxy should succeed");
  384         -
  385         -
    assert_eq!(status, StatusCode::OK);
  386         -
    assert_eq!(body, "proxied response from mock server");
  387         -
  388         -
    // Verify the mock proxy received the expected request
  389         -
    let requests = mock_proxy.requests();
  390         -
    assert_eq!(requests.len(), 1);
  391         -
    assert_eq!(requests[0].method, "GET");
  392         -
    assert_eq!(requests[0].uri, target_url);
         445  +
    assert_eq!(
         446  +
        (StatusCode::OK, "caller authorization".to_string()),
         447  +
        send_request(&client, request)
         448  +
            .await
         449  +
            .expect("caller authorization request succeeds")
         450  +
    );
  393    451   
}
  394    452   
  395    453   
#[tokio::test]
  396         -
async fn test_proxy_authentication() {
  397         -
    // Create a mock proxy that requires authentication
  398         -
    let mock_proxy = MockProxyServer::with_auth_validation("testuser", "testpass").await;
  399         -
  400         -
    // Configure connector with authenticated proxy
  401         -
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr()))
  402         -
        .unwrap()
  403         -
        .with_basic_auth("testuser", "testpass");
  404         -
  405         -
    // Make request through authenticated proxy - use safe domain
  406         -
    let target_url = "http://aws.amazon.com/protected/resource";
  407         -
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
  408         -
  409         -
    let (status, body) = result.expect("Authenticated proxy request should succeed");
         454  +
async fn test_caller_proxy_authorization_is_preserved_with_hyper_util_legacy_pool() {
         455  +
    caller_proxy_authorization_is_preserved(&HyperUtilLegacyPool).await;
         456  +
}
  410    457   
  411         -
    assert_eq!(status, StatusCode::OK);
  412         -
    assert_eq!(body, "authenticated");
         458  +
#[tokio::test]
         459  +
async fn test_caller_proxy_authorization_is_preserved_with_partitioned_connection_pool() {
         460  +
    caller_proxy_authorization_is_preserved(&PartitionedConnectionPool).await;
         461  +
}
  413    462   
  414         -
    // Verify the proxy received the request with correct auth
  415         -
    let requests = mock_proxy.requests();
  416         -
    assert_eq!(requests.len(), 1);
         463  +
async fn proxy_url_authentication_is_applied(backend: &dyn HttpClientBackend) {
         464  +
    let proxy = MockHttpServer::with_auth_validation("urluser", "urlpass").await;
         465  +
    let config =
         466  +
        ProxyConfig::http(format!("http://urluser:urlpass@{}", proxy.addr())).expect("valid proxy");
         467  +
    let client = http_client(backend, proxy_backend_config(config));
  417    468   
  418         -
    let expected_auth = format!(
  419         -
        "Basic {}",
  420         -
        base64::prelude::BASE64_STANDARD.encode("testuser:testpass")
         469  +
    assert_eq!(
         470  +
        StatusCode::OK,
         471  +
        get(&client, "http://service.test/url-auth")
         472  +
            .await
         473  +
            .expect("URL-authenticated request succeeds")
         474  +
            .0
  421    475   
    );
  422    476   
    assert_eq!(
  423         -
        requests[0].headers.get("proxy-authorization"),
  424         -
        Some(&expected_auth)
         477  +
        Some(&basic_authorization("urluser", "urlpass")),
         478  +
        proxy.requests()[0].headers.get("proxy-authorization")
  425    479   
    );
  426    480   
}
  427    481   
  428         -
/// Tests URL-embedded proxy authentication (http://user:pass@proxy.com format)
  429         -
/// Verifies that credentials in the proxy URL are properly extracted and used
  430    482   
#[tokio::test]
  431         -
async fn test_proxy_url_embedded_auth() {
  432         -
    let mock_proxy = MockProxyServer::with_auth_validation("urluser", "urlpass").await;
  433         -
  434         -
    // Configure proxy with credentials embedded in URL
  435         -
    let proxy_url = format!("http://urluser:urlpass@{}", mock_proxy.addr());
  436         -
    let proxy_config = ProxyConfig::http(proxy_url).unwrap();
  437         -
  438         -
    // Make request through proxy with URL-embedded auth
  439         -
    let target_url = "http://aws.amazon.com/api/test";
  440         -
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
         483  +
async fn test_proxy_url_authentication_with_hyper_util_legacy_pool() {
         484  +
    proxy_url_authentication_is_applied(&HyperUtilLegacyPool).await;
         485  +
}
  441    486   
  442         -
    let (status, body) = result.expect("URL-embedded auth proxy request should succeed");
  443         -
    assert_eq!(status, StatusCode::OK);
  444         -
    assert_eq!(body, "authenticated");
         487  +
#[tokio::test]
         488  +
async fn test_proxy_url_authentication_with_partitioned_connection_pool() {
         489  +
    proxy_url_authentication_is_applied(&PartitionedConnectionPool).await;
         490  +
}
  445    491   
  446         -
    // Verify the proxy received the request with correct auth
  447         -
    let requests = mock_proxy.requests();
  448         -
    assert_eq!(requests.len(), 1);
         492  +
async fn proxy_url_authentication_precedes_configured_authentication(
         493  +
    backend: &dyn HttpClientBackend,
         494  +
) {
         495  +
    let proxy = MockHttpServer::with_auth_validation("urluser", "urlpass").await;
         496  +
    let config = ProxyConfig::http(format!("http://urluser:urlpass@{}", proxy.addr()))
         497  +
        .expect("valid proxy")
         498  +
        .with_basic_auth("configured", "credentials");
         499  +
    let client = http_client(backend, proxy_backend_config(config));
  449    500   
  450         -
    let expected_auth = format!(
  451         -
        "Basic {}",
  452         -
        base64::prelude::BASE64_STANDARD.encode("urluser:urlpass")
         501  +
    assert_eq!(
         502  +
        StatusCode::OK,
         503  +
        get(&client, "http://service.test/auth-precedence")
         504  +
            .await
         505  +
            .expect("proxy request succeeds")
         506  +
            .0
  453    507   
    );
  454    508   
    assert_eq!(
  455         -
        requests[0].headers.get("proxy-authorization"),
  456         -
        Some(&expected_auth)
         509  +
        Some(&basic_authorization("urluser", "urlpass")),
         510  +
        proxy.requests()[0].headers.get("proxy-authorization")
  457    511   
    );
  458    512   
}
  459    513   
  460         -
/// Tests authentication precedence: URL-embedded credentials should take precedence over programmatic auth
  461         -
/// Verifies that when both URL auth and with_basic_auth() are provided, URL auth wins
  462    514   
#[tokio::test]
  463         -
async fn test_proxy_auth_precedence() {
  464         -
    let mock_proxy = MockProxyServer::with_auth_validation("urluser", "urlpass").await;
  465         -
  466         -
    // Configure proxy with URL-embedded auth AND programmatic auth
  467         -
    // URL auth should take precedence
  468         -
    let proxy_url = format!("http://urluser:urlpass@{}", mock_proxy.addr());
  469         -
    let proxy_config = ProxyConfig::http(proxy_url)
  470         -
        .unwrap()
  471         -
        .with_basic_auth("programmatic", "auth"); // This should be ignored
  472         -
  473         -
    // Make request - should use URL-embedded auth, not programmatic auth
  474         -
    let target_url = "http://aws.amazon.com/precedence/test";
  475         -
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
  476         -
  477         -
    let (status, body) = result.expect("Auth precedence test should succeed");
  478         -
    assert_eq!(status, StatusCode::OK);
  479         -
    assert_eq!(body, "authenticated");
  480         -
  481         -
    // Verify the proxy received the request with URL-embedded auth (not programmatic)
  482         -
    let requests = mock_proxy.requests();
  483         -
    assert_eq!(requests.len(), 1);
  484         -
  485         -
    let expected_auth = format!(
  486         -
        "Basic {}",
  487         -
        base64::prelude::BASE64_STANDARD.encode("urluser:urlpass")
  488         -
    );
  489         -
    assert_eq!(
  490         -
        requests[0].headers.get("proxy-authorization"),
  491         -
        Some(&expected_auth)
  492         -
    );
         515  +
async fn test_proxy_url_authentication_precedence_with_hyper_util_legacy_pool() {
         516  +
    proxy_url_authentication_precedes_configured_authentication(&HyperUtilLegacyPool).await;
  493    517   
}
  494    518   
  495    519   
#[tokio::test]
  496         -
async fn test_proxy_from_environment_variables() {
  497         -
    let mock_proxy = MockProxyServer::with_response(StatusCode::OK, "env proxy response").await;
         520  +
async fn test_proxy_url_authentication_precedence_with_partitioned_connection_pool() {
         521  +
    proxy_url_authentication_precedes_configured_authentication(&PartitionedConnectionPool).await;
         522  +
}
  498    523   
         524  +
async fn environment_proxy_is_used(backend: &dyn HttpClientBackend) {
         525  +
    let proxy = MockHttpServer::with_response(StatusCode::OK, "environment proxy").await;
         526  +
    let proxy_uri = format!("http://{}", proxy.addr());
  499    527   
    with_env_vars(
  500    528   
        &[
  501         -
            ("HTTP_PROXY", &format!("http://{}", mock_proxy.addr())),
         529  +
            ("HTTP_PROXY", &proxy_uri),
  502    530   
            ("NO_PROXY", "localhost,127.0.0.1"),
  503    531   
        ],
  504    532   
        || async {
  505         -
            // Create connector with environment-based proxy config
  506         -
            let proxy_config = ProxyConfig::from_env();
  507         -
  508         -
            // Make request through environment-configured proxy
  509         -
            let target_url = "http://aws.amazon.com/v1/data";
  510         -
            let result = make_http_request_through_proxy(proxy_config, target_url).await;
  511         -
  512         -
            let (status, body) = result.expect("Environment proxy request should succeed");
  513         -
  514         -
            assert_eq!(status, StatusCode::OK);
  515         -
            assert_eq!(body, "env proxy response");
  516         -
  517         -
            // Verify the proxy received the request
  518         -
            let requests = mock_proxy.requests();
  519         -
            assert_eq!(requests.len(), 1);
  520         -
            assert_eq!(requests[0].uri, target_url);
         533  +
            let client = http_client(backend, proxy_backend_config(ProxyConfig::from_env()));
         534  +
            assert_eq!(
         535  +
                (StatusCode::OK, "environment proxy".to_string()),
         536  +
                get(&client, "http://service.test/environment")
         537  +
                    .await
         538  +
                    .expect("environment proxy request succeeds")
         539  +
            );
  521    540   
        },
  522    541   
    )
  523    542   
    .await;
         543  +
    assert_eq!("http://service.test/environment", proxy.requests()[0].uri);
  524    544   
}
  525    545   
  526         -
/// Tests that NO_PROXY bypass rules work correctly
  527         -
/// Verifies that requests to bypassed hosts do not go through the proxy
  528    546   
#[tokio::test]
  529         -
async fn test_no_proxy_bypass_rules() {
  530         -
    let mock_proxy = MockProxyServer::with_response(StatusCode::OK, "should not reach here").await;
  531         -
  532         -
    // Create a second mock server that will act as the "direct" target
  533         -
    let direct_server = MockProxyServer::with_response(StatusCode::OK, "direct connection").await;
  534         -
  535         -
    // Configure proxy with NO_PROXY rules that include the direct server's address
  536         -
    // Use just the IP address for the NO_PROXY rule
  537         -
    let direct_ip = "127.0.0.1";
  538         -
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr()))
  539         -
        .unwrap()
  540         -
        .no_proxy(direct_ip);
  541         -
  542         -
    // Make request to the direct server (should bypass proxy due to NO_PROXY rule)
  543         -
    let result = make_http_request_through_proxy(
  544         -
        proxy_config,
  545         -
        &format!("http://{}/test", direct_server.addr()),
  546         -
    )
  547         -
    .await;
         547  +
async fn test_environment_proxy_with_hyper_util_legacy_pool() {
         548  +
    environment_proxy_is_used(&HyperUtilLegacyPool).await;
         549  +
}
  548    550   
  549         -
    let (status, body) = result.expect("Direct connection should succeed");
  550         -
    assert_eq!(status, StatusCode::OK);
  551         -
    assert_eq!(body, "direct connection");
         551  +
#[tokio::test]
         552  +
async fn test_environment_proxy_with_partitioned_connection_pool() {
         553  +
    environment_proxy_is_used(&PartitionedConnectionPool).await;
         554  +
}
  552    555   
  553         -
    // Verify the mock proxy received no requests (bypassed)
  554         -
    let proxy_requests = mock_proxy.requests();
  555         -
    assert_eq!(
  556         -
        proxy_requests.len(),
  557         -
        0,
  558         -
        "Proxy should not have received any requests due to NO_PROXY bypass"
  559         -
    );
         556  +
async fn no_proxy_bypasses_proxy(backend: &dyn HttpClientBackend) {
         557  +
    let proxy = MockHttpServer::with_response(StatusCode::OK, "unexpected proxy").await;
         558  +
    let origin = MockHttpServer::with_response(StatusCode::OK, "direct").await;
         559  +
    let config = ProxyConfig::http(format!("http://{}", proxy.addr()))
         560  +
        .expect("valid proxy")
         561  +
        .no_proxy("127.0.0.1");
         562  +
    let client = http_client(backend, proxy_backend_config(config));
  560    563   
  561         -
    // Verify the direct server received the request
  562         -
    let direct_requests = direct_server.requests();
  563    564   
    assert_eq!(
  564         -
        direct_requests.len(),
  565         -
        1,
  566         -
        "Direct server should have received the request"
         565  +
        (StatusCode::OK, "direct".to_string()),
         566  +
        get(&client, &format!("http://{}/bypass", origin.addr()))
         567  +
            .await
         568  +
            .expect("direct request succeeds")
  567    569   
    );
         570  +
    assert!(proxy.requests().is_empty());
         571  +
    assert_eq!("/bypass", origin.requests()[0].uri);
  568    572   
}
  569    573   
  570         -
/// Tests that disabled proxy configuration results in direct connections
  571         -
/// Verifies that ProxyConfig::disabled() bypasses all proxy logic
  572    574   
#[tokio::test]
  573         -
async fn test_proxy_disabled() {
  574         -
    // Create a direct target server
  575         -
    let direct_server = MockProxyServer::with_response(StatusCode::OK, "direct connection").await;
  576         -
  577         -
    // Create a disabled proxy configuration
  578         -
    let proxy_config = ProxyConfig::disabled();
         575  +
async fn test_no_proxy_bypass_with_hyper_util_legacy_pool() {
         576  +
    no_proxy_bypasses_proxy(&HyperUtilLegacyPool).await;
         577  +
}
  579    578   
  580         -
    // Make request with disabled proxy (should go direct to our mock server)
  581         -
    let result = make_http_request_through_proxy(
  582         -
        proxy_config,
  583         -
        &format!("http://{}/get", direct_server.addr()),
  584         -
    )
  585         -
    .await;
         579  +
#[tokio::test]
         580  +
async fn test_no_proxy_bypass_with_partitioned_connection_pool() {
         581  +
    no_proxy_bypasses_proxy(&PartitionedConnectionPool).await;
         582  +
}
  586    583   
  587         -
    let (status, body) = result.expect("Direct connection should succeed");
  588         -
    assert_eq!(status, StatusCode::OK);
  589         -
    assert_eq!(body, "direct connection");
         584  +
async fn disabled_proxy_uses_direct_origin_form(backend: &dyn HttpClientBackend) {
         585  +
    let origin = MockHttpServer::with_response(StatusCode::OK, "direct").await;
         586  +
    let client = http_client(backend, proxy_backend_config(ProxyConfig::disabled()));
  590    587   
  591         -
    // Verify the direct server received the request
  592         -
    let requests = direct_server.requests();
  593    588   
    assert_eq!(
  594         -
        requests.len(),
  595         -
        1,
  596         -
        "Direct server should have received the request"
  597         -
    );
  598         -
    assert_eq!(requests[0].method, "GET");
  599         -
    // For direct connections, the URI might be just the path part
  600         -
    assert!(
  601         -
        requests[0].uri == format!("http://{}/get", direct_server.addr())
  602         -
            || requests[0].uri == "/get",
  603         -
        "URI should be either full URL or path, got: {}",
  604         -
        requests[0].uri
         589  +
        (StatusCode::OK, "direct".to_string()),
         590  +
        get(&client, &format!("http://{}/direct", origin.addr()))
         591  +
            .await
         592  +
            .expect("direct request succeeds")
  605    593   
    );
         594  +
    assert_eq!("/direct", origin.requests()[0].uri);
  606    595   
}
  607    596   
  608         -
/// Tests HTTPS-only proxy configuration
  609         -
/// Verifies that HTTP requests bypass HTTPS-only proxies
  610    597   
#[tokio::test]
  611         -
async fn test_https_proxy_configuration() {
  612         -
    let mock_proxy = MockProxyServer::with_response(StatusCode::OK, "https proxy response").await;
  613         -
  614         -
    // Create a direct target server for HTTP requests
  615         -
    let direct_server =
  616         -
        MockProxyServer::with_response(StatusCode::OK, "direct http connection").await;
  617         -
  618         -
    // Configure HTTPS-only proxy
  619         -
    let proxy_config = ProxyConfig::https(format!("http://{}", mock_proxy.addr())).unwrap();
         598  +
async fn test_disabled_proxy_uses_direct_origin_form_with_hyper_util_legacy_pool() {
         599  +
    disabled_proxy_uses_direct_origin_form(&HyperUtilLegacyPool).await;
         600  +
}
  620    601   
  621         -
    // Test: HTTP request should NOT go through HTTPS-only proxy, should go direct
  622         -
    let target_url = format!("http://{}/api", direct_server.addr());
  623         -
    let result = make_http_request_through_proxy(proxy_config.clone(), &target_url).await;
         602  +
#[tokio::test]
         603  +
async fn test_disabled_proxy_uses_direct_origin_form_with_partitioned_connection_pool() {
         604  +
    disabled_proxy_uses_direct_origin_form(&PartitionedConnectionPool).await;
         605  +
}
  624    606   
  625         -
    // The HTTP request should succeed by going directly to our mock server
  626         -
    let (status, body) = result.expect("HTTP request should succeed via direct connection");
  627         -
    assert_eq!(status, StatusCode::OK);
  628         -
    assert_eq!(body, "direct http connection");
         607  +
async fn https_only_proxy_bypasses_http(backend: &dyn HttpClientBackend) {
         608  +
    let proxy = MockHttpServer::with_response(StatusCode::OK, "unexpected proxy").await;
         609  +
    let origin = MockHttpServer::with_response(StatusCode::OK, "direct HTTP").await;
         610  +
    let config =
         611  +
        ProxyConfig::https(format!("http://{}", proxy.addr())).expect("valid proxy configuration");
         612  +
    let client = http_client(backend, proxy_backend_config(config));
  629    613   
  630         -
    // Verify the HTTPS-only proxy received no requests
  631         -
    let proxy_requests = mock_proxy.requests();
  632    614   
    assert_eq!(
  633         -
        proxy_requests.len(),
  634         -
        0,
  635         -
        "HTTP request should not go through HTTPS-only proxy"
  636         -
    );
  637         -
  638         -
    // Verify the direct server received the request
  639         -
    let direct_requests = direct_server.requests();
  640         -
    assert_eq!(
  641         -
        direct_requests.len(),
  642         -
        1,
  643         -
        "Direct server should have received the HTTP request"
         615  +
        (StatusCode::OK, "direct HTTP".to_string()),
         616  +
        get(&client, &format!("http://{}/http-only", origin.addr()))
         617  +
            .await
         618  +
            .expect("direct HTTP request succeeds")
  644    619   
    );
         620  +
    assert!(proxy.requests().is_empty());
  645    621   
}
  646    622   
  647         -
/// Tests all-traffic proxy configuration
  648         -
/// Verifies that both HTTP and HTTPS requests go through all-traffic proxies
  649    623   
#[tokio::test]
  650         -
async fn test_all_traffic_proxy() {
  651         -
    let mock_proxy = MockProxyServer::with_response(StatusCode::OK, "all traffic proxy").await;
  652         -
  653         -
    // Configure proxy for all traffic
  654         -
    let proxy_config = ProxyConfig::all(format!("http://{}", mock_proxy.addr())).unwrap();
         624  +
async fn test_https_only_proxy_bypasses_http_with_hyper_util_legacy_pool() {
         625  +
    https_only_proxy_bypasses_http(&HyperUtilLegacyPool).await;
         626  +
}
  655    627   
  656         -
    // HTTP request should go through the proxy
  657         -
    let target_url = "http://aws.amazon.com/api/endpoint";
  658         -
    let result = make_http_request_through_proxy(proxy_config.clone(), target_url).await;
         628  +
#[tokio::test]
         629  +
async fn test_https_only_proxy_bypasses_http_with_partitioned_connection_pool() {
         630  +
    https_only_proxy_bypasses_http(&PartitionedConnectionPool).await;
         631  +
}
  659    632   
  660         -
    let (status, body) = result.expect("HTTP request through all-traffic proxy should succeed");
  661         -
    assert_eq!(status, StatusCode::OK);
  662         -
    assert_eq!(body, "all traffic proxy");
         633  +
async fn all_traffic_proxy_forwards_http(backend: &dyn HttpClientBackend) {
         634  +
    let proxy = MockHttpServer::with_response(StatusCode::OK, "all traffic").await;
         635  +
    let config = ProxyConfig::all(format!("http://{}", proxy.addr())).expect("valid proxy");
         636  +
    let client = http_client(backend, proxy_backend_config(config));
  663    637   
  664         -
    // Verify the proxy received the HTTP request
  665         -
    let requests = mock_proxy.requests();
  666    638   
    assert_eq!(
  667         -
        requests.len(),
  668         -
        1,
  669         -
        "Proxy should have received exactly one request"
         639  +
        (StatusCode::OK, "all traffic".to_string()),
         640  +
        get(&client, "http://service.test/all")
         641  +
            .await
         642  +
            .expect("all-traffic proxy request succeeds")
  670    643   
    );
  671         -
    assert_eq!(requests[0].method, "GET");
  672         -
    assert_eq!(requests[0].uri, target_url);
         644  +
    assert_eq!("http://service.test/all", proxy.requests()[0].uri);
  673    645   
}
  674    646   
  675         -
/// Tests proxy connection failure handling
  676         -
/// Verifies that unreachable proxy servers result in appropriate connection errors
  677    647   
#[tokio::test]
  678         -
async fn test_proxy_connection_failure() {
  679         -
    // Configure proxy pointing to non-existent server
  680         -
    let proxy_config = ProxyConfig::http("http://127.0.0.1:1").unwrap(); // Port 1 should be unavailable
         648  +
async fn test_all_traffic_proxy_forwards_http_with_hyper_util_legacy_pool() {
         649  +
    all_traffic_proxy_forwards_http(&HyperUtilLegacyPool).await;
         650  +
}
  681    651   
  682         -
    // Make request through non-existent proxy - use a safe domain that won't cause issues
  683         -
    let target_url = "http://aws.amazon.com/api/test";
  684         -
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
         652  +
#[tokio::test]
         653  +
async fn test_all_traffic_proxy_forwards_http_with_partitioned_connection_pool() {
         654  +
    all_traffic_proxy_forwards_http(&PartitionedConnectionPool).await;
         655  +
}
  685    656   
  686         -
    // The request should fail with a connection error
         657  +
async fn unreachable_proxy_fails(backend: &dyn HttpClientBackend) {
         658  +
    let config = ProxyConfig::http("http://127.0.0.1:1").expect("valid proxy");
         659  +
    let client = http_client(backend, proxy_backend_config(config));
  687    660   
    assert!(
  688         -
        result.is_err(),
  689         -
        "Request should fail when proxy is unreachable"
         661  +
        get(&client, "http://service.test/unreachable")
         662  +
            .await
         663  +
            .is_err(),
         664  +
        "an unreachable proxy must fail the request"
  690    665   
    );
         666  +
}
  691    667   
  692         -
    let error = result.unwrap_err();
  693         -
    let error_msg = error.to_string().to_lowercase();
  694         -
  695         -
    // Verify it's a connection-related error (not a different kind of error)
  696         -
    assert!(
  697         -
        error_msg.contains("connection")
  698         -
            || error_msg.contains("refused")
  699         -
            || error_msg.contains("unreachable")
  700         -
            || error_msg.contains("timeout")
  701         -
            || error_msg.contains("connect")
  702         -
            || error_msg.contains("io error"), // Include generic IO errors
  703         -
        "Error should be connection-related, got: {}",
  704         -
        error
  705         -
    );
         668  +
#[tokio::test]
         669  +
async fn test_unreachable_proxy_fails_with_hyper_util_legacy_pool() {
         670  +
    unreachable_proxy_fails(&HyperUtilLegacyPool).await;
  706    671   
}
  707    672   
  708         -
/// Tests proxy authentication failure handling
  709         -
/// Verifies that incorrect proxy credentials result in 407 Proxy Authentication Required
  710    673   
#[tokio::test]
  711         -
async fn test_proxy_authentication_failure() {
  712         -
    let mock_proxy = MockProxyServer::with_auth_validation("correct", "password").await;
         674  +
async fn test_unreachable_proxy_fails_with_partitioned_connection_pool() {
         675  +
    unreachable_proxy_fails(&PartitionedConnectionPool).await;
         676  +
}
  713    677   
  714         -
    // Configure proxy with wrong credentials
  715         -
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr()))
  716         -
        .unwrap()
         678  +
async fn incorrect_proxy_authentication_returns_407(backend: &dyn HttpClientBackend) {
         679  +
    let proxy = MockHttpServer::with_auth_validation("correct", "password").await;
         680  +
    let config = ProxyConfig::http(format!("http://{}", proxy.addr()))
         681  +
        .expect("valid proxy")
  717    682   
        .with_basic_auth("wrong", "credentials");
         683  +
    let client = http_client(backend, proxy_backend_config(config));
  718    684   
  719         -
    // Make request with wrong credentials - use safe domain
  720         -
    let target_url = "http://aws.amazon.com/secure/api";
  721         -
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
  722         -
  723         -
    // The request should return 407 Proxy Authentication Required
  724         -
    let (status, _body) = result.expect("Request should complete (even with auth failure)");
  725         -
    assert_eq!(status, StatusCode::PROXY_AUTHENTICATION_REQUIRED);
  726         -
  727         -
    // Verify the proxy received the request (even though auth failed)
  728         -
    let requests = mock_proxy.requests();
  729         -
    assert_eq!(requests.len(), 1, "Proxy should have received the request");
  730         -
  731         -
    // Verify the wrong credentials were sent
  732         -
    let expected_wrong_auth = format!(
  733         -
        "Basic {}",
  734         -
        base64::prelude::BASE64_STANDARD.encode("wrong:credentials")
         685  +
    assert_eq!(
         686  +
        StatusCode::PROXY_AUTHENTICATION_REQUIRED,
         687  +
        get(&client, "http://service.test/denied")
         688  +
            .await
         689  +
            .expect("proxy response is returned")
         690  +
            .0
  735    691   
    );
  736    692   
    assert_eq!(
  737         -
        requests[0].headers.get("proxy-authorization"),
  738         -
        Some(&expected_wrong_auth)
         693  +
        Some(&basic_authorization("wrong", "credentials")),
         694  +
        proxy.requests()[0].headers.get("proxy-authorization")
  739    695   
    );
  740    696   
}
  741    697   
  742         -
/// Tests that ProxyConfig::disabled() overrides environment proxy settings
  743         -
/// Verifies that explicit proxy disabling takes precedence over environment variables
  744    698   
#[tokio::test]
  745         -
async fn test_explicit_proxy_disable_overrides_environment() {
  746         -
    let mock_proxy = MockProxyServer::new(|_req| {
  747         -
        panic!("Request should not reach proxy when explicitly disabled");
  748         -
    })
  749         -
    .await;
  750         -
  751         -
    // Create a direct target server
  752         -
    let direct_server = MockProxyServer::with_response(StatusCode::OK, "direct connection").await;
  753         -
  754         -
    with_env_vars(
  755         -
        &[("HTTP_PROXY", &format!("http://{}", mock_proxy.addr()))],
  756         -
        || async {
  757         -
            // Create connector with explicitly disabled proxy (should override environment)
  758         -
            let proxy_config = ProxyConfig::disabled();
  759         -
  760         -
            // Make request - should go direct despite HTTP_PROXY environment variable
  761         -
            let target_url = format!("http://{}/test", direct_server.addr());
  762         -
            let result = make_http_request_through_proxy(proxy_config, &target_url).await;
  763         -
  764         -
            let (status, body) = result.expect("Direct connection should succeed");
  765         -
            assert_eq!(status, StatusCode::OK);
  766         -
            assert_eq!(body, "direct connection");
  767         -
  768         -
            // Verify the proxy received no requests (disabled)
  769         -
            let proxy_requests = mock_proxy.requests();
  770         -
            assert_eq!(
  771         -
                proxy_requests.len(),
  772         -
                0,
  773         -
                "Proxy should not receive requests when explicitly disabled"
  774         -
            );
  775         -
  776         -
            // Verify the direct server received the request
  777         -
            let direct_requests = direct_server.requests();
  778         -
            assert_eq!(
  779         -
                direct_requests.len(),
  780         -
                1,
  781         -
                "Direct server should have received the request"
  782         -
            );
  783         -
        },
  784         -
    )
  785         -
    .await;
         699  +
async fn test_incorrect_proxy_authentication_returns_407_with_hyper_util_legacy_pool() {
         700  +
    incorrect_proxy_authentication_returns_407(&HyperUtilLegacyPool).await;
  786    701   
}
  787    702   
  788         -
// ================================================================================================
  789         -
// HTTPS/CONNECT Tunneling Tests
  790         -
// ================================================================================================
  791         -
//
  792         -
// These tests are for HTTPS tunneling through HTTP proxies using the CONNECT method.
  793         -
  794         -
/// Helper function to make HTTPS requests through proxy using TLS providers
  795         -
/// This is similar to make_http_request_through_proxy but uses TLS-enabled connectors
  796         -
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
  797         -
async fn make_https_request_through_proxy(
  798         -
    proxy_config: ProxyConfig,
  799         -
    target_url: &str,
  800         -
    tls_provider: tls::Provider,
  801         -
) -> Result<(StatusCode, String), Box<dyn std::error::Error + Send + Sync>> {
  802         -
    let http_client = http_client_fn(move |settings, _components| {
  803         -
        let connector = Connector::builder()
  804         -
            .proxy_config(proxy_config.clone())
  805         -
            .connector_settings(settings.clone())
  806         -
            .tls_provider(tls_provider.clone())
  807         -
            .build();
  808         -
  809         -
        aws_smithy_runtime_api::client::http::SharedHttpConnector::new(connector)
  810         -
    });
  811         -
  812         -
    let connector_settings = HttpConnectorSettings::builder().build();
  813         -
    let runtime_components = RuntimeComponentsBuilder::for_tests()
  814         -
        .with_time_source(Some(SystemTimeSource::new()))
  815         -
        .build()
  816         -
        .unwrap();
  817         -
  818         -
    let http_connector = http_client.http_connector(&connector_settings, &runtime_components);
  819         -
  820         -
    let request = HttpRequest::get(target_url)
  821         -
        .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
  822         -
  823         -
    let response = http_connector.call(request).await?;
  824         -
  825         -
    let status = response.status();
  826         -
    let body_bytes = response.into_body().collect().await?.to_bytes();
  827         -
    let body_string = String::from_utf8(body_bytes.to_vec())?;
  828         -
  829         -
    Ok((status.into(), body_string))
  830         -
}
  831         -
  832         -
/// Generic test function for HTTPS CONNECT with authentication
  833         -
/// Tests that HTTPS requests through HTTP proxy use CONNECT method with proper auth headers
  834         -
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
  835         -
async fn run_https_connect_with_auth_test(tls_provider: tls::Provider, provider_name: &str) {
  836         -
    let mock_proxy = MockProxyServer::new(|req| {
  837         -
        // For HTTPS through HTTP proxy, we should see a CONNECT request
  838         -
        assert_eq!(req.method, "CONNECT");
  839         -
        assert_eq!(req.uri, "secure.aws.amazon.com:443");
  840         -
  841         -
        // Verify authentication header is present
  842         -
        let expected_auth = format!(
  843         -
            "Basic {}",
  844         -
            base64::prelude::BASE64_STANDARD.encode("connectuser:connectpass")
         703  +
#[tokio::test]
         704  +
async fn test_incorrect_proxy_authentication_returns_407_with_partitioned_connection_pool() {
         705  +
    incorrect_proxy_authentication_returns_407(&PartitionedConnectionPool).await;
         706  +
}
         707  +
         708  +
async fn disabled_proxy_overrides_environment(backend: &dyn HttpClientBackend) {
         709  +
    let proxy = MockHttpServer::with_response(StatusCode::OK, "unexpected proxy").await;
         710  +
    let origin = MockHttpServer::with_response(StatusCode::OK, "direct").await;
         711  +
    let proxy_uri = format!("http://{}", proxy.addr());
         712  +
    with_env_vars(&[("HTTP_PROXY", &proxy_uri)], || async {
         713  +
        let client = http_client(backend, proxy_backend_config(ProxyConfig::disabled()));
         714  +
        assert_eq!(
         715  +
            (StatusCode::OK, "direct".to_string()),
         716  +
            get(&client, &format!("http://{}/disabled", origin.addr()))
         717  +
                .await
         718  +
                .expect("direct request succeeds")
  845    719   
        );
  846         -
        assert_eq!(req.headers.get("proxy-authorization"), Some(&expected_auth));
  847         -
  848         -
        // Return 400 to avoid dealing with actual TLS tunneling
  849         -
        // The important part is that we got the CONNECT request with correct auth
  850         -
        Response::builder()
  851         -
            .status(StatusCode::BAD_REQUEST)
  852         -
            .body("CONNECT tunnel setup failed".to_string())
  853         -
            .unwrap()
  854    720   
    })
  855    721   
    .await;
         722  +
    assert!(proxy.requests().is_empty());
         723  +
    assert_eq!("/disabled", origin.requests()[0].uri);
         724  +
}
  856    725   
  857         -
    // Configure proxy with authentication
  858         -
    let proxy_config = ProxyConfig::all(format!("http://{}", mock_proxy.addr()))
  859         -
        .unwrap()
  860         -
        .with_basic_auth("connectuser", "connectpass");
  861         -
  862         -
    // Make HTTPS request - should trigger CONNECT method
  863         -
    let target_url = "https://secure.aws.amazon.com/api/secure";
  864         -
    let result = make_https_request_through_proxy(proxy_config, target_url, tls_provider).await;
         726  +
#[tokio::test]
         727  +
async fn test_disabled_proxy_overrides_environment_with_hyper_util_legacy_pool() {
         728  +
    disabled_proxy_overrides_environment(&HyperUtilLegacyPool).await;
         729  +
}
  865    730   
  866         -
    // We expect this to fail with a connection error since we returned 400
  867         -
    // The important thing is that the CONNECT request was made correctly
  868         -
    assert!(
  869         -
        result.is_err(),
  870         -
        "CONNECT tunnel should fail with 400 response for {}",
  871         -
        provider_name
         731  +
#[tokio::test]
         732  +
async fn test_disabled_proxy_overrides_environment_with_partitioned_connection_pool() {
         733  +
    disabled_proxy_overrides_environment(&PartitionedConnectionPool).await;
         734  +
}
         735  +
         736  +
async fn idle_proxy_connection_is_evicted(backend: &dyn HttpClientBackend) {
         737  +
    let proxy = MockHttpServer::with_response(StatusCode::OK, "proxied").await;
         738  +
    let config = ProxyConfig::http(format!("http://{}", proxy.addr())).expect("valid proxy");
         739  +
    let client = http_client(
         740  +
        backend,
         741  +
        BackendConfig {
         742  +
            pool_idle_timeout: Some(Duration::from_millis(100)),
         743  +
            ..proxy_backend_config(config)
         744  +
        },
  872    745   
    );
  873    746   
  874         -
    // Verify the proxy received the CONNECT request
  875         -
    let requests = mock_proxy.requests();
  876    747   
    assert_eq!(
  877         -
        requests.len(),
  878         -
        1,
  879         -
        "Proxy should have received exactly one CONNECT request for {}",
  880         -
        provider_name
         748  +
        StatusCode::OK,
         749  +
        get(&client, "http://service.test/idle")
         750  +
            .await
         751  +
            .expect("proxy request succeeds")
         752  +
            .0
  881    753   
    );
         754  +
    assert_eq!(1, proxy.connection_count());
         755  +
         756  +
    tokio::time::timeout(Duration::from_secs(2), async {
         757  +
        while proxy.connection_count() != 0 {
         758  +
            tokio::time::sleep(Duration::from_millis(10)).await;
         759  +
        }
         760  +
    })
         761  +
    .await
         762  +
    .expect("idle proxy connection should close");
  882    763   
}
  883    764   
  884         -
/// Generic test function for CONNECT without authentication (should get 407)
  885         -
/// Tests that HTTPS requests without auth get proper 407 response
  886         -
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
  887         -
async fn run_https_connect_auth_required_test(tls_provider: tls::Provider, provider_name: &str) {
  888         -
    let mock_proxy = MockProxyServer::new(|req| {
  889         -
        // For HTTPS through HTTP proxy, we should see a CONNECT request
  890         -
        assert_eq!(req.method, "CONNECT");
  891         -
        assert_eq!(req.uri, "secure.aws.amazon.com:443");
         765  +
#[tokio::test]
         766  +
async fn test_idle_proxy_connection_is_evicted_with_hyper_util_legacy_pool() {
         767  +
    idle_proxy_connection_is_evicted(&HyperUtilLegacyPool).await;
         768  +
}
  892    769   
  893         -
        // No auth header should be present
  894         -
        assert!(!req.headers.contains_key("proxy-authorization"));
         770  +
#[tokio::test]
         771  +
async fn test_idle_proxy_connection_is_evicted_with_partitioned_connection_pool() {
         772  +
    idle_proxy_connection_is_evicted(&PartitionedConnectionPool).await;
         773  +
}
  895    774   
  896         -
        // Return 407 Proxy Authentication Required
         775  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         776  +
async fn https_connect_uses_authority_form_and_authentication(
         777  +
    backend: &dyn HttpsClientBackend,
         778  +
    provider: tls::Provider,
         779  +
) {
         780  +
    let expected = basic_authorization("connectuser", "connectpass");
         781  +
    let expected_for_handler = expected.clone();
         782  +
    let proxy = MockHttpServer::new(move |request| {
         783  +
        assert_eq!("CONNECT", request.method);
         784  +
        assert_eq!("secure.example.com:443", request.uri);
         785  +
        assert_eq!(
         786  +
            Some(&expected_for_handler),
         787  +
            request.headers.get("proxy-authorization")
         788  +
        );
  897    789   
        Response::builder()
  898         -
            .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
  899         -
            .body("Proxy authentication required for CONNECT".to_string())
  900         -
            .unwrap()
         790  +
            .status(StatusCode::BAD_REQUEST)
         791  +
            .body("tunnel rejected".to_string())
         792  +
            .expect("valid response")
  901    793   
    })
  902    794   
    .await;
  903         -
  904         -
    // Configure proxy without authentication
  905         -
    let proxy_config = ProxyConfig::all(format!("http://{}", mock_proxy.addr())).unwrap();
  906         -
  907         -
    // Make HTTPS request - should trigger CONNECT method and get 407
  908         -
    let target_url = "https://secure.aws.amazon.com/api/secure";
  909         -
    let result = make_https_request_through_proxy(proxy_config, target_url, tls_provider).await;
  910         -
  911         -
    // We expect this to fail with a connection error since we returned 407
  912         -
    assert!(
  913         -
        result.is_err(),
  914         -
        "CONNECT tunnel should fail with 407 response for {}",
  915         -
        provider_name
         795  +
    let config = ProxyConfig::all(format!("http://{}", proxy.addr()))
         796  +
        .expect("valid proxy")
         797  +
        .with_basic_auth("connectuser", "connectpass");
         798  +
    let client = https_client(
         799  +
        backend,
         800  +
        proxy_backend_config(config),
         801  +
        provider,
         802  +
        tls::TlsContext::default(),
  916    803   
    );
  917    804   
  918         -
    let error_msg = result.unwrap_err().to_string();
  919         -
    let error_msg_lower = error_msg.to_lowercase();
  920         -
  921         -
    // The important thing is that the request failed (which means CONNECT was attempted)
  922         -
    // The specific error message format is less critical for this test
  923         -
    // We accept either specific proxy auth errors OR generic connection errors
  924         -
    // since both indicate the CONNECT tunnel attempt was made
  925    805   
    assert!(
  926         -
        error_msg_lower.contains("407")
  927         -
            || error_msg_lower.contains("proxy")
  928         -
            || error_msg_lower.contains("auth")
  929         -
            || error_msg_lower.contains("io error")
  930         -
            || error_msg_lower.contains("connection"),
  931         -
        "Error should be connection-related (indicating CONNECT was attempted) for {}, got: {}",
  932         -
        provider_name,
  933         -
        error_msg
  934         -
    );
  935         -
  936         -
    // Verify the proxy received the CONNECT request
  937         -
    let requests = mock_proxy.requests();
  938         -
    assert_eq!(
  939         -
        requests.len(),
  940         -
        1,
  941         -
        "Proxy should have received exactly one CONNECT request for {}",
  942         -
        provider_name
         806  +
        get(&client, "https://secure.example.com/private")
         807  +
            .await
         808  +
            .is_err(),
         809  +
        "a rejected CONNECT request must fail"
  943    810   
    );
         811  +
    assert_eq!(1, proxy.requests().len());
  944    812   
}
  945    813   
  946         -
/// Tests HTTPS tunneling through HTTP proxy with CONNECT method (rustls provider)
  947         -
/// Verifies that HTTPS requests through HTTP proxy use CONNECT method with authentication
  948    814   
#[cfg(feature = "rustls-ring")]
  949    815   
#[tokio::test]
  950         -
async fn test_https_connect_with_auth_rustls() {
  951         -
    run_https_connect_with_auth_test(
         816  +
async fn test_https_connect_form_and_auth_with_rustls_and_hyper_util_legacy_pool() {
         817  +
    https_connect_uses_authority_form_and_authentication(
         818  +
        &HyperUtilLegacyPool,
  952    819   
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
  953         -
        "rustls",
  954    820   
    )
  955    821   
    .await;
  956    822   
}
  957    823   
  958         -
/// Tests CONNECT method without authentication (should get 407) - rustls provider
  959         -
/// Verifies that HTTPS requests without auth get proper 407 response
  960    824   
#[cfg(feature = "rustls-ring")]
  961    825   
#[tokio::test]
  962         -
async fn test_https_connect_auth_required_rustls() {
  963         -
    run_https_connect_auth_required_test(
         826  +
async fn test_https_connect_form_and_auth_with_rustls_and_partitioned_connection_pool() {
         827  +
    https_connect_uses_authority_form_and_authentication(
         828  +
        &PartitionedConnectionPool,
  964    829   
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
  965         -
        "rustls",
  966    830   
    )
  967    831   
    .await;
  968    832   
}
  969    833   
  970         -
/// Tests HTTPS tunneling through HTTP proxy with CONNECT method (s2n-tls provider)
  971         -
/// Verifies that HTTPS requests through HTTP proxy use CONNECT method with authentication
  972    834   
#[cfg(feature = "s2n-tls")]
  973    835   
#[tokio::test]
  974         -
async fn test_https_connect_with_auth_s2n_tls() {
  975         -
    run_https_connect_with_auth_test(tls::Provider::S2nTls, "s2n-tls").await;
         836  +
async fn test_https_connect_form_and_auth_with_s2n_tls_and_hyper_util_legacy_pool() {
         837  +
    https_connect_uses_authority_form_and_authentication(
         838  +
        &HyperUtilLegacyPool,
         839  +
        tls::Provider::S2nTls,
         840  +
    )
         841  +
    .await;
  976    842   
}
  977    843   
  978         -
/// Tests CONNECT method without authentication (should get 407) - s2n-tls provider
  979         -
/// Verifies that HTTPS requests without auth get proper 407 response
  980    844   
#[cfg(feature = "s2n-tls")]
  981    845   
#[tokio::test]
  982         -
async fn test_https_connect_auth_required_s2n_tls() {
  983         -
    run_https_connect_auth_required_test(tls::Provider::S2nTls, "s2n-tls").await;
         846  +
async fn test_https_connect_form_and_auth_with_s2n_tls_and_partitioned_connection_pool() {
         847  +
    https_connect_uses_authority_form_and_authentication(
         848  +
        &PartitionedConnectionPool,
         849  +
        tls::Provider::S2nTls,
         850  +
    )
         851  +
    .await;
  984    852   
}
  985    853   
  986         -
/// Tests that HTTP requests through proxy use absolute URI form
  987         -
/// Verifies that the full URL (including hostname) is sent to the proxy
  988         -
#[tokio::test]
  989         -
async fn test_http_proxy_absolute_uri_form() {
  990         -
    let target_host = "api.example.com";
  991         -
    let target_path = "/v1/data";
  992         -
    let expected_absolute_uri = format!("http://{}{}", target_host, target_path);
  993         -
  994         -
    // Clone for use in closure
  995         -
    let expected_uri_clone = expected_absolute_uri.clone();
  996         -
    let target_host_clone = target_host.to_string();
  997         -
  998         -
    let mock_proxy = MockProxyServer::new(move |req| {
  999         -
        // For HTTP through proxy, we should see the full absolute URI
 1000         -
        assert_eq!(req.method, "GET");
 1001         -
        assert_eq!(req.uri, expected_uri_clone);
 1002         -
 1003         -
        // Host header should still be present
 1004         -
        assert_eq!(req.headers.get("host"), Some(&target_host_clone));
 1005         -
         854  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         855  +
async fn https_connect_without_authentication_is_rejected(
         856  +
    backend: &dyn HttpsClientBackend,
         857  +
    provider: tls::Provider,
         858  +
) {
         859  +
    let proxy = MockHttpServer::new(|request| {
         860  +
        assert_eq!("CONNECT", request.method);
         861  +
        assert_eq!("secure.example.com:443", request.uri);
         862  +
        assert!(!request.headers.contains_key("proxy-authorization"));
 1006    863   
        Response::builder()
 1007         -
            .status(StatusCode::OK)
 1008         -
            .body("proxied response".to_string())
 1009         -
            .unwrap()
         864  +
            .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
         865  +
            .body("authentication required".to_string())
         866  +
            .expect("valid response")
 1010    867   
    })
 1011    868   
    .await;
         869  +
    let config = ProxyConfig::all(format!("http://{}", proxy.addr())).expect("valid proxy");
         870  +
    let client = https_client(
         871  +
        backend,
         872  +
        proxy_backend_config(config),
         873  +
        provider,
         874  +
        tls::TlsContext::default(),
         875  +
    );
 1012    876   
 1013         -
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr())).unwrap();
 1014         -
 1015         -
    let result = make_http_request_through_proxy(proxy_config, &expected_absolute_uri).await;
 1016         -
 1017         -
    let (status, body) = result.expect("HTTP request through proxy should succeed");
 1018         -
    assert_eq!(status, StatusCode::OK);
 1019         -
    assert_eq!(body, "proxied response");
 1020         -
 1021         -
    // Verify the proxy received the request with absolute URI
 1022         -
    let requests = mock_proxy.requests();
 1023         -
    assert_eq!(requests.len(), 1);
 1024         -
    assert_eq!(requests[0].uri, expected_absolute_uri);
         877  +
    assert!(
         878  +
        get(&client, "https://secure.example.com/private")
         879  +
            .await
         880  +
            .is_err(),
         881  +
        "a 407 CONNECT response must fail"
         882  +
    );
         883  +
    assert_eq!(1, proxy.requests().len());
 1025    884   
}
 1026    885   
 1027         -
/// Tests that direct HTTP requests (no proxy) use origin form URI
 1028         -
/// Verifies that only the path is sent when connecting directly
         886  +
#[cfg(feature = "rustls-ring")]
 1029    887   
#[tokio::test]
 1030         -
async fn test_direct_http_origin_uri_form() {
 1031         -
    let target_path = "/v1/data";
 1032         -
 1033         -
    // Create a direct target server (no proxy)
 1034         -
    let direct_server = MockProxyServer::new(move |req| {
 1035         -
        // For direct connections, we should see only the path (origin form)
 1036         -
        assert_eq!(req.method, "GET");
 1037         -
        // The URI should be just the path part, not the full URL
 1038         -
        assert!(
 1039         -
            req.uri == target_path || req.uri.ends_with(target_path),
 1040         -
            "Expected origin form URI ending with '{}', got '{}'",
 1041         -
            target_path,
 1042         -
            req.uri
 1043         -
        );
 1044         -
 1045         -
        Response::builder()
 1046         -
            .status(StatusCode::OK)
 1047         -
            .body("direct response".to_string())
 1048         -
            .unwrap()
 1049         -
    })
         888  +
async fn test_https_connect_without_auth_with_rustls_and_hyper_util_legacy_pool() {
         889  +
    https_connect_without_authentication_is_rejected(
         890  +
        &HyperUtilLegacyPool,
         891  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         892  +
    )
 1050    893   
    .await;
 1051         -
 1052         -
    // Use disabled proxy to ensure direct connection
 1053         -
    let proxy_config = ProxyConfig::disabled();
 1054         -
 1055         -
    let target_url = format!("http://{}{}", direct_server.addr(), target_path);
 1056         -
    let result = make_http_request_through_proxy(proxy_config, &target_url).await;
 1057         -
 1058         -
    let (status, body) = result.expect("Direct HTTP request should succeed");
 1059         -
    assert_eq!(status, StatusCode::OK);
 1060         -
    assert_eq!(body, "direct response");
 1061         -
 1062         -
    // Verify the server received the request
 1063         -
    let requests = direct_server.requests();
 1064         -
    assert_eq!(requests.len(), 1);
 1065    894   
}
 1066    895   
 1067         -
/// Tests URI form handling with different proxy configurations
 1068         -
/// Verifies that URI form changes based on proxy vs direct connection
         896  +
#[cfg(feature = "rustls-ring")]
 1069    897   
#[tokio::test]
 1070         -
async fn test_uri_form_proxy_vs_direct() {
 1071         -
    let target_host = "test.example.com";
 1072         -
    let target_path = "/api/test";
 1073         -
    let full_url = format!("http://{}{}", target_host, target_path);
 1074         -
 1075         -
    // Test 1: Through proxy - should use absolute form
 1076         -
    {
 1077         -
        // Clone for use in closure
 1078         -
        let target_host_clone = target_host.to_string();
 1079         -
        let target_path_clone = target_path.to_string();
 1080         -
 1081         -
        let mock_proxy = MockProxyServer::new(move |req| {
 1082         -
            // Should receive absolute URI
 1083         -
            assert!(req.uri.starts_with("http://"));
 1084         -
            assert!(req.uri.contains(&target_host_clone));
 1085         -
            assert!(req.uri.contains(&target_path_clone));
 1086         -
 1087         -
            Response::builder()
 1088         -
                .status(StatusCode::OK)
 1089         -
                .body("proxy response".to_string())
 1090         -
                .unwrap()
 1091         -
        })
 1092         -
        .await;
 1093         -
 1094         -
        let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr())).unwrap();
 1095         -
        let result = make_http_request_through_proxy(proxy_config, &full_url).await;
 1096         -
 1097         -
        assert!(result.is_ok(), "Proxy request should succeed");
 1098         -
        let requests = mock_proxy.requests();
 1099         -
        assert_eq!(requests.len(), 1);
 1100         -
        assert_eq!(requests[0].uri, full_url);
 1101         -
    }
 1102         -
 1103         -
    // Test 2: Direct connection - should use origin form
 1104         -
    {
 1105         -
        let target_path_clone = target_path.to_string();
 1106         -
 1107         -
        let direct_server = MockProxyServer::new(move |req| {
 1108         -
            // Should receive only the path part
 1109         -
            assert!(!req.uri.starts_with("http://"));
 1110         -
            assert!(req.uri == target_path_clone || req.uri.ends_with(&target_path_clone));
         898  +
async fn test_https_connect_without_auth_with_rustls_and_partitioned_connection_pool() {
         899  +
    https_connect_without_authentication_is_rejected(
         900  +
        &PartitionedConnectionPool,
         901  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         902  +
    )
         903  +
    .await;
         904  +
}
 1111    905   
 1112         -
            Response::builder()
 1113         -
                .status(StatusCode::OK)
 1114         -
                .body("direct response".to_string())
 1115         -
                .unwrap()
 1116         -
        })
         906  +
#[cfg(feature = "s2n-tls")]
         907  +
#[tokio::test]
         908  +
async fn test_https_connect_without_auth_with_s2n_tls_and_hyper_util_legacy_pool() {
         909  +
    https_connect_without_authentication_is_rejected(&HyperUtilLegacyPool, tls::Provider::S2nTls)
 1117    910   
        .await;
 1118         -
 1119         -
        let proxy_config = ProxyConfig::disabled();
 1120         -
        let direct_url = format!("http://{}{}", direct_server.addr(), target_path);
 1121         -
        let result = make_http_request_through_proxy(proxy_config, &direct_url).await;
 1122         -
 1123         -
        assert!(result.is_ok(), "Direct request should succeed");
 1124         -
        let requests = direct_server.requests();
 1125         -
        assert_eq!(requests.len(), 1);
 1126         -
    }
 1127    911   
}
 1128    912   
 1129         -
/// Generic test function for CONNECT URI form validation
 1130         -
/// Tests that CONNECT requests use the correct host:port format
 1131         -
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
 1132         -
async fn run_connect_uri_form_test(tls_provider: tls::Provider, provider_name: &str) {
 1133         -
    let target_host = "secure.example.com";
 1134         -
    let target_port = 443;
 1135         -
    let expected_connect_uri = format!("{}:{}", target_host, target_port);
 1136         -
 1137         -
    // Clone for use in closure
 1138         -
    let expected_uri_clone = expected_connect_uri.clone();
 1139         -
 1140         -
    let mock_proxy = MockProxyServer::new(move |req| {
 1141         -
        if req.method == "CONNECT" {
 1142         -
            // CONNECT should use host:port format
 1143         -
            assert_eq!(req.uri, expected_uri_clone);
 1144         -
 1145         -
            // CONNECT requests should not have a Host header in the CONNECT line
 1146         -
            // (the Host header is for the tunneled HTTP request, not the CONNECT)
 1147         -
 1148         -
            Response::builder()
 1149         -
                .status(StatusCode::OK)
 1150         -
                .body("Connection established".to_string())
 1151         -
                .unwrap()
 1152         -
        } else {
 1153         -
            // This shouldn't happen in our test, but handle it gracefully
 1154         -
            Response::builder()
 1155         -
                .status(StatusCode::BAD_REQUEST)
 1156         -
                .body("Unexpected non-CONNECT request".to_string())
 1157         -
                .unwrap()
 1158         -
        }
 1159         -
    })
         913  +
#[cfg(feature = "s2n-tls")]
         914  +
#[tokio::test]
         915  +
async fn test_https_connect_without_auth_with_s2n_tls_and_partitioned_connection_pool() {
         916  +
    https_connect_without_authentication_is_rejected(
         917  +
        &PartitionedConnectionPool,
         918  +
        tls::Provider::S2nTls,
         919  +
    )
 1160    920   
    .await;
         921  +
}
 1161    922   
 1162         -
    let proxy_config = ProxyConfig::all(format!("http://{}", mock_proxy.addr())).unwrap();
 1163         -
 1164         -
    // Try to make an HTTPS request - this should trigger CONNECT
 1165         -
    let target_url = format!("https://{}/api/secure", target_host);
         923  +
#[cfg(any(feature = "rustls-ring", feature = "s2n-tls"))]
         924  +
async fn tunneled_https_request_uses_origin_form(
         925  +
    backend: &dyn HttpsClientBackend,
         926  +
    provider: tls::Provider,
         927  +
) {
         928  +
    let origin = MockTlsOrigin::new("tunneled response").await;
         929  +
    let authorization = basic_authorization("connectuser", "connectpass");
         930  +
    let proxy = MockConnectProxy::relay_to(origin.addr(), Some(authorization.clone())).await;
         931  +
    let config = ProxyConfig::all(format!("http://{}", proxy.addr()))
         932  +
        .expect("valid proxy")
         933  +
        .with_basic_auth("connectuser", "connectpass");
         934  +
    let client = https_client(
         935  +
        backend,
         936  +
        proxy_backend_config(config),
         937  +
        provider,
         938  +
        test_tls::SERVER_IDENTITY.client_context(),
         939  +
    );
         940  +
    let target = format!("https://localhost:{}/inside?value=1", origin.addr().port());
 1166    941   
 1167         -
    let _result = make_https_request_through_proxy(proxy_config, &target_url, tls_provider).await;
         942  +
    assert_eq!(
         943  +
        (StatusCode::OK, "tunneled response".to_string()),
         944  +
        get(&client, &target)
         945  +
            .await
         946  +
            .expect("tunneled HTTPS request succeeds")
         947  +
    );
 1168    948   
 1169         -
    // The request will likely fail due to our mock setup, but that's OK
 1170         -
    // The important thing is that the CONNECT request was made with correct URI
 1171         -
    let requests = mock_proxy.requests();
         949  +
    let proxy_requests = proxy.requests();
         950  +
    assert_eq!(1, proxy_requests.len());
         951  +
    assert_eq!("CONNECT", proxy_requests[0].method);
         952  +
    assert_eq!(
         953  +
        format!("localhost:{}", origin.addr().port()),
         954  +
        proxy_requests[0].uri
         955  +
    );
 1172    956   
    assert_eq!(
 1173         -
        requests.len(),
 1174         -
        1,
 1175         -
        "Should have received exactly one CONNECT request for {}",
 1176         -
        provider_name
         957  +
        Some(&authorization),
         958  +
        proxy_requests[0].headers.get("proxy-authorization")
         959  +
    );
         960  +
         961  +
    let origin_requests = origin.requests();
         962  +
    assert_eq!(1, origin_requests.len());
         963  +
    assert_eq!("GET", origin_requests[0].method);
         964  +
    assert_eq!("/inside?value=1", origin_requests[0].uri);
         965  +
    assert!(
         966  +
        !origin_requests[0]
         967  +
            .headers
         968  +
            .contains_key("proxy-authorization"),
         969  +
        "proxy credentials must not cross the CONNECT tunnel"
 1177    970   
    );
 1178         -
    assert_eq!(requests[0].method, "CONNECT");
 1179         -
    assert_eq!(requests[0].uri, expected_connect_uri);
 1180    971   
}
 1181    972   
 1182         -
/// Tests CONNECT method URI form for HTTPS tunneling - rustls provider
 1183         -
/// Verifies that CONNECT requests use the correct host:port format
 1184    973   
#[cfg(feature = "rustls-ring")]
 1185    974   
#[tokio::test]
 1186         -
async fn test_connect_uri_form_rustls() {
 1187         -
    run_connect_uri_form_test(
         975  +
async fn test_tunneled_https_origin_form_with_rustls_and_hyper_util_legacy_pool() {
         976  +
    tunneled_https_request_uses_origin_form(
         977  +
        &HyperUtilLegacyPool,
         978  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         979  +
    )
         980  +
    .await;
         981  +
}
         982  +
         983  +
#[cfg(feature = "rustls-ring")]
         984  +
#[tokio::test]
         985  +
async fn test_tunneled_https_origin_form_with_rustls_and_partitioned_connection_pool() {
         986  +
    tunneled_https_request_uses_origin_form(
         987  +
        &PartitionedConnectionPool,
 1188    988   
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
 1189         -
        "rustls",
 1190    989   
    )
 1191    990   
    .await;
 1192    991   
}
 1193    992   
 1194         -
/// Tests CONNECT method URI form for HTTPS tunneling - s2n-tls provider
 1195         -
/// Verifies that CONNECT requests use the correct host:port format
 1196    993   
#[cfg(feature = "s2n-tls")]
 1197    994   
#[tokio::test]
 1198         -
async fn test_connect_uri_form_s2n_tls() {
 1199         -
    run_connect_uri_form_test(tls::Provider::S2nTls, "s2n-tls").await;
         995  +
async fn test_tunneled_https_origin_form_with_s2n_tls_and_hyper_util_legacy_pool() {
         996  +
    tunneled_https_request_uses_origin_form(&HyperUtilLegacyPool, tls::Provider::S2nTls).await;
         997  +
}
         998  +
         999  +
#[cfg(feature = "s2n-tls")]
        1000  +
#[tokio::test]
        1001  +
async fn test_tunneled_https_origin_form_with_s2n_tls_and_partitioned_connection_pool() {
        1002  +
    tunneled_https_request_uses_origin_form(&PartitionedConnectionPool, tls::Provider::S2nTls)
        1003  +
        .await;
 1200   1004   
}