1use std::{fmt, io};
7
8use crate::{
9 Layer, Service,
10 error::BoxError,
11 extensions::ExtensionsRef,
12 http::{Body as ResponseBody, Request, Response, StreamingBody},
13 net::client::EstablishedClientConnection,
14 rt::Executor,
15 service::BoxService,
16 telemetry::tracing,
17};
18
19#[doc(inline)]
20pub use ::rama_http::service::client::blocking::{
21 Body as BlockingBody, Client as BlockingHttpClient, Response as BlockingResponse,
22};
23#[doc(inline)]
24pub use ::rama_http_backend::client::*;
25use rama_core::{
26 error::{ErrorContext, ErrorExt as _, extra::OpaqueError},
27 extensions::Egress,
28 layer::MapErr,
29};
30use rama_http::{
31 layer::{
32 forward_proxy::{HttpForwardProxyLayer, HttpForwardProxyService},
33 map_response_body::MapResponseBody,
34 },
35 proxy::PlaintextHttpProxyMode,
36};
37
38#[cfg(any(feature = "boring", feature = "rustls"))]
39use crate::tls::client::TlsClientConfig;
40
41#[cfg(feature = "boring")]
42use {
43 crate::{quic::tls::BoringTlsProvider, tls::boring::client::TlsClientSessionCache},
44 std::sync::Arc,
45};
46
47pub mod builder;
48#[doc(inline)]
49pub use builder::EasyHttpConnectorBuilder;
50
51#[cfg(feature = "socks5")]
52mod proxy_connector;
53#[cfg(feature = "socks5")]
54#[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
55#[doc(inline)]
56pub use proxy_connector::{MaybeProxiedConnection, ProxyConnector, ProxyConnectorLayer};
57
58pub struct EasyHttpWebClient<BodyIn, ConnResponse, L> {
75 connector: BoxService<Request<BodyIn>, ConnResponse, OpaqueError>,
76 forward_proxy_layer: HttpForwardProxyLayer,
77 plaintext_http_proxy_mode: Option<PlaintextHttpProxyMode>,
78 jit_layers: L,
79}
80
81impl<BodyIn, ConnResponse, L> fmt::Debug for EasyHttpWebClient<BodyIn, ConnResponse, L> {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 f.debug_struct("EasyHttpWebClient").finish()
84 }
85}
86
87impl<BodyIn, ConnResponse, L: Clone> Clone for EasyHttpWebClient<BodyIn, ConnResponse, L> {
88 fn clone(&self) -> Self {
89 Self {
90 connector: self.connector.clone(),
91 forward_proxy_layer: self.forward_proxy_layer.clone(),
92 plaintext_http_proxy_mode: self.plaintext_http_proxy_mode,
93 jit_layers: self.jit_layers.clone(),
94 }
95 }
96}
97
98impl EasyHttpWebClient<(), (), ()> {
99 #[must_use]
101 pub fn connector_builder() -> EasyHttpConnectorBuilder {
102 EasyHttpConnectorBuilder::new()
103 }
104
105 pub fn try_blocking() -> io::Result<BlockingHttpWebClient> {
124 BlockingHttpClient::try_new(EasyHttpWebClient::default())
125 }
126}
127
128pub type DefaultHttpWebClient<Body = crate::http::Body> = EasyHttpWebClient<
139 Body,
140 EstablishedClientConnection<
141 rama_http::layer::alt_svc::AltSvc<
142 BindBodyToConn<
143 crate::net::client::pool::MultiplexedConnection<
144 HttpClientService<Body>,
145 HttpConnId,
146 >,
147 >,
148 >,
149 Request<Body>,
150 >,
151 (),
152>;
153
154pub type BlockingHttpWebClient = BlockingHttpClient<DefaultHttpWebClient>;
156
157impl<Body> Default for DefaultHttpWebClient<Body>
158where
159 Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
160{
161 #[inline(always)]
162 fn default() -> Self {
163 Self::default_with_executor(Executor::default())
164 }
165}
166
167impl<Body> DefaultHttpWebClient<Body>
168where
169 Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
170{
171 core::cfg_select! {
172 feature = "boring" => {
173 pub fn default_with_executor(exec: Executor) -> Self {
174 let tls_config = TlsClientConfig::default_http();
175 #[expect(
176 clippy::expect_used,
177 reason = "fixed default H3 limits and the explicit BoringSSL provider are valid"
178 )]
179 let h3 = Http3Connector::builder(exec.clone())
180 .with_tls_config(tls_config.clone())
181 .with_tls_provider(Arc::new(BoringTlsProvider))
182 .build_lazy()
183 .expect("default BoringSSL HTTP/3 configuration is valid");
184
185 EasyHttpConnectorBuilder::new()
186 .with_default_transport_connector()
187 .with_default_dns_connector()
188 .with_tls_proxy_support_using_boringssl()
189 .with_proxy_support()
190 .with_tls_support_using_boringssl(tls_config)
191 .with_tls_session_store(Arc::new(TlsClientSessionCache::default()))
192 .with_default_http_connector(exec)
193 .with_http3_support(h3)
194 .with_default_connection_pool()
195 .build_client()
196 }
197 }
198 feature = "rustls" => {
199 pub fn default_with_executor(exec: Executor) -> Self {
200 let tls_config = TlsClientConfig::default_http();
201 #[cfg(any(feature = "ring", feature = "aws-lc"))]
202 #[expect(
203 clippy::expect_used,
204 reason = "fixed default H3 limits and a compiled-in Rustls crypto provider are valid"
205 )]
206 let h3 = Http3Connector::builder(exec.clone())
207 .with_tls_config(tls_config.clone())
208 .build_lazy()
209 .expect("default Rustls HTTP/3 configuration is valid");
210
211 let builder = EasyHttpConnectorBuilder::new()
212 .with_default_transport_connector()
213 .with_default_dns_connector()
214 .with_tls_proxy_support_using_rustls()
215 .with_proxy_support()
216 .with_tls_support_using_rustls(tls_config)
217 .with_default_http_connector(exec);
218 #[cfg(any(feature = "ring", feature = "aws-lc"))]
219 let builder = builder.with_http3_support(h3);
220
221 builder
222 .with_default_connection_pool()
223 .build_client()
224 }
225 }
226 _ => {
227 pub fn default_with_executor(exec: Executor) -> Self {
228 EasyHttpConnectorBuilder::new()
229 .with_default_transport_connector()
230 .with_default_dns_connector()
231 .without_tls_proxy_support()
232 .with_proxy_support()
233 .without_tls_support()
234 .with_default_http_connector(exec)
235 .with_default_connection_pool()
236 .build_client()
237 }
238 }
239 }
240}
241
242impl<BodyIn, ConnResponse> EasyHttpWebClient<BodyIn, ConnResponse, ()>
243where
244 BodyIn: Send + 'static,
245{
246 #[must_use]
252 pub fn new<S>(connector: S) -> Self
253 where
254 S: Service<Request<BodyIn>, Output = ConnResponse, Error: Into<BoxError>>,
255 {
256 Self {
257 connector: MapErr::into_opaque_error(connector).boxed(),
258 forward_proxy_layer: HttpForwardProxyLayer::new(),
259 plaintext_http_proxy_mode: None,
260 jit_layers: (),
261 }
262 }
263}
264
265impl<BodyIn, ConnResponse, L> EasyHttpWebClient<BodyIn, ConnResponse, L> {
266 pub fn try_into_blocking(self) -> io::Result<BlockingHttpClient<Self>> {
269 BlockingHttpClient::try_new(self)
270 }
271
272 #[must_use]
275 pub fn into_blocking_with_runtime(
276 self,
277 runtime: &crate::rt::blocking::Runtime,
278 ) -> BlockingHttpClient<Self> {
279 BlockingHttpClient::with_runtime(self, runtime)
280 }
281
282 #[must_use]
286 pub fn with_connector<S, BodyInNew, ConnResponseNew>(
287 self,
288 connector: S,
289 ) -> EasyHttpWebClient<BodyInNew, ConnResponseNew, L>
290 where
291 S: Service<Request<BodyInNew>, Output = ConnResponseNew, Error: Into<BoxError>>,
292 BodyInNew: Send + 'static,
293 {
294 EasyHttpWebClient {
295 connector: MapErr::into_opaque_error(connector).boxed(),
296 forward_proxy_layer: self.forward_proxy_layer,
297 plaintext_http_proxy_mode: self.plaintext_http_proxy_mode,
298 jit_layers: self.jit_layers,
299 }
300 }
301
302 pub fn with_jit_layer<T>(self, jit_layers: T) -> EasyHttpWebClient<BodyIn, ConnResponse, T> {
314 EasyHttpWebClient {
315 connector: self.connector,
316 forward_proxy_layer: self.forward_proxy_layer,
317 plaintext_http_proxy_mode: self.plaintext_http_proxy_mode,
318 jit_layers,
319 }
320 }
321
322 crate::utils::macros::generate_set_and_with! {
323 pub fn forward_proxy_auth(mut self, enabled: bool) -> Self {
335 self.forward_proxy_layer.set_proxy_auth(enabled);
336 self
337 }
338 }
339
340 #[must_use]
344 pub fn without_forward_proxy_auth(self) -> Self {
345 self.with_forward_proxy_auth(false)
346 }
347
348 crate::utils::macros::generate_set_and_with! {
349 pub fn tunnel_plaintext_http(mut self, enabled: bool) -> Self {
361 self.plaintext_http_proxy_mode = Some(if enabled {
362 PlaintextHttpProxyMode::Tunnel
363 } else {
364 PlaintextHttpProxyMode::Forward
365 });
366 self
367 }
368 }
369
370 crate::utils::macros::generate_set_and_with! {
371 pub fn isolate_forward_proxy_auth_error(mut self, enabled: bool) -> Self {
378 self.forward_proxy_layer.set_isolate_auth_error(enabled);
379 self
380 }
381 }
382}
383
384impl<Body, ConnectionBody, Connection, IncomingBody, L> Service<Request<Body>>
385 for EasyHttpWebClient<Body, EstablishedClientConnection<Connection, Request<ConnectionBody>>, L>
386where
387 Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
388 Connection: Service<Request<ConnectionBody>, Output = Response<IncomingBody>, Error = BoxError>
389 + ExtensionsRef,
390 IncomingBody: Send + Sync + 'static,
391 ResponseBody: From<IncomingBody>,
392 ConnectionBody:
395 StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
396 L: Layer<
397 HttpForwardProxyService<MapResponseBody<Connection, fn(IncomingBody) -> ResponseBody>>,
398 Service: Service<Request<ConnectionBody>, Output = Response, Error = BoxError>,
399 > + Send
400 + Sync
401 + 'static,
402{
403 type Output = Response;
404 type Error = OpaqueError;
405
406 async fn serve(&self, req: Request<Body>) -> Result<Self::Output, Self::Error> {
407 let uri = req.uri().clone();
408
409 if let Some(mode) = self.plaintext_http_proxy_mode {
410 req.extensions().insert(mode);
411 }
412
413 let EstablishedClientConnection {
414 input: req,
415 conn: http_connection,
416 } = self.connector.serve(req).await.into_opaque_error()?;
417
418 req.extensions()
422 .insert(Egress(http_connection.extensions().clone()));
423
424 let http_connection = MapResponseBody::into_boxed_streaming_body(http_connection);
425 let http_connection = self.forward_proxy_layer.layer(http_connection);
426 let http_connection = self.jit_layers.layer(http_connection);
427
428 tracing::trace!(url.full = %uri, "send http req to connector stack");
430
431 let result = http_connection.serve(req).await;
432
433 match result {
434 Ok(resp) => {
435 tracing::trace!(url.full = %uri, "response received from connector stack");
436 Ok(resp)
437 }
438 Err(err) => Err(err
439 .context("http request failure")
440 .context_field("uri", uri)
441 .into_opaque_error()),
442 }
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use std::{
449 convert::Infallible,
450 num::NonZeroUsize,
451 sync::{
452 Arc,
453 atomic::{AtomicUsize, Ordering},
454 },
455 time::Duration,
456 };
457
458 use rama_core::extensions::Extensions;
459 use rama_core::{error::BoxErrorExt as _, service::service_fn};
460 use rama_http::{Body, BodyExtractExt, Version};
461 use rama_http_backend::server::HttpServer;
462 use rama_net::{
463 address::ProxyAddress,
464 client::{
465 ConnectRequest, ConnectionError, ConnectionErrorDomain, ConnectionErrorKind,
466 ConnectorService, ConnectorTarget, EstablishedProxyRoute, ProxyRoute,
467 ProxyRouteFailureCache, ProxyRouteFailureCacheConfig, ProxyRouteFailureCacheScope,
468 ProxyRoutes,
469 },
470 test_utils::client::{MockConnectorService, MockSocket},
471 };
472 use rama_utils::octets::kib;
473 use serde::{Deserialize, Serialize};
474 use tokio::time::sleep;
475
476 #[cfg(feature = "boring")]
477 use {
478 crate::{
479 net::address::SocketAddress,
480 tcp::{TcpStream, server::TcpListener},
481 tls::{
482 boring::{TlsStream, server::TlsAcceptorLayer},
483 client::{NegotiatedTlsParameters, ServerVerifyMode, TlsServerVerify},
484 server::{GeneratedServerAuthConfig, ServerAuthData, TlsServerConfig},
485 },
486 },
487 rama_core::{extensions::ExtensionsRef as _, layer::MapInputLayer},
488 rama_http::header::CONNECTION,
489 };
490
491 use super::*;
492
493 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
494 struct Output {
495 conn: usize,
496 resp: usize,
497 }
498
499 #[tokio::test]
500 async fn default_client_request_future_stays_within_stack_budget() {
501 let client = DefaultHttpWebClient::<Body>::default();
502 let request = Request::builder()
503 .uri("https://example.com/")
504 .body(Body::empty())
505 .unwrap();
506 let request = client.serve(request);
507 let inner_size = std::mem::size_of_val(&request);
508 let request = tokio::time::timeout(Duration::from_secs(1), request);
509 let size = std::mem::size_of_val(&request);
510 eprintln!("default client request: {inner_size} bytes; with timeout: {size} bytes");
511 assert!(
512 size <= kib(16),
513 "default client request future is {size} bytes"
514 );
515 }
516
517 #[test]
518 fn default_client_can_be_constructed_and_cloned_without_a_runtime() {
519 let client = DefaultHttpWebClient::<Body>::default();
520 drop(client.clone());
521 drop(client);
522 }
523
524 #[derive(Debug, Clone, Default)]
525 struct EmptyHttpConnection {
526 extensions: Extensions,
527 }
528
529 impl ExtensionsRef for EmptyHttpConnection {
530 fn extensions(&self) -> &Extensions {
531 &self.extensions
532 }
533 }
534
535 impl Service<Request> for EmptyHttpConnection {
536 type Output = Response;
537 type Error = BoxError;
538
539 async fn serve(&self, _request: Request) -> Result<Self::Output, Self::Error> {
540 Ok(Response::new(Body::empty()))
541 }
542 }
543
544 #[derive(Debug, Clone)]
545 struct InspectConnectionRouteLayer(EstablishedProxyRoute);
546
547 impl<S: ExtensionsRef> Layer<S> for InspectConnectionRouteLayer {
548 type Service = S;
549
550 fn layer(&self, inner: S) -> Self::Service {
551 assert_eq!(
552 inner.extensions().get_ref::<EstablishedProxyRoute>(),
553 Some(&self.0),
554 );
555 inner
556 }
557 }
558
559 fn dummy_server<Input: Send + 'static>()
560 -> impl Service<
561 Input,
562 Output = EstablishedClientConnection<MockSocket, Input>,
563 Error = Infallible,
564 > + Clone {
565 let created_connections = Arc::new(AtomicUsize::new(0));
566 MockConnectorService::new(move || {
567 let created_connections = created_connections.clone();
568 let conn = created_connections.fetch_add(1, Ordering::Relaxed);
569
570 let created_response = Arc::new(AtomicUsize::new(0));
572
573 HttpServer::auto(Executor::default()).service(service_fn(move |_req: Request| {
574 let created_response = created_response.clone();
575 let resp = created_response.fetch_add(1, Ordering::Relaxed);
576 async move {
577 sleep(Duration::from_millis(5)).await;
578 let out = Output { conn, resp };
579 let resp = Response::new(Body::from(serde_json::to_vec(&out).unwrap()));
580 Ok::<_, Infallible>(resp)
581 }
582 }))
583 })
584 }
585
586 #[tokio::test]
587 async fn custom_connector_receives_plaintext_http_proxy_mode() {
588 let connector = service_fn(|request: Request| async move {
589 assert_eq!(
590 request.extensions().get_ref::<PlaintextHttpProxyMode>(),
591 Some(&PlaintextHttpProxyMode::Tunnel)
592 );
593
594 let conn = EmptyHttpConnection::default();
595 Ok::<_, Infallible>(EstablishedClientConnection {
596 input: request,
597 conn,
598 })
599 });
600 let client = EasyHttpWebClient::new(connector).with_tunnel_plaintext_http(true);
601 let request = Request::builder()
602 .uri("http://example.com/")
603 .body(Body::empty())
604 .unwrap();
605
606 let response = client.serve(request).await.unwrap();
607 assert_eq!(response.status(), crate::http::StatusCode::OK);
608 }
609
610 #[tokio::test]
611 async fn jit_layer_can_read_established_connection_extensions() {
612 let connector = service_fn(|request: Request| async move {
613 let conn = EmptyHttpConnection::default();
614 conn.extensions().insert(EstablishedProxyRoute::Direct);
615 Ok::<_, Infallible>(EstablishedClientConnection {
616 input: request,
617 conn,
618 })
619 });
620 let client = EasyHttpWebClient::new(connector)
621 .with_jit_layer(InspectConnectionRouteLayer(EstablishedProxyRoute::Direct));
622 let request = Request::builder()
623 .uri("http://example.com/")
624 .extension(ProxyRoute::Direct)
625 .body(Body::empty())
626 .unwrap();
627
628 let response = client.serve(request).await.unwrap();
629 assert_eq!(response.status(), crate::http::StatusCode::OK);
630 }
631
632 #[tokio::test]
633 async fn jit_request_metadata_cannot_change_proxy_credentials_target_or_challenge_isolation() {
634 use rama_core::layer::{MapInputLayer, MapOutputLayer};
635 use rama_http::{HeaderValue, StatusCode, header::PROXY_AUTHORIZATION};
636
637 #[derive(Debug, Clone)]
638 struct InspectProxyConnection {
639 extensions: Extensions,
640 }
641
642 impl ExtensionsRef for InspectProxyConnection {
643 fn extensions(&self) -> &Extensions {
644 &self.extensions
645 }
646 }
647
648 impl Service<Request> for InspectProxyConnection {
649 type Output = Response;
650 type Error = BoxError;
651
652 async fn serve(&self, request: Request) -> Result<Response, BoxError> {
653 let route = self.extensions.get_ref::<EstablishedProxyRoute>();
654 let is_forward = route.is_some_and(EstablishedProxyRoute::is_http_forward);
655 assert_eq!(
656 request
657 .extensions()
658 .egress()
659 .unwrap()
660 .0
661 .get_ref::<EstablishedProxyRoute>(),
662 route,
663 );
664 assert_eq!(
665 request.extensions().get_ref::<ProxyRoute>(),
666 Some(&ProxyRoute::Proxy(
667 "http://wrong:request-secret@requested.example:8080"
668 .parse()
669 .unwrap(),
670 )),
671 "forward policy must preserve the caller's requested route",
672 );
673 let mut target = Vec::new();
674 rama_http::proto::h1::head::encode_request_target(
675 request.method(),
676 request.uri(),
677 request.extensions(),
678 &mut target,
679 )
680 .unwrap();
681 if is_forward {
682 assert_eq!(
683 request.headers().get(PROXY_AUTHORIZATION).unwrap(),
684 "Basic dXBzdHJlYW06c2VjcmV0",
685 );
686 assert_eq!(&target[..], b"http://origin.example/resource");
687 } else {
688 assert!(request.headers().get(PROXY_AUTHORIZATION).is_none());
689 assert_eq!(&target[..], b"/resource");
690 }
691 Ok(Response::builder()
692 .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
693 .header("proxy-authenticate", "Basic realm=private-upstream")
694 .body(Body::from("private upstream challenge"))
695 .unwrap())
696 }
697 }
698
699 let proxy: ProxyAddress = "http://upstream:secret@proxy.example:8080".parse().unwrap();
700 for isolate in [false, true] {
701 for route in [
702 None,
703 Some(EstablishedProxyRoute::Direct),
704 Some(EstablishedProxyRoute::Tunnel(proxy.clone())),
705 Some(EstablishedProxyRoute::Tunnel(
706 "socks5://proxy.example:1080".parse().unwrap(),
707 )),
708 Some(EstablishedProxyRoute::Forward(proxy.clone())),
709 ] {
710 let is_forward = route
711 .as_ref()
712 .is_some_and(EstablishedProxyRoute::is_http_forward);
713 let connector = service_fn(move |request: Request| {
714 let route = route.clone();
715 async move {
716 let extensions = Extensions::new();
717 if let Some(route) = route {
718 extensions.insert(route);
719 }
720 Ok::<_, Infallible>(EstablishedClientConnection {
721 input: request,
722 conn: InspectProxyConnection { extensions },
723 })
724 }
725 });
726 let stale_route = if is_forward {
727 EstablishedProxyRoute::Direct
728 } else {
729 EstablishedProxyRoute::Forward(proxy.clone())
730 };
731 let observed_responses = Arc::new(AtomicUsize::new(0));
732 let client = EasyHttpWebClient::new(connector)
733 .with_isolate_forward_proxy_auth_error(isolate)
734 .with_jit_layer((
735 MapInputLayer::new(move |mut request: Request| {
736 request.extensions().insert(stale_route.clone());
737 let stale_egress = Extensions::new();
738 stale_egress.insert(stale_route.clone());
739 request.extensions().insert(Egress(stale_egress));
740 request.headers_mut().insert(
741 PROXY_AUTHORIZATION,
742 HeaderValue::from_static("Basic downstream-secret"),
743 );
744 request
745 }),
746 MapOutputLayer::new({
747 let observed_responses = observed_responses.clone();
748 move |response: Response| {
749 observed_responses.fetch_add(1, Ordering::Relaxed);
750 response
751 }
752 }),
753 ));
754 let request = Request::builder()
755 .uri("http://origin.example/resource")
756 .body(Body::empty())
757 .unwrap();
758 request.extensions().insert(ProxyRoute::Proxy(
759 "http://wrong:request-secret@requested.example:8080"
760 .parse()
761 .unwrap(),
762 ));
763 let result = client.serve(request).await;
764 if isolate && is_forward {
765 assert!(result.is_err());
766 assert_eq!(observed_responses.load(Ordering::Relaxed), 0);
767 } else {
768 assert_eq!(
769 result.unwrap().status(),
770 StatusCode::PROXY_AUTHENTICATION_REQUIRED
771 );
772 assert_eq!(observed_responses.load(Ordering::Relaxed), 1);
773 }
774 }
775 }
776 }
777
778 #[test]
779 fn blocking_client_drives_the_composed_http_stack() {
780 let client = EasyHttpWebClient::connector_builder()
781 .with_custom_transport_connector(dummy_server())
782 .without_dns_connector()
783 .without_tls_proxy_support()
784 .without_proxy_support()
785 .without_tls_support()
786 .with_default_http_connector(Executor::default())
787 .without_connection_pool()
788 .build_client()
789 .try_into_blocking()
790 .unwrap();
791
792 let cloned = client.clone();
793 drop(client);
794 let response = cloned.get("http://example.com").send().unwrap();
795 assert_eq!(
796 response.try_into_json::<Output>().unwrap(),
797 Output { conn: 0, resp: 0 }
798 );
799 }
800
801 #[test]
802 fn default_blocking_http_client_is_cloneable_and_pooled() {
803 fn assert_default_client(_: &DefaultHttpWebClient) {}
804
805 let client = EasyHttpWebClient::try_blocking().unwrap();
806 assert_default_client(client.get_ref());
807 let cloned = client.clone();
808 drop(client);
809 let request = cloned.get("https://example.com").build().unwrap();
810 assert_eq!(request.uri(), &"https://example.com".parse().unwrap());
811 }
812
813 #[cfg(feature = "ws")]
814 #[test]
815 fn default_blocking_http_client_builds_websocket_requests() {
816 use crate::http::ws::handshake::client::BlockingHttpClientWebSocketExt as _;
817
818 let client = EasyHttpWebClient::try_blocking().unwrap();
819 let _from_url = client
820 .websocket("wss://example.com/chat")
821 .with_header("authorization", "Bearer secret");
822
823 let request = Request::builder()
824 .uri("wss://example.com/chat")
825 .body(Body::empty())
826 .unwrap();
827 let _from_request = client.websocket_with_request(request);
828 }
829
830 #[tokio::test]
831 async fn no_pool_tries_proxy_routes_in_order() {
832 let attempts = Arc::new(parking_lot::Mutex::new(Vec::new()));
833 let transport = service_fn({
834 let attempts = attempts.clone();
835 let direct = dummy_server::<ConnectRequest>();
836 move |input: ConnectRequest| {
837 let attempts = attempts.clone();
838 let direct = direct.clone();
839 async move {
840 let route = input.extensions.get_ref::<ProxyRoute>().unwrap();
841 attempts.lock().push(route.clone());
842 if route.proxy_address().is_some() {
843 Err(ConnectionError::transport(
844 BoxError::from_static_str("proxy unavailable"),
845 ConnectionErrorKind::Unavailable,
846 ))
847 } else {
848 direct.connect(input).await
849 }
850 }
851 }
852 });
853 let client = EasyHttpWebClient::connector_builder()
854 .with_custom_transport_connector(transport)
855 .without_dns_connector()
856 .without_tls_proxy_support()
857 .with_custom_proxy_connector(())
858 .without_tls_support()
859 .with_default_http_connector(Executor::default())
860 .without_connection_pool()
861 .build_client();
862 let proxy = ProxyRoute::Proxy("http://proxy.example:8080".parse::<ProxyAddress>().unwrap());
863 let request = || {
864 let request = Request::builder()
865 .uri("http://example.com")
866 .body(Body::empty())
867 .unwrap();
868 request
869 .extensions()
870 .insert(ProxyRoutes::new([proxy.clone(), ProxyRoute::Direct]));
871 request
872 };
873
874 for _ in 0..2 {
875 client
876 .serve(request())
877 .await
878 .context("serve request through direct fallback")
879 .unwrap();
880 }
881
882 assert_eq!(
883 attempts.lock().as_slice(),
884 [proxy, ProxyRoute::Direct, ProxyRoute::Direct]
885 );
886 }
887
888 #[tokio::test]
889 async fn no_proxy_tls_support_rejects_https_proxy() {
890 let client = EasyHttpWebClient::connector_builder()
891 .with_custom_transport_connector(dummy_server())
892 .without_dns_connector()
893 .without_tls_proxy_support()
894 .with_proxy_support()
895 .without_tls_support()
896 .with_default_http_connector(Executor::default())
897 .without_connection_pool()
898 .build_client();
899 let request = Request::builder()
900 .uri("http://example.com")
901 .body(Body::empty())
902 .unwrap();
903 request.extensions().insert(ProxyRoutes::new([
904 ProxyRoute::Proxy(
905 "https://proxy.example:8443"
906 .parse::<ProxyAddress>()
907 .unwrap(),
908 ),
909 ProxyRoute::Direct,
910 ]));
911
912 let error =
913 ConnectionError::from(client.serve(request).await.unwrap_err().into_box_error());
914 assert_eq!(error.domain(), ConnectionErrorDomain::Transport);
915 assert_eq!(error.kind(), ConnectionErrorKind::Protocol);
916 }
917
918 #[tokio::test]
919 async fn easy_client_pools_plaintext_proxy_versions_separately() {
920 let proxy: ProxyAddress = "http://proxy.example:8080".parse().unwrap();
921 let dials = Arc::new(AtomicUsize::new(0));
922 let transport = service_fn({
923 let inner = dummy_server::<ConnectRequest>();
924 let proxy = proxy.clone();
925 let dials = dials.clone();
926 move |input: ConnectRequest| {
927 let inner = inner.clone();
928 let proxy = proxy.clone();
929 let dials = dials.clone();
930 async move {
931 assert_eq!(
932 input.extensions.get_ref::<ConnectorTarget>(),
933 Some(&ConnectorTarget(proxy.address.clone())),
934 );
935 assert_eq!(
936 input
937 .extensions
938 .get_ref::<ProxyRoute>()
939 .and_then(ProxyRoute::proxy_address),
940 Some(&proxy),
941 );
942 dials.fetch_add(1, Ordering::Relaxed);
943 inner.connect(input).await
944 }
945 }
946 });
947 let client = EasyHttpWebClient::connector_builder()
948 .with_custom_transport_connector(transport)
949 .without_dns_connector()
950 .without_tls_proxy_support()
951 .with_proxy_support()
952 .without_tls_support()
953 .with_default_http_connector(Executor::default())
954 .with_default_connection_pool()
955 .build_client();
956
957 for (version, expected_conn) in [(Version::HTTP_11, 0), (Version::HTTP_2, 1)] {
958 let request = Request::builder()
959 .uri("http://example.com")
960 .version(version)
961 .body(Body::empty())
962 .unwrap();
963 request
964 .extensions()
965 .insert(ProxyRoutes::from(proxy.clone()));
966
967 let response = client.serve(request).await.unwrap();
968 assert_eq!(response.version(), version);
969 assert_eq!(
970 response.try_into_json::<Output>().await.unwrap(),
971 Output {
972 conn: expected_conn,
973 resp: 0,
974 },
975 );
976 }
977 assert_eq!(dials.load(Ordering::Relaxed), 2);
978 }
979
980 #[cfg(feature = "socks5")]
981 #[tokio::test]
982 async fn umbrella_proxy_connector_falls_back_across_supported_plan() {
983 let attempts = Arc::new(parking_lot::Mutex::new(Vec::new()));
984 let transport = service_fn({
985 let attempts = attempts.clone();
986 let direct = dummy_server::<ConnectRequest>();
987 move |input: ConnectRequest| {
988 let attempts = attempts.clone();
989 let direct = direct.clone();
990 async move {
991 let route = input.extensions.get_ref::<ProxyRoute>().unwrap().clone();
992 attempts.lock().push(route.clone());
993 if route.proxy_address().is_some() {
994 Err(ConnectionError::transport(
995 BoxError::from_static_str("proxy unavailable"),
996 ConnectionErrorKind::Unavailable,
997 ))
998 } else {
999 direct.connect(input).await
1000 }
1001 }
1002 }
1003 });
1004 let client = EasyHttpWebClient::connector_builder()
1005 .with_custom_transport_connector(transport)
1006 .without_dns_connector()
1007 .without_tls_proxy_support()
1008 .with_proxy_support()
1009 .without_tls_support()
1010 .with_default_http_connector(Executor::default())
1011 .without_connection_pool()
1012 .build_client();
1013 let request = Request::builder()
1014 .uri("http://example.com")
1015 .body(Body::empty())
1016 .unwrap();
1017 let socks = ProxyRoute::Proxy(
1018 "socks5://socks.example:1080"
1019 .parse::<ProxyAddress>()
1020 .unwrap(),
1021 );
1022 let http = ProxyRoute::Proxy("http://http.example:8080".parse::<ProxyAddress>().unwrap());
1023 request.extensions().insert(ProxyRoutes::new([
1024 socks.clone(),
1025 http.clone(),
1026 ProxyRoute::Direct,
1027 ]));
1028
1029 let response = client.serve(request).await.unwrap();
1030 let output = response.try_into_json::<Output>().await.unwrap();
1031 assert_eq!(output, Output { conn: 0, resp: 0 });
1032 assert_eq!(
1033 attempts.lock().as_slice(),
1034 [socks, http, ProxyRoute::Direct]
1035 );
1036 }
1037
1038 #[tokio::test]
1039 async fn default_pool_caches_failed_route_and_reuses_selected_connection() {
1040 let attempts = Arc::new(parking_lot::Mutex::new(Vec::new()));
1041 let transport = service_fn({
1042 let attempts = attempts.clone();
1043 let direct = dummy_server::<ConnectRequest>();
1044 move |input: ConnectRequest| {
1045 let attempts = attempts.clone();
1046 let direct = direct.clone();
1047 async move {
1048 let route = input.extensions.get_ref::<ProxyRoute>().unwrap();
1049 attempts.lock().push(route.clone());
1050 if route.proxy_address().is_some() {
1051 Err(ConnectionError::transport(
1052 BoxError::from_static_str("proxy unavailable"),
1053 ConnectionErrorKind::Unavailable,
1054 ))
1055 } else {
1056 direct.connect(input).await
1057 }
1058 }
1059 }
1060 });
1061 let client = EasyHttpWebClient::connector_builder()
1062 .with_custom_transport_connector(transport)
1063 .without_dns_connector()
1064 .without_tls_proxy_support()
1065 .with_custom_proxy_connector(())
1066 .without_tls_support()
1067 .with_default_http_connector(Executor::default())
1068 .with_default_connection_pool()
1069 .build_client();
1070 let proxy = ProxyRoute::Proxy("http://proxy.example:8080".parse::<ProxyAddress>().unwrap());
1071 let request = || {
1072 let request = Request::builder()
1073 .uri("http://example.com")
1074 .body(Body::empty())
1075 .unwrap();
1076 request
1077 .extensions()
1078 .insert(ProxyRoutes::new([proxy.clone(), ProxyRoute::Direct]));
1079 request
1080 };
1081
1082 for expected_response_index in 0..2 {
1083 let response = client.serve(request()).await.unwrap();
1084 let output = response.try_into_json::<Output>().await.unwrap();
1085 assert_eq!(output.conn, 0);
1086 assert_eq!(output.resp, expected_response_index);
1087 }
1088
1089 assert_eq!(attempts.lock().as_slice(), [proxy, ProxyRoute::Direct]);
1090 }
1091
1092 #[tokio::test]
1093 async fn easy_client_can_disable_proxy_route_failure_cache() {
1094 let attempts = Arc::new(parking_lot::Mutex::new(Vec::new()));
1095 let transport = service_fn({
1096 let attempts = attempts.clone();
1097 let direct = dummy_server::<ConnectRequest>();
1098 move |input: ConnectRequest| {
1099 let attempts = attempts.clone();
1100 let direct = direct.clone();
1101 async move {
1102 let route = input.extensions.get_ref::<ProxyRoute>().unwrap();
1103 attempts.lock().push(route.clone());
1104 if route.proxy_address().is_some() {
1105 Err(ConnectionError::transport(
1106 BoxError::from_static_str("proxy unavailable"),
1107 ConnectionErrorKind::Unavailable,
1108 ))
1109 } else {
1110 direct.connect(input).await
1111 }
1112 }
1113 }
1114 });
1115 let client = EasyHttpWebClient::connector_builder()
1116 .with_custom_transport_connector(transport)
1117 .without_dns_connector()
1118 .without_tls_proxy_support()
1119 .with_custom_proxy_connector(())
1120 .without_tls_support()
1121 .with_default_http_connector(Executor::default())
1122 .without_proxy_route_failure_cache()
1123 .without_connection_pool()
1124 .build_client();
1125 let proxy = ProxyRoute::Proxy("http://proxy.example:8080".parse().unwrap());
1126
1127 for _ in 0..2 {
1128 let request = Request::builder()
1129 .uri("http://example.com")
1130 .body(Body::empty())
1131 .unwrap();
1132 request
1133 .extensions()
1134 .insert(ProxyRoutes::new([proxy.clone(), ProxyRoute::Direct]));
1135 client.serve(request).await.unwrap();
1136 }
1137
1138 assert_eq!(
1139 attempts.lock().as_slice(),
1140 [proxy.clone(), ProxyRoute::Direct, proxy, ProxyRoute::Direct]
1141 );
1142 }
1143
1144 #[tokio::test]
1145 async fn proxy_free_easy_client_omits_proxy_route_failure_cache() {
1146 let attempts = Arc::new(parking_lot::Mutex::new(Vec::new()));
1147 let transport = service_fn({
1148 let attempts = attempts.clone();
1149 let direct = dummy_server::<ConnectRequest>();
1150 move |input: ConnectRequest| {
1151 let attempts = attempts.clone();
1152 let direct = direct.clone();
1153 async move {
1154 let route = input.extensions.get_ref::<ProxyRoute>().unwrap();
1155 attempts.lock().push(route.clone());
1156 if route.proxy_address().is_some() {
1157 Err(ConnectionError::transport(
1158 BoxError::from_static_str("proxy unavailable"),
1159 ConnectionErrorKind::Unavailable,
1160 ))
1161 } else {
1162 direct.connect(input).await
1163 }
1164 }
1165 }
1166 });
1167 let client = EasyHttpWebClient::connector_builder()
1168 .with_custom_transport_connector(transport)
1169 .without_dns_connector()
1170 .without_tls_proxy_support()
1171 .without_proxy_support()
1172 .without_tls_support()
1173 .with_default_http_connector(Executor::default())
1174 .without_connection_pool()
1175 .build_client();
1176 let proxy = ProxyRoute::Proxy("http://proxy.example:8080".parse().unwrap());
1177
1178 for _ in 0..2 {
1179 let request = Request::builder()
1180 .uri("http://example.com")
1181 .body(Body::empty())
1182 .unwrap();
1183 request
1184 .extensions()
1185 .insert(ProxyRoutes::new([proxy.clone(), ProxyRoute::Direct]));
1186 client.serve(request).await.unwrap();
1187 }
1188
1189 assert_eq!(
1190 attempts.lock().as_slice(),
1191 [proxy.clone(), ProxyRoute::Direct, proxy, ProxyRoute::Direct]
1192 );
1193 }
1194
1195 #[tokio::test]
1196 async fn easy_client_accepts_custom_proxy_route_failure_cache() {
1197 let attempts = Arc::new(parking_lot::Mutex::new(Vec::new()));
1198 let transport = service_fn({
1199 let attempts = attempts.clone();
1200 let direct = dummy_server::<ConnectRequest>();
1201 move |input: ConnectRequest| {
1202 let attempts = attempts.clone();
1203 let direct = direct.clone();
1204 async move {
1205 let route = input.extensions.get_ref::<ProxyRoute>().unwrap();
1206 attempts.lock().push(route.clone());
1207 if route.proxy_address().is_some() {
1208 Err(ConnectionError::transport(
1209 BoxError::from_static_str("proxy unavailable"),
1210 ConnectionErrorKind::Unavailable,
1211 ))
1212 } else {
1213 direct.connect(input).await
1214 }
1215 }
1216 }
1217 });
1218 let mut failure_cache_config = ProxyRouteFailureCacheConfig::default();
1219 failure_cache_config.scope = ProxyRouteFailureCacheScope::PerProxy;
1220 let failure_cache = ProxyRouteFailureCache::try_new(failure_cache_config).unwrap();
1221 let client = EasyHttpWebClient::connector_builder()
1222 .with_custom_transport_connector(transport)
1223 .without_dns_connector()
1224 .without_tls_proxy_support()
1225 .without_proxy_support()
1226 .without_tls_support()
1227 .with_default_http_connector(Executor::default())
1228 .with_proxy_route_failure_cache(failure_cache)
1229 .without_connection_pool()
1230 .build_client();
1231 let proxy = ProxyRoute::Proxy("http://proxy.example:8080".parse().unwrap());
1232
1233 for destination in ["one.example", "two.example"] {
1234 let request = Request::builder()
1235 .uri(format!("http://{destination}"))
1236 .body(Body::empty())
1237 .unwrap();
1238 request
1239 .extensions()
1240 .insert(ProxyRoutes::new([proxy.clone(), ProxyRoute::Direct]));
1241 client.serve(request).await.unwrap();
1242 }
1243
1244 assert_eq!(
1245 attempts.lock().as_slice(),
1246 [proxy, ProxyRoute::Direct, ProxyRoute::Direct]
1247 );
1248 }
1249
1250 #[cfg(all(feature = "rustls", any(feature = "aws-lc", feature = "ring")))]
1251 #[tokio::test]
1252 async fn rustls_https_proxy_alpn_is_scoped_across_connect() {
1253 use crate::{
1254 extensions::ExtensionsRef as _,
1255 net::{
1256 Protocol,
1257 address::HostWithPort,
1258 client::{EstablishedClientConnection, ProxyRoute},
1259 stream::service::EchoService,
1260 },
1261 tls::{
1262 client::{NegotiatedTlsParameters, TlsClientConfig},
1263 rustls::{client::TlsConnector, server::TlsAcceptorLayer},
1264 server::{GeneratedServerAuthConfig, ServerAuthData, TlsServerConfig},
1265 },
1266 };
1267 use rama_core::ServiceInput;
1268 use rama_crypto::cert::generate_server_auth;
1269 use rama_http::io::upgrade::handle_upgrade;
1270 use rama_http_backend::client::proxy::layer::HttpProxyConnectorLayer;
1271 use rama_net::http::TargetHttpVersion;
1272 use std::sync::Arc;
1273
1274 let (proxy_chain, proxy_key) =
1275 generate_server_auth(GeneratedServerAuthConfig::default()).expect("proxy auth");
1276 let proxy_trust = proxy_chain.last().expect("proxy trust anchor").clone();
1277 let (origin_chain, origin_key) =
1278 generate_server_auth(GeneratedServerAuthConfig::default()).expect("origin auth");
1279 let origin_trust = origin_chain.last().expect("origin trust anchor").clone();
1280
1281 let origin_server =
1282 TlsAcceptorLayer::new(TlsServerConfig::new().with_single_cert(ServerAuthData {
1283 cert_chain: origin_chain,
1284 private_key: origin_key,
1285 ocsp: None,
1286 }))
1287 .into_layer(EchoService::new());
1288 let (origin_done_tx, origin_done_rx) = tokio::sync::oneshot::channel();
1289 let origin_done_tx = Arc::new(parking_lot::Mutex::new(Some(origin_done_tx)));
1290
1291 let connect_version = Arc::new(parking_lot::Mutex::new(None));
1292 let observed_version = connect_version.clone();
1293 let proxy_http =
1294 HttpServer::auto(Executor::default()).service(service_fn(move |req: Request| {
1295 let origin_server = origin_server.clone();
1296 let origin_done_tx = origin_done_tx.clone();
1297 let observed_version = observed_version.clone();
1298 async move {
1299 assert_eq!(req.method(), rama_http::Method::CONNECT);
1300 *observed_version.lock() = Some(req.version());
1301 let upgrade = handle_upgrade(&req);
1302 tokio::spawn(async move {
1303 let tunnel = upgrade.await.expect("server CONNECT upgrade");
1304 let _origin_result = origin_server.serve(tunnel).await;
1308 if let Some(tx) = origin_done_tx.lock().take() {
1309 tx.send(()).expect("origin completion receiver");
1310 }
1311 });
1312 Ok::<_, Infallible>(Response::new(Body::empty()))
1313 }
1314 }));
1315 let proxy_server = TlsAcceptorLayer::new(
1316 TlsServerConfig::new()
1317 .with_single_cert(ServerAuthData {
1318 cert_chain: proxy_chain,
1319 private_key: proxy_key,
1320 ocsp: None,
1321 })
1322 .with_alpn_http_2(),
1323 )
1324 .into_layer(proxy_http);
1325
1326 let (client_io, server_io) = tokio::io::duplex(64 * 1024);
1327 let client_io = Arc::new(parking_lot::Mutex::new(Some(client_io)));
1328 let transport = service_fn(move |input: ConnectRequest| {
1329 let conn = ServiceInput::new(client_io.lock().take().expect("one proxy connection"));
1330 async move { Ok::<_, ConnectionError>(EstablishedClientConnection { input, conn }) }
1331 });
1332
1333 let proxy_config = TlsClientConfig::new()
1334 .with_alpn_http_2()
1335 .with_server_name(crate::net::address::Host::from_static("localhost"))
1336 .try_with_server_trust_anchors([proxy_trust])
1337 .expect("proxy trust");
1338 let proxy_tls = TlsConnector::tunnel(transport, None).with_base_config(proxy_config);
1339 let proxy = HttpProxyConnectorLayer::default().into_layer(proxy_tls);
1340 let origin_config = TlsClientConfig::new()
1341 .with_alpn(Default::default())
1342 .with_server_name(crate::net::address::Host::from_static("localhost"))
1343 .try_with_server_trust_anchors([origin_trust])
1344 .expect("origin trust");
1345 let connector = TlsConnector::auto(proxy).with_base_config(origin_config);
1346
1347 let input = ConnectRequest::new(HostWithPort::try_from("localhost:443").unwrap())
1348 .with_application_protocol(Protocol::HTTPS);
1349 input
1350 .extensions
1351 .insert(ProxyRoute::Proxy("https://localhost:8443".parse().unwrap()));
1352 let client = async move {
1353 let established = Box::pin(connector.serve(input))
1354 .await
1355 .expect("two TLS handshakes");
1356
1357 assert_eq!(*connect_version.lock(), Some(Version::HTTP_2));
1358 assert_eq!(
1359 established
1360 .conn
1361 .extensions()
1362 .get_ref::<NegotiatedTlsParameters>()
1363 .expect("origin TLS parameters")
1364 .application_layer_protocol,
1365 None
1366 );
1367 assert!(
1368 established
1369 .conn
1370 .extensions()
1371 .get_ref::<TargetHttpVersion>()
1372 .is_none(),
1373 "proxy HTTP/2 must not leak past CONNECT into a no-ALPN origin"
1374 );
1375 drop(established);
1376 };
1377 let (proxy_result, ()) =
1378 Box::pin(tokio::time::timeout(Duration::from_secs(5), async move {
1379 tokio::join!(proxy_server.serve(ServiceInput::new(server_io)), client)
1380 }))
1381 .await
1382 .expect("proxy/origin exchange");
1383 proxy_result.expect("proxy server");
1384 tokio::time::timeout(Duration::from_secs(5), origin_done_rx)
1385 .await
1386 .expect("origin server shutdown")
1387 .expect("origin completion signal");
1388 }
1389
1390 #[cfg(feature = "boring")]
1391 #[tokio::test]
1392 async fn boring_https_proxy_alpn_is_scoped_across_connect() {
1393 use crate::{
1394 extensions::ExtensionsRef as _,
1395 net::{
1396 Protocol,
1397 address::HostWithPort,
1398 client::{EstablishedClientConnection, ProxyRoute},
1399 stream::service::EchoService,
1400 },
1401 tls::{
1402 boring::{client::TlsConnector, server::TlsAcceptorLayer},
1403 client::{NegotiatedTlsParameters, TlsClientConfig},
1404 server::{GeneratedServerAuthConfig, ServerAuthData, TlsServerConfig},
1405 },
1406 };
1407 use rama_core::ServiceInput;
1408 use rama_crypto::cert::generate_server_auth;
1409 use rama_http::io::upgrade::handle_upgrade;
1410 use rama_http_backend::client::proxy::layer::HttpProxyConnectorLayer;
1411 use rama_net::http::TargetHttpVersion;
1412 use std::sync::Arc;
1413
1414 let (proxy_chain, proxy_key) =
1415 generate_server_auth(GeneratedServerAuthConfig::default()).expect("proxy auth");
1416 let proxy_trust = proxy_chain.last().expect("proxy trust anchor").clone();
1417 let (origin_chain, origin_key) =
1418 generate_server_auth(GeneratedServerAuthConfig::default()).expect("origin auth");
1419 let origin_trust = origin_chain.last().expect("origin trust anchor").clone();
1420
1421 let origin_server =
1422 TlsAcceptorLayer::new(TlsServerConfig::new().with_single_cert(ServerAuthData {
1423 cert_chain: origin_chain,
1424 private_key: origin_key,
1425 ocsp: None,
1426 }))
1427 .into_layer(EchoService::new());
1428 let (origin_done_tx, origin_done_rx) = tokio::sync::oneshot::channel();
1429 let origin_done_tx = Arc::new(parking_lot::Mutex::new(Some(origin_done_tx)));
1430
1431 let connect_version = Arc::new(parking_lot::Mutex::new(None));
1432 let observed_version = connect_version.clone();
1433 let proxy_http =
1434 HttpServer::auto(Executor::default()).service(service_fn(move |req: Request| {
1435 let origin_server = origin_server.clone();
1436 let origin_done_tx = origin_done_tx.clone();
1437 let observed_version = observed_version.clone();
1438 async move {
1439 assert_eq!(req.method(), rama_http::Method::CONNECT);
1440 *observed_version.lock() = Some(req.version());
1441 let upgrade = handle_upgrade(&req);
1442 tokio::spawn(async move {
1443 let tunnel = upgrade.await.expect("server CONNECT upgrade");
1444 let _origin_result = origin_server.serve(tunnel).await;
1448 if let Some(tx) = origin_done_tx.lock().take() {
1449 tx.send(()).expect("origin completion receiver");
1450 }
1451 });
1452 Ok::<_, Infallible>(Response::new(Body::empty()))
1453 }
1454 }));
1455 let proxy_server = TlsAcceptorLayer::new(
1456 TlsServerConfig::new()
1457 .with_single_cert(ServerAuthData {
1458 cert_chain: proxy_chain,
1459 private_key: proxy_key,
1460 ocsp: None,
1461 })
1462 .with_alpn_http_2(),
1463 )
1464 .into_layer(proxy_http);
1465
1466 let (client_io, server_io) = tokio::io::duplex(64 * 1024);
1467 let client_io = Arc::new(parking_lot::Mutex::new(Some(client_io)));
1468 let transport = service_fn(move |input: ConnectRequest| {
1469 let conn = ServiceInput::new(client_io.lock().take().expect("one proxy connection"));
1470 async move { Ok::<_, ConnectionError>(EstablishedClientConnection { input, conn }) }
1471 });
1472
1473 let proxy_config = TlsClientConfig::new()
1474 .with_alpn_http_2()
1475 .with_server_name(crate::net::address::Host::from_static("localhost"))
1476 .try_with_server_trust_anchors([proxy_trust])
1477 .expect("proxy trust");
1478 let proxy_tls = TlsConnector::tunnel(transport, None).with_base_config(proxy_config);
1479 let proxy = HttpProxyConnectorLayer::default().into_layer(proxy_tls);
1480 let origin_config = TlsClientConfig::new()
1481 .with_alpn(Default::default())
1482 .with_server_name(crate::net::address::Host::from_static("localhost"))
1483 .try_with_server_trust_anchors([origin_trust])
1484 .expect("origin trust");
1485 let connector = TlsConnector::auto(proxy).with_base_config(origin_config);
1486
1487 let input = ConnectRequest::new(HostWithPort::try_from("localhost:443").unwrap())
1488 .with_application_protocol(Protocol::HTTPS);
1489 input
1490 .extensions
1491 .insert(ProxyRoute::Proxy("https://localhost:8443".parse().unwrap()));
1492 let client = async move {
1493 let established = connector.serve(input).await.expect("two TLS handshakes");
1494
1495 assert_eq!(*connect_version.lock(), Some(Version::HTTP_2));
1496 assert_eq!(
1497 established
1498 .conn
1499 .extensions()
1500 .get_ref::<NegotiatedTlsParameters>()
1501 .expect("origin TLS parameters")
1502 .application_layer_protocol,
1503 None
1504 );
1505 assert!(
1506 established
1507 .conn
1508 .extensions()
1509 .get_ref::<TargetHttpVersion>()
1510 .is_none(),
1511 "proxy HTTP/2 must not leak past CONNECT into a no-ALPN origin"
1512 );
1513 drop(established);
1514 };
1515 let (proxy_result, ()) =
1516 Box::pin(tokio::time::timeout(Duration::from_secs(5), async move {
1517 tokio::join!(proxy_server.serve(ServiceInput::new(server_io)), client)
1518 }))
1519 .await
1520 .expect("proxy/origin exchange");
1521 proxy_result.expect("proxy server");
1522 tokio::time::timeout(Duration::from_secs(5), origin_done_rx)
1523 .await
1524 .expect("origin server shutdown")
1525 .expect("origin completion signal");
1526 }
1527
1528 #[cfg(feature = "boring")]
1529 #[tokio::test]
1530 async fn default_client_resumes_tls_sessions() {
1531 let exec = Executor::default();
1532 let resumed = Arc::new(parking_lot::Mutex::new(Vec::new()));
1533 let record = resumed.clone();
1534 let server = TlsAcceptorLayer::new(
1535 TlsServerConfig::new()
1536 .with_server_auth(
1537 ServerAuthData::new_generated(GeneratedServerAuthConfig::default()).unwrap(),
1538 )
1539 .with_alpn_http_1(),
1540 )
1541 .with_session_resumption(true)
1542 .into_layer(
1543 MapInputLayer::new(move |stream: TlsStream<TcpStream>| {
1544 let params = stream.extensions().get_ref::<NegotiatedTlsParameters>();
1545 record.lock().push(params.and_then(|params| params.resumed));
1546 stream
1547 })
1548 .into_layer(HttpServer::new_http1(exec.clone()).service(service_fn(
1549 async |_: Request| {
1550 Ok::<_, Infallible>(
1552 Response::builder()
1553 .header(CONNECTION, "close")
1554 .body(Body::empty())
1555 .unwrap(),
1556 )
1557 },
1558 ))),
1559 );
1560 let listener = TcpListener::bind_address(SocketAddress::local_ipv4(0), exec)
1561 .await
1562 .unwrap();
1563 let port = listener.local_addr().unwrap().port();
1564 let server = tokio::spawn(listener.serve(server));
1565
1566 let client = DefaultHttpWebClient::<Body>::default();
1567 for _ in 0..2 {
1568 let request = Request::builder()
1569 .uri(format!("https://localhost:{port}/"))
1570 .body(Body::empty())
1571 .unwrap();
1572 request
1573 .extensions()
1574 .insert(TlsServerVerify(ServerVerifyMode::Disable));
1575 let response = tokio::time::timeout(Duration::from_secs(5), client.serve(request))
1576 .await
1577 .unwrap()
1578 .unwrap();
1579 response.try_into_string().await.unwrap();
1580 }
1581 server.abort();
1582 assert_eq!(*resumed.lock(), [Some(false), Some(true)]);
1583 }
1584
1585 #[cfg(feature = "boring")]
1586 #[test]
1587 fn proxy_failure_cache_keeps_tls_client_future_bounded() {
1588 let client = EasyHttpWebClient::connector_builder()
1589 .with_default_transport_connector()
1590 .with_default_dns_connector()
1591 .without_tls_proxy_support()
1592 .with_proxy_support()
1593 .with_tls_support_using_boringssl_and_default_http_version(
1594 crate::tls::client::TlsClientConfig::default_http(),
1595 Version::HTTP_11,
1596 )
1597 .with_default_http_connector(Executor::default())
1598 .without_connection_pool()
1599 .build_client();
1600 let request = Request::builder()
1601 .uri("https://example.com")
1602 .body(Body::empty())
1603 .unwrap();
1604
1605 let future = client.serve(request);
1606 let future_size = std::mem::size_of_val(&future);
1607
1608 assert!(
1609 future_size < 64 * 1024,
1610 "easy TLS client future is unexpectedly large: {future_size} bytes"
1611 );
1612 }
1613
1614 #[tokio::test]
1615 async fn connection_is_in_use_until_response_body_is_consumed() {
1616 let client = EasyHttpWebClient::connector_builder()
1617 .with_custom_transport_connector(dummy_server())
1618 .without_dns_connector()
1619 .without_tls_proxy_support()
1620 .without_proxy_support()
1621 .without_tls_support()
1622 .with_default_http_connector(Executor::default())
1623 .with_connection_pool(HttpPooledConnectorConfig {
1624 max_streams_per_connection: NonZeroUsize::new(1),
1625 max_connections_total: NonZeroUsize::new(4),
1626 ..Default::default()
1627 })
1628 .build_client();
1629
1630 let req = || {
1631 Request::builder()
1632 .uri("http://example.com")
1633 .version(Version::HTTP_2)
1634 .body(Body::empty())
1635 .unwrap()
1636 };
1637
1638 let res1 = client.serve(req()).await.unwrap();
1642 let res2 = client.serve(req()).await.unwrap();
1643
1644 let out2 = res2.try_into_json::<Output>().await.unwrap();
1646 let out1 = res1.try_into_json::<Output>().await.unwrap();
1647
1648 assert_eq!(out1.conn, 0, "first request uses the first connection");
1649 assert_eq!(
1652 out2.conn, 1,
1653 "second request must not reuse a connection whose response body is still in flight"
1654 );
1655 }
1656
1657 #[tokio::test]
1661 async fn default_pool_multiplexes_on_h2() {
1662 let client = EasyHttpWebClient::connector_builder()
1663 .with_custom_transport_connector(dummy_server())
1664 .without_dns_connector()
1665 .without_tls_proxy_support()
1666 .without_proxy_support()
1667 .without_tls_support()
1668 .with_default_http_connector(Executor::default())
1669 .with_default_connection_pool()
1670 .build_client();
1671
1672 let req = || {
1673 Request::builder()
1674 .uri("http://example.com")
1675 .version(Version::HTTP_2)
1676 .body(Body::empty())
1677 .unwrap()
1678 };
1679 let (res1, res2, res3) = tokio::join!(
1680 client.serve(req()),
1681 client.serve(req()),
1682 client.serve(req()),
1683 );
1684
1685 for (i, res) in [res1, res2, res3].into_iter().enumerate() {
1687 let out = res.unwrap().try_into_json::<Output>().await.unwrap();
1688 assert_eq!(out.conn, 0);
1689 assert_eq!(out.resp, i);
1690 }
1691 }
1692
1693 #[tokio::test]
1694 async fn default_pool_does_not_multiplexes_on_h1() {
1695 let client = EasyHttpWebClient::connector_builder()
1696 .with_custom_transport_connector(dummy_server())
1697 .without_dns_connector()
1698 .without_tls_proxy_support()
1699 .without_proxy_support()
1700 .without_tls_support()
1701 .with_default_http_connector(Executor::default())
1702 .with_default_connection_pool()
1703 .build_client();
1704
1705 let req = || {
1706 Request::builder()
1707 .uri("http://example.com")
1708 .version(Version::HTTP_11)
1709 .body(Body::empty())
1710 .unwrap()
1711 };
1712 let (res1, res2, res3) = tokio::join!(
1713 client.serve(req()),
1714 client.serve(req()),
1715 client.serve(req()),
1716 );
1717
1718 for (i, res) in [res1, res2, res3].into_iter().enumerate() {
1721 let out = res.unwrap().try_into_json::<Output>().await.unwrap();
1722 assert_eq!(out.conn, i);
1723 assert_eq!(out.resp, 0);
1724 }
1725 }
1726
1727 #[tokio::test]
1728 async fn multiplex_on_h2_respects_limits() {
1729 let client = EasyHttpWebClient::connector_builder()
1730 .with_custom_transport_connector(dummy_server())
1731 .without_dns_connector()
1732 .without_tls_proxy_support()
1733 .without_proxy_support()
1734 .without_tls_support()
1735 .with_default_http_connector(Executor::default())
1736 .with_connection_pool(HttpPooledConnectorConfig {
1737 max_streams_per_connection: NonZeroUsize::new(2),
1738 ..Default::default()
1739 })
1740 .build_client();
1741
1742 let req = || {
1743 Request::builder()
1744 .uri("http://example.com")
1745 .version(Version::HTTP_2)
1746 .body(Body::empty())
1747 .unwrap()
1748 };
1749 let (res1, res2, res3, res4) = tokio::join!(
1750 client.serve(req()),
1751 client.serve(req()),
1752 client.serve(req()),
1753 client.serve(req()),
1754 );
1755
1756 for (i, res) in [res1, res2, res3, res4].into_iter().enumerate() {
1758 let out = res.unwrap().try_into_json::<Output>().await.unwrap();
1759 assert_eq!(out.conn, i / 2);
1760 assert_eq!(out.resp, i % 2);
1761 }
1762 }
1763}