1use std::{error::Error, str::FromStr, sync::LazyLock};
4
5use futures::{
6 FutureExt, TryFutureExt,
7 future::{self, Either},
8};
9use http::{
10 header,
11 uri::{Authority, Scheme, Uri},
12 version::Version,
13};
14use hyper::client::conn;
15use hyper_util::rt::TokioIo;
16use motore::{make::MakeConnection, service::Service};
17use volo::{context::Context, net::Address};
18
19use super::{
20 connector::{HttpMakeConnection, PeerInfo},
21 pool::{self, Connecting, Pool, Poolable, Pooled, Reservation},
22};
23use crate::{
24 body::Body,
25 context::ClientContext,
26 error::{
27 BoxError, ClientError,
28 client::{Result, connect_error, no_address, request_error, retry, tri},
29 },
30 request::Request,
31 response::Response,
32 utils::lazy::Started,
33};
34
35#[derive(Default)]
37pub(crate) struct ClientConfig {
38 #[cfg(feature = "http1")]
39 pub h1: super::http1::Config,
40 #[cfg(feature = "http2")]
41 pub h2: super::http2::Config,
42}
43
44#[derive(Clone)]
45pub(crate) struct ClientTransportConfig {
46 pub stat_enable: bool,
47 #[cfg(feature = "__tls")]
48 #[cfg_attr(docsrs, doc(cfg(any(feature = "rustls", feature = "native-tls"))))]
49 pub disable_tls: bool,
50}
51
52impl Default for ClientTransportConfig {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl ClientTransportConfig {
59 pub fn new() -> Self {
60 Self {
61 stat_enable: true,
62 #[cfg(feature = "__tls")]
63 disable_tls: false,
64 }
65 }
66}
67
68pub struct ClientTransport<B = Body> {
77 #[cfg(feature = "http1")]
78 h1_client: conn::http1::Builder,
79 #[cfg(feature = "http2")]
80 h2_client: conn::http2::Builder<hyper_util::rt::TokioExecutor>,
81 config: ClientTransportConfig,
82 connector: HttpMakeConnection,
83 pool: Pool<PoolKey, HttpConnection<B>>,
84}
85
86#[cfg(feature = "__tls")]
87type PoolKey = (Scheme, Address, Option<faststr::FastStr>);
88
89#[cfg(not(feature = "__tls"))]
90type PoolKey = (Scheme, Address);
91
92impl<B> ClientTransport<B> {
93 pub(crate) fn new(
94 http_config: ClientConfig,
95 transport_config: ClientTransportConfig,
96 pool_config: pool::Config,
97 #[cfg(feature = "__tls")] tls_connector: Option<volo::net::tls::TlsConnector>,
98 ) -> Self {
99 #[cfg(feature = "http1")]
100 let h1_client = super::http1::client(&http_config.h1);
101 #[cfg(feature = "http2")]
102 let h2_client = super::http2::client(&http_config.h2);
103
104 let builder = HttpMakeConnection::builder(&transport_config);
105 #[cfg(feature = "__tls")]
106 let builder = match tls_connector {
107 Some(connector) => builder.with_tls_connector(connector),
108 None => builder,
109 };
110 let connector = builder.build();
111
112 Self {
113 #[cfg(feature = "http1")]
114 h1_client,
115 #[cfg(feature = "http2")]
116 h2_client,
117 config: transport_config,
118 connector,
119 pool: Pool::new(pool_config),
120 }
121 }
122
123 fn connect_to(
124 &self,
125 ver: pool::Ver,
126 peer: PeerInfo,
127 ) -> impl Started<Output = Result<Pooled<PoolKey, HttpConnection<B>>>> + Send + 'static
128 where
129 B: http_body::Body + Unpin + Send + 'static,
130 B::Data: Send,
131 B::Error: Into<BoxError> + 'static,
132 {
133 let key = pool_key(&peer);
134 let connector = self.connector.clone();
135 let pool = self.pool.clone();
136 #[cfg(feature = "http1")]
137 let h1_client = self.h1_client.clone();
138 #[cfg(feature = "http2")]
139 let h2_client = self.h2_client.clone();
140
141 crate::utils::lazy::lazy(move || {
142 let connecting = match pool.connecting(&key, ver) {
143 Some(lock) => lock,
144 None => return Either::Right(future::err(retry())),
145 };
146 Either::Left(Box::pin(connect_impl(
147 ver,
148 peer,
149 connector,
150 pool,
151 connecting,
152 #[cfg(feature = "http1")]
153 h1_client,
154 #[cfg(feature = "http2")]
155 h2_client,
156 )))
157 })
158 }
159
160 async fn pooled_connect(
161 &self,
162 ver: Version,
163 peer: PeerInfo,
164 ) -> Result<Pooled<PoolKey, HttpConnection<B>>>
165 where
166 B: http_body::Body + Unpin + Send + 'static,
167 B::Data: Send,
168 B::Error: Into<BoxError> + 'static,
169 {
170 let key = pool_key(&peer);
171
172 let checkout = self.pool.checkout(key);
173 let connect = self.connect_to(ver.into(), peer);
174
175 match future::select(checkout, connect).await {
177 Either::Left((Ok(checked_out), connecting)) => {
178 if connecting.started() {
180 let conn_fut = connecting
181 .map_err(|err| tracing::trace!("background connect error: {err}"))
182 .map(|_pooled| {
183 });
185 tokio::spawn(conn_fut);
187 }
188 Ok(checked_out)
189 }
190 Either::Right((Ok(connected), _checkout)) => Ok(connected),
191 Either::Left((Err(err), connecting)) => {
192 if err.is_canceled() {
194 connecting.await
195 } else {
196 Err(connect_error(err))
198 }
199 }
200 Either::Right((Err(err), checkout)) => {
201 if err
204 .source()
205 .is_some_and(<dyn Error>::is::<crate::error::client::Retry>)
206 {
207 checkout.await.map_err(connect_error)
208 } else {
209 Err(err)
211 }
212 }
213 }
214 }
215}
216
217fn pool_key(peer: &PeerInfo) -> PoolKey {
218 (
219 peer.scheme.clone(),
220 peer.address.clone(),
221 #[cfg(feature = "__tls")]
222 (peer.scheme == Scheme::HTTPS).then(|| peer.name.clone()),
223 )
224}
225
226async fn connect_impl<B>(
227 _ver: pool::Ver,
228 peer: PeerInfo,
229 connector: HttpMakeConnection,
230 pool: Pool<PoolKey, HttpConnection<B>>,
231 connecting: Connecting<PoolKey, HttpConnection<B>>,
232 #[cfg(feature = "http1")] h1_client: conn::http1::Builder,
233 #[cfg(feature = "http2")] h2_client: conn::http2::Builder<hyper_util::rt::TokioExecutor>,
234) -> Result<Pooled<PoolKey, HttpConnection<B>>>
235where
236 B: http_body::Body + Unpin + Send + 'static,
237 B::Data: Send,
238 B::Error: Into<BoxError> + 'static,
239{
240 #[cfg(feature = "http1")]
241 let key = pool_key(&peer);
242
243 let conn = match connector.make_connection(peer).await {
244 Ok(conn) => conn,
245 Err(err) => {
246 tracing::warn!("[Volo-HTTP] failed to make connection: {err}");
247 return Err(err);
248 }
249 };
250
251 #[cfg(feature = "http2")]
252 let use_h2 = conn_use_h2(_ver, &conn);
253 #[cfg(not(feature = "http2"))]
254 let use_h2 = false;
255
256 let conn = TokioIo::new(conn);
257 if use_h2 {
258 #[cfg(feature = "http2")]
259 {
260 let connecting = if _ver == pool::Ver::Auto {
261 tri!(connecting.alpn_h2(&pool).ok_or_else(retry))
262 } else {
263 connecting
264 };
265 let (mut sender, conn) = tri!(h2_client.handshake(conn).await.map_err(connect_error));
266 tokio::spawn(conn);
267 tri!(sender.ready().await.map_err(connect_error));
269 Ok(pool.pooled(connecting, HttpConnection::H2(sender)))
270 }
271 #[cfg(not(feature = "http2"))]
272 Err(crate::error::client::bad_version())
273 } else {
274 #[cfg(feature = "http1")]
275 {
276 let (mut sender, conn) = tri!(h1_client.handshake(conn).await.map_err(connect_error));
277
278 let (return_tx, return_rx) = tokio::sync::mpsc::unbounded_channel();
281
282 let driver = ManagedH1Connection {
284 connection: conn,
285 return_rx,
286 waiting: None,
287 returner: pool.return_handle(),
288 key,
289 };
290
291 tokio::spawn(driver);
292
293 tri!(sender.ready().await.map_err(connect_error));
296
297 let lease = H1Lease::new(sender, return_tx);
298 Ok(pool.pooled(connecting, HttpConnection::H1(lease)))
299 }
300 #[cfg(not(feature = "http1"))]
301 Err(crate::error::client::bad_version())
302 }
303}
304
305#[cfg(feature = "http2")]
306fn conn_use_h2(ver: pool::Ver, _conn: &volo::net::conn::Conn) -> bool {
307 #[cfg(feature = "__tls")]
308 let use_h2 = match _conn.stream.negotiated_alpn().as_deref() {
309 Some(alpn) => {
310 if alpn == b"h2" {
312 return true;
313 }
314 false
316 }
317 None => true,
319 };
320 #[cfg(not(feature = "__tls"))]
321 let use_h2 = true;
322
323 if use_h2 && (ver == pool::Ver::Http2 || cfg!(not(feature = "http1"))) {
325 return true;
326 }
327
328 false
329}
330
331impl<B> Service<ClientContext, Request<B>> for ClientTransport<B>
332where
333 B: http_body::Body + Unpin + Send + 'static,
334 B::Data: Send,
335 B::Error: Into<Box<dyn Error + Send + Sync>> + 'static,
336{
337 type Response = Response;
338 type Error = ClientError;
339
340 async fn call(
341 &self,
342 cx: &mut ClientContext,
343 mut req: Request<B>,
344 ) -> Result<Self::Response, Self::Error> {
345 rewrite_uri(cx, &mut req);
346
347 let callee = cx.rpc_info().callee();
348 let address = callee.address().ok_or_else(no_address)?;
349
350 let ver = req.version();
351 let peer = PeerInfo {
352 scheme: cx.target().scheme().cloned().unwrap_or(Scheme::HTTP),
353 address,
354 #[cfg(feature = "__tls")]
355 name: callee.service_name(),
356 };
357
358 let stat_enabled = self.config.stat_enable;
359 if stat_enabled {
360 cx.stats.record_transport_start_at();
361 }
362
363 let mut conn = tri!(self.pooled_connect(ver, peer).await);
364 let res = conn.send_request(req).await;
365
366 if stat_enabled {
367 cx.stats.record_transport_end_at();
368 }
369
370 res
371 }
372}
373
374#[cfg(feature = "http1")]
375struct H1Returned<B> {
376 http_sender: conn::http1::SendRequest<B>,
377 return_tx: tokio::sync::mpsc::UnboundedSender<H1Returned<B>>,
378}
379
380#[cfg(feature = "http1")]
381struct H1Lease<B> {
382 returned: Option<H1Returned<B>>,
383}
384
385#[cfg(feature = "http1")]
386struct H1ReturnGuard<B> {
387 returned: Option<H1Returned<B>>,
388}
389
390#[cfg(feature = "http1")]
391impl<B> H1ReturnGuard<B> {
392 fn http_sender_mut(&mut self) -> &mut conn::http1::SendRequest<B> {
393 &mut self
394 .returned
395 .as_mut()
396 .expect("HTTP/1 sender already returned")
397 .http_sender
398 }
399
400 fn return_to_driver(&mut self) {
401 let Some(returned) = self.returned.take() else {
402 return;
403 };
404
405 let tx = returned.return_tx.clone();
408 if let Err(_err) = tx.send(returned) {
409 tracing::trace!("HTTP/1 connection driver already closed");
415 };
416 }
417}
418
419#[cfg(feature = "http1")]
420impl<B> Drop for H1ReturnGuard<B> {
421 fn drop(&mut self) {
422 self.return_to_driver();
424 }
425}
426
427#[cfg(feature = "http1")]
428impl<B> H1Lease<B> {
429 fn new(
430 http_sender: conn::http1::SendRequest<B>,
431 return_tx: tokio::sync::mpsc::UnboundedSender<H1Returned<B>>,
432 ) -> Self {
433 Self {
434 returned: Some(H1Returned {
435 http_sender,
436 return_tx,
437 }),
438 }
439 }
440
441 fn from_returned(returned: H1Returned<B>) -> Self {
442 Self {
443 returned: Some(returned),
444 }
445 }
446
447 fn is_ready(&self) -> bool {
448 self.returned
449 .as_ref()
450 .is_some_and(|returned| returned.http_sender.is_ready())
451 }
452
453 async fn send_request(
454 &mut self,
455 req: Request<B>,
456 ) -> hyper::Result<http::Response<hyper::body::Incoming>>
457 where
458 B: http_body::Body + Send + 'static,
459 B::Data: Send,
460 B::Error: Into<BoxError> + 'static,
461 {
462 let returned = self
464 .returned
465 .take()
466 .expect("an HTTP/1 lease can only send one request");
467
468 let mut guard = H1ReturnGuard {
469 returned: Some(returned),
470 };
471
472 let result = guard.http_sender_mut().send_request(req).await;
473
474 guard.return_to_driver();
476 result
477 }
478}
479
480#[cfg(feature = "http1")]
481#[pin_project::pin_project]
482struct ManagedH1Connection<I, B>
483where
484 I: hyper::rt::Read + hyper::rt::Write,
485 B: http_body::Body + Send + 'static,
486{
487 #[pin]
488 connection: conn::http1::Connection<I, B>,
489 return_rx: tokio::sync::mpsc::UnboundedReceiver<H1Returned<B>>,
490 waiting: Option<H1Returned<B>>,
491 returner: pool::PoolReturn<PoolKey, HttpConnection<B>>,
492 key: PoolKey,
493}
494
495#[cfg(feature = "http1")]
496impl<I, B> Future for ManagedH1Connection<I, B>
497where
498 I: hyper::rt::Read + hyper::rt::Write + Unpin + Send + 'static,
499 B: http_body::Body + Unpin + Send + 'static,
500 B::Data: Send,
501 B::Error: Into<BoxError> + 'static,
502{
503 type Output = ();
504
505 fn poll(
506 self: std::pin::Pin<&mut Self>,
507 cx: &mut std::task::Context<'_>,
508 ) -> std::task::Poll<Self::Output> {
509 use std::task::Poll;
510
511 let this = self.project();
512
513 if this.waiting.is_none() {
515 match this.return_rx.poll_recv(cx) {
516 Poll::Ready(Some(returned)) => {
517 *this.waiting = Some(returned);
518 }
519 Poll::Ready(None) => {
520 tracing::trace!("HTTP/1 return channel closed");
522 return Poll::Ready(());
523 }
524 Poll::Pending => {}
525 }
526 }
527
528 match this.connection.poll(cx) {
530 Poll::Ready(Ok(())) => {
531 tracing::trace!("HTTP/1 connection driver completed");
532 return Poll::Ready(());
533 }
534 Poll::Ready(Err(err)) => {
535 tracing::trace!("HTTP/1 connection driver failed: {err}");
536 return Poll::Ready(());
537 }
538 Poll::Pending => {}
539 }
540
541 let Some(returned) = this.waiting.as_mut() else {
543 return Poll::Pending;
547 };
548
549 match returned.http_sender.poll_ready(cx) {
552 Poll::Pending => return Poll::Pending,
553 Poll::Ready(Err(err)) => {
554 tracing::trace!("HTTP/1 sender closed before becoming reusable: {err}");
555 return Poll::Ready(());
556 }
557 Poll::Ready(Ok(())) => {}
558 }
559
560 let returned = this
562 .waiting
563 .take()
564 .expect("HTTP/1 waiting sender disappeared");
565 let http_connection = HttpConnection::H1(H1Lease::from_returned(returned));
566
567 if let Err(_err) = this.returner.put_ready(this.key.clone(), http_connection) {
568 tracing::trace!("HTTP/1 pool dropped before the connection became reusable");
571 return Poll::Ready(());
572 }
573
574 match this.return_rx.poll_recv(cx) {
577 Poll::Ready(Some(returned)) => {
578 debug_assert!(this.waiting.is_none());
579 *this.waiting = Some(returned);
580 cx.waker().wake_by_ref();
581 Poll::Pending
582 }
583 Poll::Ready(None) => {
584 Poll::Ready(())
586 }
587 Poll::Pending => Poll::Pending,
588 }
589 }
590}
591
592enum HttpConnection<B> {
593 #[cfg(feature = "http1")]
594 H1(H1Lease<B>),
595 #[cfg(feature = "http2")]
596 H2(conn::http2::SendRequest<B>),
597}
598
599impl<B> Poolable for HttpConnection<B>
600where
601 B: Send + 'static,
602{
603 fn is_open(&self) -> bool {
604 match &self {
605 #[cfg(feature = "http1")]
606 Self::H1(h1) => h1.is_ready(),
607 #[cfg(feature = "http2")]
608 Self::H2(h2) => h2.is_ready(),
609 }
610 }
611
612 fn reserve(self) -> Reservation<Self> {
613 match self {
614 #[cfg(feature = "http1")]
615 Self::H1(h1) => Reservation::Unique(Self::H1(h1)),
616 #[cfg(feature = "http2")]
617 Self::H2(h2) => Reservation::Shared(Self::H2(h2.clone()), Self::H2(h2)),
618 }
619 }
620
621 fn can_share(&self) -> bool {
622 match self {
623 #[cfg(feature = "http1")]
624 Self::H1(_) => false,
625 #[cfg(feature = "http2")]
626 Self::H2(_) => true,
627 }
628 }
629}
630
631impl<B> HttpConnection<B>
632where
633 B: http_body::Body + Send + 'static,
634 B::Data: Send,
635 B::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
636{
637 pub async fn send_request(&mut self, req: Request<B>) -> Result<Response> {
638 let res = match self {
639 #[cfg(feature = "http1")]
640 Self::H1(h1) => h1.send_request(req).await,
641 #[cfg(feature = "http2")]
642 Self::H2(h2) => h2.send_request(req).await,
643 };
644 match res {
645 Ok(resp) => Ok(resp.map(Body::from_incoming)),
646 Err(err) => Err(request_error(err)),
647 }
648 }
649}
650
651static PLACEHOLDER: LazyLock<Authority> =
652 LazyLock::new(|| Authority::from_static("volo-http.placeholder"));
653
654fn gen_authority<B>(req: &Request<B>) -> Authority {
655 let Some(host) = req.headers().get(header::HOST) else {
656 return PLACEHOLDER.to_owned();
657 };
658 let Ok(host) = host.to_str() else {
659 return PLACEHOLDER.to_owned();
660 };
661 let Ok(authority) = Authority::from_str(host) else {
662 return PLACEHOLDER.to_owned();
663 };
664 authority
665}
666
667fn rewrite_uri<B>(cx: &ClientContext, req: &mut Request<B>) {
678 if req.version() != Version::HTTP_2 {
679 return;
680 }
681 let scheme = cx.target().scheme().cloned().unwrap_or(Scheme::HTTP);
682 let authority = gen_authority(req);
683 let mut parts = req.uri().to_owned().into_parts();
684 parts.scheme = Some(scheme);
685 parts.authority = Some(authority);
686 let Ok(uri) = Uri::from_parts(parts) else {
687 return;
688 };
689 *req.uri_mut() = uri;
690}
691
692#[cfg(all(test, feature = "http1"))]
693mod h1_connection_reuse_tests {
694 use std::{
695 future::{Future, pending, poll_fn},
696 net::{Ipv4Addr, SocketAddr},
697 task::Poll,
698 time::Duration,
699 };
700
701 use bytes::Bytes;
702 use http::{Request, header::HOST, uri::Scheme};
703 use http_body_util::Empty;
704 use hyper::client::conn;
705 use hyper_util::rt::TokioIo;
706 use tokio::{
707 io::{AsyncReadExt, AsyncWriteExt, DuplexStream},
708 sync::{mpsc, oneshot},
709 time::timeout,
710 };
711 use volo::net::Address;
712
713 use super::{H1Lease, HttpConnection, ManagedH1Connection, PoolKey, pool};
714 use crate::body::BodyConversion;
715
716 fn pool_key() -> PoolKey {
717 (
718 Scheme::HTTP,
719 Address::Ip(SocketAddr::from((Ipv4Addr::LOCALHOST, 80))),
720 #[cfg(feature = "__tls")]
721 None,
722 )
723 }
724
725 fn empty_request() -> Request<Empty<Bytes>> {
726 Request::builder()
727 .method("GET")
728 .uri("/")
729 .header(HOST, "example.test")
730 .body(Empty::new())
731 .expect("valid request")
732 }
733
734 async fn read_request_head(io: &mut DuplexStream) {
735 let mut head = Vec::new();
736 let mut byte = [0_u8; 1];
737
738 loop {
739 let read = io.read(&mut byte).await.expect("read request");
740 assert_ne!(read, 0, "client closed before sending a full request");
741 head.push(byte[0]);
742
743 if head.ends_with(b"\r\n\r\n") {
744 return;
745 }
746 }
747 }
748
749 #[tokio::test]
750 async fn managed_driver_reuses_only_after_response_body_eof() {
751 let (client_io, mut server_io) = tokio::io::duplex(4096);
752 let (head_sent_tx, head_sent_rx) = oneshot::channel();
753 let (release_body_tx, release_body_rx) = oneshot::channel();
754
755 let server_task = tokio::spawn(async move {
756 read_request_head(&mut server_io).await;
757 server_io
758 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n")
759 .await
760 .expect("write first response head");
761 head_sent_tx.send(()).expect("signal response head");
762
763 release_body_rx.await.expect("release first response body");
764 server_io
765 .write_all(b"pong")
766 .await
767 .expect("write first response body");
768
769 read_request_head(&mut server_io).await;
771 server_io
772 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok")
773 .await
774 .expect("write second response");
775
776 pending::<()>().await;
777 });
778
779 let (mut sender, connection) =
780 conn::http1::handshake::<_, Empty<Bytes>>(TokioIo::new(client_io))
781 .await
782 .expect("HTTP/1 handshake");
783
784 let pool = pool::Pool::new(pool::Config {
785 idle_timeout: Duration::from_secs(60),
786 max_idle_per_host: 16,
787 });
788 let key = pool_key();
789 let (return_tx, return_rx) = mpsc::unbounded_channel();
790
791 let driver_task = tokio::spawn(ManagedH1Connection {
792 connection,
793 return_rx,
794 waiting: None,
795 returner: pool.return_handle(),
796 key: key.clone(),
797 });
798
799 sender.ready().await.expect("initial sender readiness");
800 let connecting = pool
801 .connecting(&key, pool::Ver::Auto)
802 .expect("HTTP/1 does not serialize connects");
803 let mut pooled = pool.pooled(
804 connecting,
805 HttpConnection::H1(H1Lease::new(sender, return_tx)),
806 );
807
808 let response = pooled
809 .send_request(empty_request())
810 .await
811 .expect("first response head");
812 head_sent_rx.await.expect("server sent response head");
813
814 let mut early_checkout = Box::pin(pool.checkout(key.clone()));
817 let returned_too_early =
818 poll_fn(|cx| Poll::Ready(matches!(early_checkout.as_mut().poll(cx), Poll::Ready(_))))
819 .await;
820 assert!(!returned_too_early);
821 drop(early_checkout);
822
823 release_body_tx.send(()).expect("release response body");
824 assert_eq!(
825 response
826 .into_body()
827 .into_vec()
828 .await
829 .expect("collect first response"),
830 b"pong"
831 );
832
833 drop(pooled);
835
836 let mut reused = timeout(Duration::from_secs(1), pool.checkout(key.clone()))
837 .await
838 .expect("driver did not return the ready connection")
839 .expect("checkout failed");
840
841 let second = reused
842 .send_request(empty_request())
843 .await
844 .expect("second response head");
845 assert_eq!(
846 second
847 .into_body()
848 .into_vec()
849 .await
850 .expect("collect second response"),
851 b"ok"
852 );
853
854 drop(reused);
855 drop(pool);
856
857 timeout(Duration::from_secs(1), driver_task)
858 .await
859 .expect("driver should stop after the pool is dropped")
860 .expect("driver task panicked");
861 server_task.abort();
862 }
863
864 #[tokio::test]
865 async fn canceled_send_returns_sender_and_consumes_old_lease() {
866 let (client_io, mut server_io) = tokio::io::duplex(4096);
867 let (request_seen_tx, request_seen_rx) = oneshot::channel();
868
869 let server_task = tokio::spawn(async move {
870 read_request_head(&mut server_io).await;
871 request_seen_tx.send(()).expect("signal request received");
872
873 pending::<()>().await;
875 });
876
877 let (mut sender, connection) =
878 conn::http1::handshake::<_, Empty<Bytes>>(TokioIo::new(client_io))
879 .await
880 .expect("HTTP/1 handshake");
881 let connection_task = tokio::spawn(async move {
882 let _ = connection.await;
883 });
884
885 sender.ready().await.expect("initial sender readiness");
886 let (return_tx, mut return_rx) = mpsc::unbounded_channel();
887 let mut lease = H1Lease::new(sender, return_tx);
888 let mut send = Box::pin(lease.send_request(empty_request()));
889
890 tokio::select! {
891 result = &mut send => panic!("request completed unexpectedly: {result:?}"),
892 result = request_seen_rx => result.expect("server observed the request"),
893 }
894
895 drop(send);
897
898 assert!(
899 lease.returned.is_none(),
900 "the old lease must permanently lose its sender"
901 );
902
903 let returned = timeout(Duration::from_secs(1), return_rx.recv())
904 .await
905 .expect("guard did not return the sender")
906 .expect("return channel closed unexpectedly");
907
908 assert!(matches!(
909 return_rx.try_recv(),
910 Err(mpsc::error::TryRecvError::Empty)
911 ));
912
913 drop(returned);
914 connection_task.abort();
915 server_task.abort();
916 }
917
918 #[tokio::test]
919 async fn managed_driver_stops_when_return_channel_is_closed() {
920 let (client_io, _server_io) = tokio::io::duplex(4096);
921 let (_sender, connection) =
922 conn::http1::handshake::<_, Empty<Bytes>>(TokioIo::new(client_io))
923 .await
924 .expect("HTTP/1 handshake");
925
926 let pool = pool::Pool::new(pool::Config {
927 idle_timeout: Duration::from_secs(60),
928 max_idle_per_host: 16,
929 });
930 let (return_tx, return_rx) = mpsc::unbounded_channel();
931 drop(return_tx); let driver = ManagedH1Connection {
934 connection,
935 return_rx,
936 waiting: None,
937 returner: pool.return_handle(),
938 key: pool_key(),
939 };
940
941 timeout(Duration::from_secs(1), driver)
942 .await
943 .expect("driver should observe the closed return channel");
944 }
945
946 #[tokio::test]
947 async fn managed_driver_stops_when_connection_completes() {
948 let (client_io, _server_io) = tokio::io::duplex(4096);
949 let (sender, connection) =
950 conn::http1::handshake::<_, Empty<Bytes>>(TokioIo::new(client_io))
951 .await
952 .expect("HTTP/1 handshake");
953
954 let pool = pool::Pool::new(pool::Config {
955 idle_timeout: Duration::from_secs(60),
956 max_idle_per_host: 16,
957 });
958 let (_return_tx, return_rx) = mpsc::unbounded_channel();
959 let driver = ManagedH1Connection {
960 connection,
961 return_rx,
962 waiting: None,
963 returner: pool.return_handle(),
964 key: pool_key(),
965 };
966
967 drop(sender);
968
969 timeout(Duration::from_secs(1), driver)
970 .await
971 .expect("dropping the Hyper sender should complete the connection driver");
972 }
973
974 #[tokio::test]
975 async fn managed_driver_stops_when_connection_fails() {
976 let io = tokio_test::io::Builder::new()
977 .read_error(std::io::Error::other("injected read failure"))
978 .build(); let (sender, connection) = conn::http1::handshake::<_, Empty<Bytes>>(TokioIo::new(io))
980 .await
981 .expect("HTTP/1 handshake");
982
983 let pool = pool::Pool::new(pool::Config {
984 idle_timeout: Duration::from_secs(60),
985 max_idle_per_host: 16,
986 });
987 let (return_tx, return_rx) = mpsc::unbounded_channel();
988 let driver = ManagedH1Connection {
989 connection,
990 return_rx,
991 waiting: None,
992 returner: pool.return_handle(),
993 key: pool_key(),
994 };
995
996 timeout(Duration::from_secs(1), driver)
997 .await
998 .expect("I/O failure should stop the connection driver");
999
1000 drop(sender);
1001 drop(return_tx);
1002 }
1003
1004 #[tokio::test]
1005 async fn managed_driver_drops_sender_closed_before_reuse() {
1006 let (client_a, _server_a) = tokio::io::duplex(4096);
1007 let (sender_a, connection_a) =
1008 conn::http1::handshake::<_, Empty<Bytes>>(TokioIo::new(client_a))
1009 .await
1010 .expect("first HTTP/1 handshake");
1011
1012 let (client_b, _server_b) = tokio::io::duplex(4096);
1013 let (sender_b, connection_b) =
1014 conn::http1::handshake::<_, Empty<Bytes>>(TokioIo::new(client_b))
1015 .await
1016 .expect("second HTTP/1 handshake");
1017 drop(connection_b);
1018
1019 let pool = pool::Pool::new(pool::Config {
1020 idle_timeout: Duration::from_secs(60),
1021 max_idle_per_host: 16,
1022 });
1023 let key = pool_key();
1024 let (return_tx, return_rx) = mpsc::unbounded_channel();
1025 let driver = ManagedH1Connection {
1026 connection: connection_a,
1027 return_rx,
1028 waiting: Some(crate::client::protocol::H1Returned {
1029 http_sender: sender_b,
1030 return_tx: return_tx.clone(),
1031 }),
1032 returner: pool.return_handle(),
1033 key: key.clone(),
1034 };
1035
1036 timeout(Duration::from_secs(1), driver)
1037 .await
1038 .expect("closed sender should stop the driver");
1039
1040 let mut checkout = pool.checkout(key);
1041 let returned = poll_fn(|cx| {
1042 Poll::Ready(matches!(
1043 std::pin::Pin::new(&mut checkout).poll(cx),
1044 Poll::Ready(_)
1045 ))
1046 })
1047 .await;
1048 assert!(!returned, "closed sender must not be returned to the pool");
1049
1050 drop(sender_a);
1051 drop(return_tx);
1052 }
1053}