1use std::error::Error as StdError;
2use std::fmt;
3use std::future::Future;
4use std::marker::Unpin;
5use std::mem;
6use std::pin::Pin;
7use std::task::{Context, Poll};
8use std::time::Duration;
9
10use futures_channel::oneshot;
11use futures_util::future::{self, Either, FutureExt as _, TryFutureExt as _};
12use http::header::{HeaderValue, HOST};
13use http::uri::{Port, Scheme};
14use http::{Method, Request, Response, Uri, Version};
15use tracing::{debug, trace, warn};
16
17use crate::body::{Body, HttpBody};
18use crate::client::connect::CaptureConnectionExtension;
19use crate::common::{exec::BoxSendFuture, lazy as hyper_lazy, sync_wrapper::SyncWrapper, Lazy};
20#[cfg(feature = "http2")]
21use crate::ext::Protocol;
22use crate::rt::Executor;
23
24use super::conn;
25use super::connect::{self, sealed::Connect, Alpn, Connected, Connection};
26use super::pool::{
27 self, CheckoutIsClosedError, Key as PoolKey, Pool, Poolable, Pooled, Reservation,
28};
29#[cfg(feature = "tcp")]
30use super::HttpConnector;
31
32#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
37pub struct Client<C, B = Body> {
38 config: Config,
39 #[cfg_attr(feature = "deprecated", allow(deprecated))]
40 conn_builder: conn::Builder,
41 connector: C,
42 pool: Pool<PoolClient<B>>,
43}
44
45#[derive(Clone, Copy, Debug)]
46struct Config {
47 retry_canceled_requests: bool,
48 set_host: bool,
49 ver: Ver,
50}
51
52#[must_use = "futures do nothing unless polled"]
56pub struct ResponseFuture {
57 inner: SyncWrapper<Pin<Box<dyn Future<Output = crate::Result<Response<Body>>> + Send>>>,
58}
59
60#[cfg(feature = "tcp")]
63impl Client<HttpConnector, Body> {
64 #[cfg_attr(docsrs, doc(cfg(feature = "tcp")))]
72 #[inline]
73 pub fn new() -> Client<HttpConnector, Body> {
74 Builder::default().build_http()
75 }
76}
77
78#[cfg(feature = "tcp")]
79impl Default for Client<HttpConnector, Body> {
80 fn default() -> Client<HttpConnector, Body> {
81 Client::new()
82 }
83}
84
85impl Client<(), Body> {
86 #[inline]
106 pub fn builder() -> Builder {
107 Builder::default()
108 }
109}
110
111impl<C, B> Client<C, B>
112where
113 C: Connect + Clone + Send + Sync + 'static,
114 B: HttpBody + Send + 'static,
115 B::Data: Send,
116 B::Error: Into<Box<dyn StdError + Send + Sync>>,
117{
118 pub fn get(&self, uri: Uri) -> ResponseFuture
140 where
141 B: Default,
142 {
143 let body = B::default();
144 if !body.is_end_stream() {
145 warn!("default HttpBody used for get() does not return true for is_end_stream");
146 }
147
148 let mut req = Request::new(body);
149 *req.uri_mut() = uri;
150 self.request(req)
151 }
152
153 pub fn request(&self, mut req: Request<B>) -> ResponseFuture {
175 let is_http_connect = req.method() == Method::CONNECT;
176 match req.version() {
177 Version::HTTP_11 => (),
178 Version::HTTP_10 => {
179 if is_http_connect {
180 warn!("CONNECT is not allowed for HTTP/1.0");
181 return ResponseFuture::new(future::err(
182 crate::Error::new_user_unsupported_request_method(),
183 ));
184 }
185 }
186 Version::HTTP_2 => (),
187 other => return ResponseFuture::error_version(other),
189 };
190
191 let pool_key = match extract_domain(req.uri_mut(), is_http_connect) {
192 Ok(s) => s,
193 Err(err) => {
194 return ResponseFuture::new(future::err(err));
195 }
196 };
197
198 ResponseFuture::new(self.clone().retryably_send_request(req, pool_key))
199 }
200
201 async fn retryably_send_request(
202 self,
203 mut req: Request<B>,
204 pool_key: PoolKey,
205 ) -> crate::Result<Response<Body>> {
206 let uri = req.uri().clone();
207
208 loop {
209 req = match self.send_request(req, pool_key.clone()).await {
210 Ok(resp) => return Ok(resp),
211 Err(ClientError::Normal(err)) => return Err(err),
212 Err(ClientError::Canceled {
213 connection_reused,
214 mut req,
215 reason,
216 }) => {
217 if !self.config.retry_canceled_requests || !connection_reused {
218 return Err(reason);
221 }
222
223 trace!(
224 "unstarted request canceled, trying again (reason={:?})",
225 reason
226 );
227 *req.uri_mut() = uri.clone();
228 req
229 }
230 }
231 }
232 }
233
234 async fn send_request(
235 &self,
236 mut req: Request<B>,
237 pool_key: PoolKey,
238 ) -> Result<Response<Body>, ClientError<B>> {
239 let mut pooled = match self.connection_for(pool_key).await {
240 Ok(pooled) => pooled,
241 Err(ClientConnectError::Normal(err)) => return Err(ClientError::Normal(err)),
242 Err(ClientConnectError::H2CheckoutIsClosed(reason)) => {
243 return Err(ClientError::Canceled {
244 connection_reused: true,
245 req,
246 reason,
247 })
248 }
249 };
250 req.extensions_mut()
251 .get_mut::<CaptureConnectionExtension>()
252 .map(|conn| conn.set(&pooled.conn_info));
253 if pooled.is_http1() {
254 if req.version() == Version::HTTP_2 {
255 warn!("Connection is HTTP/1, but request requires HTTP/2");
256 return Err(ClientError::Normal(
257 crate::Error::new_user_unsupported_version()
258 .with_client_connect_info(pooled.conn_info.clone()),
259 ));
260 }
261
262 if self.config.set_host {
263 let uri = req.uri().clone();
264 req.headers_mut().entry(HOST).or_insert_with(|| {
265 let hostname = uri.host().expect("authority implies host");
266 if let Some(port) = get_non_default_port(&uri) {
267 let s = format!("{}:{}", hostname, port);
268 HeaderValue::from_str(&s)
269 } else {
270 HeaderValue::from_str(hostname)
271 }
272 .expect("uri host is valid header value")
273 });
274 }
275
276 if req.method() == Method::CONNECT {
278 authority_form(req.uri_mut());
279 } else if pooled.conn_info.is_proxied {
280 absolute_form(req.uri_mut());
281 } else {
282 origin_form(req.uri_mut());
283 }
284 } else if req.method() == Method::CONNECT {
285 #[cfg(not(feature = "http2"))]
286 authority_form(req.uri_mut());
287
288 #[cfg(feature = "http2")]
289 if req.extensions().get::<Protocol>().is_none() {
290 authority_form(req.uri_mut());
291 }
292 }
293
294 let mut res = match pooled.send_request_retryable(req).await {
295 Err((err, orig_req)) => {
296 return Err(ClientError::map_with_reused(pooled.is_reused())((
297 err.with_client_connect_info(pooled.conn_info.clone()),
298 orig_req,
299 )));
300 }
301 Ok(res) => res,
302 };
303
304 if let Some(extra) = &pooled.conn_info.extra {
306 extra.set(res.extensions_mut());
307 }
308
309 if pooled.is_closed() {
317 return Ok(res);
318 }
319
320 if pooled.is_http2() || !pooled.is_pool_enabled() || pooled.is_ready() {
331 drop(pooled);
332 } else if !res.body().is_end_stream() {
333 let (delayed_tx, delayed_rx) = oneshot::channel();
334 res.body_mut().delayed_eof(delayed_rx);
335 let on_idle = future::poll_fn(move |cx| pooled.poll_ready(cx)).map(move |_| {
336 drop(delayed_tx);
339 });
340
341 #[cfg_attr(feature = "deprecated", allow(deprecated))]
342 self.conn_builder.exec.execute(on_idle);
343 } else {
344 let on_idle = future::poll_fn(move |cx| pooled.poll_ready(cx)).map(|_| ());
347
348 #[cfg_attr(feature = "deprecated", allow(deprecated))]
349 self.conn_builder.exec.execute(on_idle);
350 }
351
352 Ok(res)
353 }
354
355 async fn connection_for(
356 &self,
357 pool_key: PoolKey,
358 ) -> Result<Pooled<PoolClient<B>>, ClientConnectError> {
359 let checkout = self.pool.checkout(pool_key.clone());
373 let connect = self.connect_to(pool_key);
374 let is_ver_h2 = self.config.ver == Ver::Http2;
375
376 match future::select(checkout, connect).await {
379 Either::Left((Ok(checked_out), connecting)) => {
384 if connecting.started() {
392 let bg = connecting
393 .map_err(|err| {
394 trace!("background connect error: {}", err);
395 })
396 .map(|_pooled| {
397 });
400 #[cfg_attr(feature = "deprecated", allow(deprecated))]
403 self.conn_builder.exec.execute(bg);
404 }
405 Ok(checked_out)
406 }
407 Either::Right((Ok(connected), _checkout)) => Ok(connected),
409 Either::Left((Err(err), connecting)) => {
418 if err.is_canceled() {
419 connecting.await.map_err(ClientConnectError::Normal)
420 } else {
421 Err(ClientConnectError::Normal(err))
422 }
423 }
424 Either::Right((Err(err), checkout)) => {
425 if err.is_canceled() {
426 checkout.await.map_err(move |err| {
427 if is_ver_h2
428 && err.is_canceled()
429 && err.find_source::<CheckoutIsClosedError>().is_some()
430 {
431 ClientConnectError::H2CheckoutIsClosed(err)
432 } else {
433 ClientConnectError::Normal(err)
434 }
435 })
436 } else {
437 Err(ClientConnectError::Normal(err))
438 }
439 }
440 }
441 }
442
443 fn connect_to(
444 &self,
445 pool_key: PoolKey,
446 ) -> impl Lazy<Output = crate::Result<Pooled<PoolClient<B>>>> + Unpin {
447 #[cfg_attr(feature = "deprecated", allow(deprecated))]
448 let executor = self.conn_builder.exec.clone();
449 let pool = self.pool.clone();
450 #[cfg(not(feature = "http2"))]
451 let conn_builder = self.conn_builder.clone();
452 #[cfg(feature = "http2")]
453 let mut conn_builder = self.conn_builder.clone();
454 let ver = self.config.ver;
455 let is_ver_h2 = ver == Ver::Http2;
456 let connector = self.connector.clone();
457 let dst = domain_as_uri(pool_key.clone());
458 hyper_lazy(move || {
459 let connecting = match pool.connecting(&pool_key, ver) {
465 Some(lock) => lock,
466 None => {
467 let canceled =
468 crate::Error::new_canceled().with("HTTP/2 connection in progress");
469 return Either::Right(future::err(canceled));
470 }
471 };
472 Either::Left(
473 connector
474 .connect(connect::sealed::Internal, dst)
475 .map_err(crate::Error::new_connect)
476 .and_then(move |io| {
477 let connected = io.connected();
478 let connecting = if connected.alpn == Alpn::H2 && !is_ver_h2 {
482 match connecting.alpn_h2(&pool) {
483 Some(lock) => {
484 trace!("ALPN negotiated h2, updating pool");
485 lock
486 }
487 None => {
488 let canceled = crate::Error::new_canceled()
491 .with("ALPN upgraded to HTTP/2");
492 return Either::Right(future::err(canceled));
493 }
494 }
495 } else {
496 connecting
497 };
498
499 #[cfg_attr(not(feature = "http2"), allow(unused))]
500 let is_h2 = is_ver_h2 || connected.alpn == Alpn::H2;
501 #[cfg(feature = "http2")]
502 {
503 conn_builder.http2_only(is_h2);
504 }
505
506 Either::Left(Box::pin(async move {
507 let (tx, conn) = conn_builder.handshake(io).await?;
508
509 trace!("handshake complete, spawning background dispatcher task");
510 executor.execute(
511 conn.map_err(|e| debug!("client connection error: {}", e))
512 .map(|_| ()),
513 );
514
515 let tx = tx.when_ready().await?;
518
519 let tx = {
520 #[cfg(feature = "http2")]
521 {
522 if is_h2 {
523 PoolTx::Http2(tx.into_http2())
524 } else {
525 PoolTx::Http1(tx)
526 }
527 }
528 #[cfg(not(feature = "http2"))]
529 PoolTx::Http1(tx)
530 };
531
532 Ok(pool.pooled(
533 connecting,
534 PoolClient {
535 conn_info: connected,
536 tx,
537 },
538 ))
539 }))
540 }),
541 )
542 })
543 }
544}
545
546impl<C, B> tower_service::Service<Request<B>> for Client<C, B>
547where
548 C: Connect + Clone + Send + Sync + 'static,
549 B: HttpBody + Send + 'static,
550 B::Data: Send,
551 B::Error: Into<Box<dyn StdError + Send + Sync>>,
552{
553 type Response = Response<Body>;
554 type Error = crate::Error;
555 type Future = ResponseFuture;
556
557 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
558 Poll::Ready(Ok(()))
559 }
560
561 fn call(&mut self, req: Request<B>) -> Self::Future {
562 self.request(req)
563 }
564}
565
566impl<C, B> tower_service::Service<Request<B>> for &'_ Client<C, B>
567where
568 C: Connect + Clone + Send + Sync + 'static,
569 B: HttpBody + Send + 'static,
570 B::Data: Send,
571 B::Error: Into<Box<dyn StdError + Send + Sync>>,
572{
573 type Response = Response<Body>;
574 type Error = crate::Error;
575 type Future = ResponseFuture;
576
577 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
578 Poll::Ready(Ok(()))
579 }
580
581 fn call(&mut self, req: Request<B>) -> Self::Future {
582 self.request(req)
583 }
584}
585
586impl<C: Clone, B> Clone for Client<C, B> {
587 fn clone(&self) -> Client<C, B> {
588 Client {
589 config: self.config.clone(),
590 conn_builder: self.conn_builder.clone(),
591 connector: self.connector.clone(),
592 pool: self.pool.clone(),
593 }
594 }
595}
596
597impl<C, B> fmt::Debug for Client<C, B> {
598 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
599 f.debug_struct("Client").finish()
600 }
601}
602
603impl ResponseFuture {
606 fn new<F>(value: F) -> Self
607 where
608 F: Future<Output = crate::Result<Response<Body>>> + Send + 'static,
609 {
610 Self {
611 inner: SyncWrapper::new(Box::pin(value)),
612 }
613 }
614
615 fn error_version(ver: Version) -> Self {
616 warn!("Request has unsupported version \"{:?}\"", ver);
617 ResponseFuture::new(Box::pin(future::err(
618 crate::Error::new_user_unsupported_version(),
619 )))
620 }
621}
622
623impl fmt::Debug for ResponseFuture {
624 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625 f.pad("Future<Response>")
626 }
627}
628
629impl Future for ResponseFuture {
630 type Output = crate::Result<Response<Body>>;
631
632 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
633 self.inner.get_mut().as_mut().poll(cx)
634 }
635}
636
637#[allow(missing_debug_implementations)]
641struct PoolClient<B> {
642 conn_info: Connected,
643 tx: PoolTx<B>,
644}
645
646enum PoolTx<B> {
647 #[cfg_attr(feature = "deprecated", allow(deprecated))]
648 Http1(conn::SendRequest<B>),
649 #[cfg(feature = "http2")]
650 Http2(conn::Http2SendRequest<B>),
651}
652
653impl<B> PoolClient<B> {
654 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<crate::Result<()>> {
655 match self.tx {
656 PoolTx::Http1(ref mut tx) => tx.poll_ready(cx),
657 #[cfg(feature = "http2")]
658 PoolTx::Http2(_) => Poll::Ready(Ok(())),
659 }
660 }
661
662 fn is_http1(&self) -> bool {
663 !self.is_http2()
664 }
665
666 fn is_http2(&self) -> bool {
667 match self.tx {
668 PoolTx::Http1(_) => false,
669 #[cfg(feature = "http2")]
670 PoolTx::Http2(_) => true,
671 }
672 }
673
674 fn is_ready(&self) -> bool {
675 match self.tx {
676 PoolTx::Http1(ref tx) => tx.is_ready(),
677 #[cfg(feature = "http2")]
678 PoolTx::Http2(ref tx) => tx.is_ready(),
679 }
680 }
681
682 fn is_closed(&self) -> bool {
683 match self.tx {
684 PoolTx::Http1(ref tx) => tx.is_closed(),
685 #[cfg(feature = "http2")]
686 PoolTx::Http2(ref tx) => tx.is_closed(),
687 }
688 }
689}
690
691impl<B: HttpBody + 'static> PoolClient<B> {
692 fn send_request_retryable(
693 &mut self,
694 req: Request<B>,
695 ) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>>
696 where
697 B: Send,
698 {
699 match self.tx {
700 #[cfg(not(feature = "http2"))]
701 PoolTx::Http1(ref mut tx) => tx.send_request_retryable(req),
702 #[cfg(feature = "http2")]
703 PoolTx::Http1(ref mut tx) => Either::Left(tx.send_request_retryable(req)),
704 #[cfg(feature = "http2")]
705 PoolTx::Http2(ref mut tx) => Either::Right(tx.send_request_retryable(req)),
706 }
707 }
708}
709
710impl<B> Poolable for PoolClient<B>
711where
712 B: Send + 'static,
713{
714 fn is_open(&self) -> bool {
715 if self.conn_info.poisoned.poisoned() {
716 trace!(
717 "marking {:?} as closed because it was poisoned",
718 self.conn_info
719 );
720 return false;
721 }
722 match self.tx {
723 PoolTx::Http1(ref tx) => tx.is_ready(),
724 #[cfg(feature = "http2")]
725 PoolTx::Http2(ref tx) => tx.is_ready(),
726 }
727 }
728
729 fn reserve(self) -> Reservation<Self> {
730 match self.tx {
731 PoolTx::Http1(tx) => Reservation::Unique(PoolClient {
732 conn_info: self.conn_info,
733 tx: PoolTx::Http1(tx),
734 }),
735 #[cfg(feature = "http2")]
736 PoolTx::Http2(tx) => {
737 let b = PoolClient {
738 conn_info: self.conn_info.clone(),
739 tx: PoolTx::Http2(tx.clone()),
740 };
741 let a = PoolClient {
742 conn_info: self.conn_info,
743 tx: PoolTx::Http2(tx),
744 };
745 Reservation::Shared(a, b)
746 }
747 }
748 }
749
750 fn can_share(&self) -> bool {
751 self.is_http2()
752 }
753}
754
755#[allow(missing_debug_implementations)]
759enum ClientError<B> {
760 Normal(crate::Error),
761 Canceled {
762 connection_reused: bool,
763 req: Request<B>,
764 reason: crate::Error,
765 },
766}
767
768impl<B> ClientError<B> {
769 fn map_with_reused(conn_reused: bool) -> impl Fn((crate::Error, Option<Request<B>>)) -> Self {
770 move |(err, orig_req)| {
771 if let Some(req) = orig_req {
772 ClientError::Canceled {
773 connection_reused: conn_reused,
774 reason: err,
775 req,
776 }
777 } else {
778 ClientError::Normal(err)
779 }
780 }
781 }
782}
783
784enum ClientConnectError {
785 Normal(crate::Error),
786 H2CheckoutIsClosed(crate::Error),
787}
788
789#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
791pub(super) enum Ver {
792 Auto,
793 Http2,
794}
795
796fn origin_form(uri: &mut Uri) {
797 let path = match uri.path_and_query() {
798 Some(path) if path.as_str() != "/" => {
799 let mut parts = ::http::uri::Parts::default();
800 parts.path_and_query = Some(path.clone());
801 Uri::from_parts(parts).expect("path is valid uri")
802 }
803 _none_or_just_slash => {
804 debug_assert!(Uri::default() == "/");
805 Uri::default()
806 }
807 };
808 *uri = path
809}
810
811fn absolute_form(uri: &mut Uri) {
812 debug_assert!(uri.scheme().is_some(), "absolute_form needs a scheme");
813 debug_assert!(
814 uri.authority().is_some(),
815 "absolute_form needs an authority"
816 );
817 if uri.scheme() == Some(&Scheme::HTTPS) {
821 origin_form(uri);
822 }
823}
824
825fn authority_form(uri: &mut Uri) {
826 if let Some(path) = uri.path_and_query() {
827 if path != "/" {
830 warn!("HTTP/1.1 CONNECT request stripping path: {:?}", path);
831 }
832 }
833 *uri = match uri.authority() {
834 Some(auth) => {
835 let mut parts = ::http::uri::Parts::default();
836 parts.authority = Some(auth.clone());
837 Uri::from_parts(parts).expect("authority is valid")
838 }
839 None => {
840 unreachable!("authority_form with relative uri");
841 }
842 };
843}
844
845fn extract_domain(uri: &mut Uri, is_http_connect: bool) -> crate::Result<PoolKey> {
846 let uri_clone = uri.clone();
847 match (uri_clone.scheme(), uri_clone.authority()) {
848 (Some(scheme), Some(auth)) => Ok((scheme.clone(), auth.clone())),
849 (None, Some(auth)) if is_http_connect => {
850 let scheme = match auth.port_u16() {
851 Some(443) => {
852 set_scheme(uri, Scheme::HTTPS);
853 Scheme::HTTPS
854 }
855 _ => {
856 set_scheme(uri, Scheme::HTTP);
857 Scheme::HTTP
858 }
859 };
860 Ok((scheme, auth.clone()))
861 }
862 _ => {
863 debug!("Client requires absolute-form URIs, received: {:?}", uri);
864 Err(crate::Error::new_user_absolute_uri_required())
865 }
866 }
867}
868
869fn domain_as_uri((scheme, auth): PoolKey) -> Uri {
870 http::uri::Builder::new()
871 .scheme(scheme)
872 .authority(auth)
873 .path_and_query("/")
874 .build()
875 .expect("domain is valid Uri")
876}
877
878fn set_scheme(uri: &mut Uri, scheme: Scheme) {
879 debug_assert!(
880 uri.scheme().is_none(),
881 "set_scheme expects no existing scheme"
882 );
883 let old = mem::replace(uri, Uri::default());
884 let mut parts: ::http::uri::Parts = old.into();
885 parts.scheme = Some(scheme);
886 parts.path_and_query = Some("/".parse().expect("slash is a valid path"));
887 *uri = Uri::from_parts(parts).expect("scheme is valid");
888}
889
890fn get_non_default_port(uri: &Uri) -> Option<Port<&str>> {
891 match (uri.port().map(|p| p.as_u16()), is_schema_secure(uri)) {
892 (Some(443), true) => None,
893 (Some(80), false) => None,
894 _ => uri.port(),
895 }
896}
897
898fn is_schema_secure(uri: &Uri) -> bool {
899 uri.scheme_str()
900 .map(|scheme_str| matches!(scheme_str, "wss" | "https"))
901 .unwrap_or_default()
902}
903
904#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
924#[derive(Clone)]
925pub struct Builder {
926 client_config: Config,
927 #[cfg_attr(feature = "deprecated", allow(deprecated))]
928 conn_builder: conn::Builder,
929 pool_config: pool::Config,
930}
931
932impl Default for Builder {
933 fn default() -> Self {
934 Self {
935 client_config: Config {
936 retry_canceled_requests: true,
937 set_host: true,
938 ver: Ver::Auto,
939 },
940 #[cfg_attr(feature = "deprecated", allow(deprecated))]
941 conn_builder: conn::Builder::new(),
942 pool_config: pool::Config {
943 idle_timeout: Some(Duration::from_secs(90)),
944 max_idle_per_host: std::usize::MAX,
945 },
946 }
947 }
948}
949
950impl Builder {
951 #[doc(hidden)]
952 #[deprecated(
953 note = "name is confusing, to disable the connection pool, call pool_max_idle_per_host(0)"
954 )]
955 pub fn keep_alive(&mut self, val: bool) -> &mut Self {
956 if !val {
957 self.pool_max_idle_per_host(0)
959 } else if self.pool_config.max_idle_per_host == 0 {
960 self.pool_max_idle_per_host(std::usize::MAX)
962 } else {
963 self
965 }
966 }
967
968 #[doc(hidden)]
969 #[deprecated(note = "renamed to `pool_idle_timeout`")]
970 pub fn keep_alive_timeout<D>(&mut self, val: D) -> &mut Self
971 where
972 D: Into<Option<Duration>>,
973 {
974 self.pool_idle_timeout(val)
975 }
976
977 pub fn pool_idle_timeout<D>(&mut self, val: D) -> &mut Self
983 where
984 D: Into<Option<Duration>>,
985 {
986 self.pool_config.idle_timeout = val.into();
987 self
988 }
989
990 #[doc(hidden)]
991 #[deprecated(note = "renamed to `pool_max_idle_per_host`")]
992 pub fn max_idle_per_host(&mut self, max_idle: usize) -> &mut Self {
993 self.pool_config.max_idle_per_host = max_idle;
994 self
995 }
996
997 pub fn pool_max_idle_per_host(&mut self, max_idle: usize) -> &mut Self {
1001 self.pool_config.max_idle_per_host = max_idle;
1002 self
1003 }
1004
1005 pub fn http1_read_buf_exact_size(&mut self, sz: usize) -> &mut Self {
1013 self.conn_builder.http1_read_buf_exact_size(Some(sz));
1014 self
1015 }
1016
1017 #[cfg(feature = "http1")]
1027 #[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
1028 pub fn http1_max_buf_size(&mut self, max: usize) -> &mut Self {
1029 self.conn_builder.http1_max_buf_size(max);
1030 self
1031 }
1032
1033 pub fn http1_allow_spaces_after_header_name_in_responses(&mut self, val: bool) -> &mut Self {
1056 self.conn_builder
1057 .http1_allow_spaces_after_header_name_in_responses(val);
1058 self
1059 }
1060
1061 pub fn http1_allow_obsolete_multiline_headers_in_responses(&mut self, val: bool) -> &mut Self {
1093 self.conn_builder
1094 .http1_allow_obsolete_multiline_headers_in_responses(val);
1095 self
1096 }
1097
1098 pub fn http1_ignore_invalid_headers_in_responses(&mut self, val: bool) -> &mut Builder {
1123 self.conn_builder
1124 .http1_ignore_invalid_headers_in_responses(val);
1125 self
1126 }
1127
1128 pub fn http1_writev(&mut self, enabled: bool) -> &mut Builder {
1141 self.conn_builder.http1_writev(enabled);
1142 self
1143 }
1144
1145 pub fn http1_title_case_headers(&mut self, val: bool) -> &mut Self {
1152 self.conn_builder.http1_title_case_headers(val);
1153 self
1154 }
1155
1156 pub fn http1_preserve_header_case(&mut self, val: bool) -> &mut Self {
1170 self.conn_builder.http1_preserve_header_case(val);
1171 self
1172 }
1173
1174 pub fn http09_responses(&mut self, val: bool) -> &mut Self {
1178 self.conn_builder.http09_responses(val);
1179 self
1180 }
1181
1182 #[cfg(feature = "http2")]
1193 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1194 pub fn http2_only(&mut self, val: bool) -> &mut Self {
1195 self.client_config.ver = if val { Ver::Http2 } else { Ver::Auto };
1196 self
1197 }
1198
1199 #[cfg(feature = "http2")]
1208 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1209 pub fn http2_initial_stream_window_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
1210 self.conn_builder
1211 .http2_initial_stream_window_size(sz.into());
1212 self
1213 }
1214
1215 #[cfg(feature = "http2")]
1221 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1222 pub fn http2_initial_connection_window_size(
1223 &mut self,
1224 sz: impl Into<Option<u32>>,
1225 ) -> &mut Self {
1226 self.conn_builder
1227 .http2_initial_connection_window_size(sz.into());
1228 self
1229 }
1230
1231 #[cfg(feature = "http2")]
1237 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1238 pub fn http2_adaptive_window(&mut self, enabled: bool) -> &mut Self {
1239 self.conn_builder.http2_adaptive_window(enabled);
1240 self
1241 }
1242
1243 #[cfg(feature = "http2")]
1249 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1250 pub fn http2_max_frame_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
1251 self.conn_builder.http2_max_frame_size(sz);
1252 self
1253 }
1254
1255 #[cfg(feature = "runtime")]
1266 #[cfg(feature = "http2")]
1267 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1268 pub fn http2_keep_alive_interval(
1269 &mut self,
1270 interval: impl Into<Option<Duration>>,
1271 ) -> &mut Self {
1272 self.conn_builder.http2_keep_alive_interval(interval);
1273 self
1274 }
1275
1276 #[cfg(feature = "runtime")]
1287 #[cfg(feature = "http2")]
1288 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1289 pub fn http2_keep_alive_timeout(&mut self, timeout: Duration) -> &mut Self {
1290 self.conn_builder.http2_keep_alive_timeout(timeout);
1291 self
1292 }
1293
1294 #[cfg(feature = "runtime")]
1307 #[cfg(feature = "http2")]
1308 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1309 pub fn http2_keep_alive_while_idle(&mut self, enabled: bool) -> &mut Self {
1310 self.conn_builder.http2_keep_alive_while_idle(enabled);
1311 self
1312 }
1313
1314 #[cfg(feature = "http2")]
1323 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1324 pub fn http2_max_concurrent_reset_streams(&mut self, max: usize) -> &mut Self {
1325 self.conn_builder.http2_max_concurrent_reset_streams(max);
1326 self
1327 }
1328
1329 #[cfg(feature = "http2")]
1337 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1338 pub fn http2_max_send_buf_size(&mut self, max: usize) -> &mut Self {
1339 self.conn_builder.http2_max_send_buf_size(max);
1340 self
1341 }
1342
1343 #[inline]
1355 pub fn retry_canceled_requests(&mut self, val: bool) -> &mut Self {
1356 self.client_config.retry_canceled_requests = val;
1357 self
1358 }
1359
1360 #[inline]
1367 pub fn set_host(&mut self, val: bool) -> &mut Self {
1368 self.client_config.set_host = val;
1369 self
1370 }
1371
1372 pub fn executor<E>(&mut self, exec: E) -> &mut Self
1374 where
1375 E: Executor<BoxSendFuture> + Send + Sync + 'static,
1376 {
1377 self.conn_builder.executor(exec);
1378 self
1379 }
1380
1381 #[cfg(feature = "tcp")]
1383 pub fn build_http<B>(&self) -> Client<HttpConnector, B>
1384 where
1385 B: HttpBody + Send,
1386 B::Data: Send,
1387 {
1388 let mut connector = HttpConnector::new();
1389 if self.pool_config.is_enabled() {
1390 connector.set_keepalive(self.pool_config.idle_timeout);
1391 }
1392 self.build(connector)
1393 }
1394
1395 pub fn build<C, B>(&self, connector: C) -> Client<C, B>
1397 where
1398 C: Connect + Clone,
1399 B: HttpBody + Send,
1400 B::Data: Send,
1401 {
1402 #[cfg_attr(feature = "deprecated", allow(deprecated))]
1403 Client {
1404 config: self.client_config,
1405 conn_builder: self.conn_builder.clone(),
1406 connector,
1407 pool: Pool::new(self.pool_config, &self.conn_builder.exec),
1408 }
1409 }
1410}
1411
1412impl fmt::Debug for Builder {
1413 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1414 f.debug_struct("Builder")
1415 .field("client_config", &self.client_config)
1416 .field("conn_builder", &self.conn_builder)
1417 .field("pool_config", &self.pool_config)
1418 .finish()
1419 }
1420}
1421
1422#[cfg(test)]
1423mod unit_tests {
1424 use super::*;
1425
1426 #[test]
1427 fn response_future_is_sync() {
1428 fn assert_sync<T: Sync>() {}
1429 assert_sync::<ResponseFuture>();
1430 }
1431
1432 #[test]
1433 fn set_relative_uri_with_implicit_path() {
1434 let mut uri = "http://hyper.rs".parse().unwrap();
1435 origin_form(&mut uri);
1436 assert_eq!(uri.to_string(), "/");
1437 }
1438
1439 #[test]
1440 fn test_origin_form() {
1441 let mut uri = "http://hyper.rs/guides".parse().unwrap();
1442 origin_form(&mut uri);
1443 assert_eq!(uri.to_string(), "/guides");
1444
1445 let mut uri = "http://hyper.rs/guides?foo=bar".parse().unwrap();
1446 origin_form(&mut uri);
1447 assert_eq!(uri.to_string(), "/guides?foo=bar");
1448 }
1449
1450 #[test]
1451 fn test_absolute_form() {
1452 let mut uri = "http://hyper.rs/guides".parse().unwrap();
1453 absolute_form(&mut uri);
1454 assert_eq!(uri.to_string(), "http://hyper.rs/guides");
1455
1456 let mut uri = "https://hyper.rs/guides".parse().unwrap();
1457 absolute_form(&mut uri);
1458 assert_eq!(uri.to_string(), "/guides");
1459 }
1460
1461 #[test]
1462 fn test_authority_form() {
1463 let _ = pretty_env_logger::try_init();
1464
1465 let mut uri = "http://hyper.rs".parse().unwrap();
1466 authority_form(&mut uri);
1467 assert_eq!(uri.to_string(), "hyper.rs");
1468
1469 let mut uri = "hyper.rs".parse().unwrap();
1470 authority_form(&mut uri);
1471 assert_eq!(uri.to_string(), "hyper.rs");
1472 }
1473
1474 #[test]
1475 fn test_extract_domain_connect_no_port() {
1476 let mut uri = "hyper.rs".parse().unwrap();
1477 let (scheme, host) = extract_domain(&mut uri, true).expect("extract domain");
1478 assert_eq!(scheme, *"http");
1479 assert_eq!(host, "hyper.rs");
1480 }
1481
1482 #[test]
1483 fn test_is_secure() {
1484 assert_eq!(
1485 is_schema_secure(&"http://hyper.rs".parse::<Uri>().unwrap()),
1486 false
1487 );
1488 assert_eq!(is_schema_secure(&"hyper.rs".parse::<Uri>().unwrap()), false);
1489 assert_eq!(
1490 is_schema_secure(&"wss://hyper.rs".parse::<Uri>().unwrap()),
1491 true
1492 );
1493 assert_eq!(
1494 is_schema_secure(&"ws://hyper.rs".parse::<Uri>().unwrap()),
1495 false
1496 );
1497 }
1498
1499 #[test]
1500 fn test_get_non_default_port() {
1501 assert!(get_non_default_port(&"http://hyper.rs".parse::<Uri>().unwrap()).is_none());
1502 assert!(get_non_default_port(&"http://hyper.rs:80".parse::<Uri>().unwrap()).is_none());
1503 assert!(get_non_default_port(&"https://hyper.rs:443".parse::<Uri>().unwrap()).is_none());
1504 assert!(get_non_default_port(&"hyper.rs:80".parse::<Uri>().unwrap()).is_none());
1505
1506 assert_eq!(
1507 get_non_default_port(&"http://hyper.rs:123".parse::<Uri>().unwrap())
1508 .unwrap()
1509 .as_u16(),
1510 123
1511 );
1512 assert_eq!(
1513 get_non_default_port(&"https://hyper.rs:80".parse::<Uri>().unwrap())
1514 .unwrap()
1515 .as_u16(),
1516 80
1517 );
1518 assert_eq!(
1519 get_non_default_port(&"hyper.rs:123".parse::<Uri>().unwrap())
1520 .unwrap()
1521 .as_u16(),
1522 123
1523 );
1524 }
1525}