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/// A Client to make outgoing HTTP requests.
33///
34/// `Client` is cheap to clone and cloning is the recommended way to share a `Client`. The
35/// underlying connection pool will be reused.
36#[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/// A `Future` that will resolve to an HTTP Response.
53///
54/// This is returned by `Client::request` (and `Client::get`).
55#[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// ===== impl Client =====
61
62#[cfg(feature = "tcp")]
63impl Client<HttpConnector, Body> {
64 /// Create a new Client with the default [config](Builder).
65 ///
66 /// # Note
67 ///
68 /// The default connector does **not** handle TLS. Speaking to `https`
69 /// destinations will require [configuring a connector that implements
70 /// TLS](https://hyper.rs/guides/client/configuration).
71 #[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 /// Create a builder to configure a new `Client`.
87 ///
88 /// # Example
89 ///
90 /// ```
91 /// # #[cfg(feature = "runtime")]
92 /// # fn run () {
93 /// use std::time::Duration;
94 /// use hyper::Client;
95 ///
96 /// let client = Client::builder()
97 /// .pool_idle_timeout(Duration::from_secs(30))
98 /// .http2_only(true)
99 /// .build_http();
100 /// # let infer: Client<_, hyper::Body> = client;
101 /// # drop(infer);
102 /// # }
103 /// # fn main() {}
104 /// ```
105 #[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 /// Send a `GET` request to the supplied `Uri`.
119 ///
120 /// # Note
121 ///
122 /// This requires that the `HttpBody` type have a `Default` implementation.
123 /// It *should* return an "empty" version of itself, such that
124 /// `HttpBody::is_end_stream` is `true`.
125 ///
126 /// # Example
127 ///
128 /// ```
129 /// # #[cfg(feature = "runtime")]
130 /// # fn run () {
131 /// use hyper::{Client, Uri};
132 ///
133 /// let client = Client::new();
134 ///
135 /// let future = client.get(Uri::from_static("http://httpbin.org/ip"));
136 /// # }
137 /// # fn main() {}
138 /// ```
139 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 /// Send a constructed `Request` using this `Client`.
154 ///
155 /// # Example
156 ///
157 /// ```
158 /// # #[cfg(feature = "runtime")]
159 /// # fn run () {
160 /// use hyper::{Body, Method, Client, Request};
161 ///
162 /// let client = Client::new();
163 ///
164 /// let req = Request::builder()
165 /// .method(Method::POST)
166 /// .uri("http://httpbin.org/post")
167 /// .body(Body::from("Hallo!"))
168 /// .expect("request builder");
169 ///
170 /// let future = client.request(req);
171 /// # }
172 /// # fn main() {}
173 /// ```
174 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 // completely unsupported HTTP version (like HTTP/0.9)!
188 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 // if client disabled, don't retry
219 // a fresh connection means we definitely can't retry
220 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 // CONNECT always sends authority-form, so check it first...
277 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 the Connector included 'extra' info, add to Response...
305 if let Some(extra) = &pooled.conn_info.extra {
306 extra.set(res.extensions_mut());
307 }
308
309 // As of futures@0.1.21, there is a race condition in the mpsc
310 // channel, such that sending when the receiver is closing can
311 // result in the message being stuck inside the queue. It won't
312 // ever notify until the Sender side is dropped.
313 //
314 // To counteract this, we must check if our senders 'want' channel
315 // has been closed after having tried to send. If so, error out...
316 if pooled.is_closed() {
317 return Ok(res);
318 }
319
320 // If pooled is HTTP/2, we can toss this reference immediately.
321 //
322 // when pooled is dropped, it will try to insert back into the
323 // pool. To delay that, spawn a future that completes once the
324 // sender is ready again.
325 //
326 // This *should* only be once the related `Connection` has polled
327 // for a new request to start.
328 //
329 // It won't be ready if there is a body to stream.
330 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 // At this point, `pooled` is dropped, and had a chance
337 // to insert into the pool (if conn was idle)
338 drop(delayed_tx);
339 });
340
341 #[cfg_attr(feature = "deprecated", allow(deprecated))]
342 self.conn_builder.exec.execute(on_idle);
343 } else {
344 // There's no body to delay, but the connection isn't
345 // ready yet. Only re-insert when it's ready
346 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 // This actually races 2 different futures to try to get a ready
360 // connection the fastest, and to reduce connection churn.
361 //
362 // - If the pool has an idle connection waiting, that's used
363 // immediately.
364 // - Otherwise, the Connector is asked to start connecting to
365 // the destination Uri.
366 // - Meanwhile, the pool Checkout is watching to see if any other
367 // request finishes and tries to insert an idle connection.
368 // - If a new connection is started, but the Checkout wins after
369 // (an idle connection became available first), the started
370 // connection future is spawned into the runtime to complete,
371 // and then be inserted into the pool as an idle connection.
372 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 // The order of the `select` is depended on below...
377
378 match future::select(checkout, connect).await {
379 // Checkout won, connect future may have been started or not.
380 //
381 // If it has, let it finish and insert back into the pool,
382 // so as to not waste the socket...
383 Either::Left((Ok(checked_out), connecting)) => {
384 // This depends on the `select` above having the correct
385 // order, such that if the checkout future were ready
386 // immediately, the connect future will never have been
387 // started.
388 //
389 // If it *wasn't* ready yet, then the connect future will
390 // have been started...
391 if connecting.started() {
392 let bg = connecting
393 .map_err(|err| {
394 trace!("background connect error: {}", err);
395 })
396 .map(|_pooled| {
397 // dropping here should just place it in
398 // the Pool for us...
399 });
400 // An execute error here isn't important, we're just trying
401 // to prevent a waste of a socket...
402 #[cfg_attr(feature = "deprecated", allow(deprecated))]
403 self.conn_builder.exec.execute(bg);
404 }
405 Ok(checked_out)
406 }
407 // Connect won, checkout can just be dropped.
408 Either::Right((Ok(connected), _checkout)) => Ok(connected),
409 // Either checkout or connect could get canceled:
410 //
411 // 1. Connect is canceled if this is HTTP/2 and there is
412 // an outstanding HTTP/2 connecting task.
413 // 2. Checkout is canceled if the pool cannot deliver an
414 // idle connection reliably.
415 //
416 // In both cases, we should just wait for the other future.
417 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 // Try to take a "connecting lock".
460 //
461 // If the pool_key is for HTTP/2, and there is already a
462 // connection being established, then this can't take a
463 // second lock. The "connect_to" future is Canceled.
464 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 // If ALPN is h2 and we aren't http2_only already,
479 // then we need to convert our pool checkout into
480 // a single HTTP2 one.
481 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 // Another connection has already upgraded,
489 // the pool checkout should finish up for us.
490 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 // Wait for 'conn' to ready up before we
516 // declare this tx as usable
517 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(name:"Client").finish()
600 }
601}
602
603// ===== impl ResponseFuture =====
604
605impl 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// ===== impl PoolClient =====
638
639// FIXME: allow() required due to `impl Trait` leaking types to this lint
640#[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: &mut SendRequest) => Either::Left(tx.send_request_retryable(req)),
704 #[cfg(feature = "http2")]
705 PoolTx::Http2(ref mut tx: &mut Http2SendRequest) => 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// ===== impl ClientError =====
756
757// FIXME: allow() required due to `impl Trait` leaking types to this lint
758#[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: Error, orig_req: Option>)| {
771 if let Some(req: Request) = 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/// A marker to identify what version a pooled connection is.
790#[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: Uri = match uri.path_and_query() {
798 Some(path: &PathAndQuery) if path.as_str() != "/" => {
799 let mut parts: Parts = ::http::uri::Parts::default();
800 parts.path_and_query = Some(path.clone());
801 Uri::from_parts(parts).expect(msg:"path is valid uri")
802 }
803 _none_or_just_slash: Option<&PathAndQuery> => {
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 the URI is to HTTPS, and the connector claimed to be a proxy,
818 // then it *should* have tunneled, and so we don't want to send
819 // absolute-form in that case.
820 if uri.scheme() == Some(&Scheme::HTTPS) {
821 origin_form(uri);
822 }
823}
824
825fn authority_form(uri: &mut Uri) {
826 if let Some(path: &PathAndQuery) = uri.path_and_query() {
827 // `https://hyper.rs` would parse with `/` path, don't
828 // annoy people about that...
829 if path != "/" {
830 warn!("HTTP/1.1 CONNECT request stripping path: {:?}", path);
831 }
832 }
833 *uri = match uri.authority() {
834 Some(auth: &Authority) => {
835 let mut parts: Parts = ::http::uri::Parts::default();
836 parts.authority = Some(auth.clone());
837 Uri::from_parts(parts).expect(msg:"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 = uri.clone();
847 match (uri_clone.scheme(), uri_clone.authority()) {
848 (Some(scheme: &Scheme), Some(auth: &Authority)) => Ok((scheme.clone(), auth.clone())),
849 (None, Some(auth: &Authority)) if is_http_connect => {
850 let scheme: Scheme = match auth.port_u16() {
851 Some(443) => {
852 set_scheme(uri, scheme:Scheme::HTTPS);
853 Scheme::HTTPS
854 }
855 _ => {
856 set_scheme(uri, scheme: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: Scheme, auth: Authority): PoolKey) -> Uri {
870 http::uri::Builder::new()
871 .scheme(scheme)
872 .authority(auth)
873 .path_and_query("/")
874 .build()
875 .expect(msg:"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: Uri = mem::replace(dest:uri, src:Uri::default());
884 let mut parts: ::http::uri::Parts = old.into();
885 parts.scheme = Some(scheme);
886 parts.path_and_query = Some("/".parse().expect(msg:"slash is a valid path"));
887 *uri = Uri::from_parts(parts).expect(msg:"scheme is valid");
888}
889
890fn get_non_default_port(uri: &Uri) -> Option<Port<&str>> {
891 match (uri.port().map(|p: Port<&str>| 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 uriOption.scheme_str()
900 .map(|scheme_str: &str| matches!(scheme_str, "wss" | "https"))
901 .unwrap_or_default()
902}
903
904/// A builder to configure a new [`Client`](Client).
905///
906/// # Example
907///
908/// ```
909/// # #[cfg(feature = "runtime")]
910/// # fn run () {
911/// use std::time::Duration;
912/// use hyper::Client;
913///
914/// let client = Client::builder()
915/// .pool_idle_timeout(Duration::from_secs(30))
916/// .http2_only(true)
917/// .build_http();
918/// # let infer: Client<_, hyper::Body> = client;
919/// # drop(infer);
920/// # }
921/// # fn main() {}
922/// ```
923#[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 // disable
958 self.pool_max_idle_per_host(0)
959 } else if self.pool_config.max_idle_per_host == 0 {
960 // enable
961 self.pool_max_idle_per_host(std::usize::MAX)
962 } else {
963 // already enabled
964 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 /// Set an optional timeout for idle sockets being kept-alive.
978 ///
979 /// Pass `None` to disable timeout.
980 ///
981 /// Default is 90 seconds.
982 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 /// Sets the maximum idle connection per host allowed in the pool.
998 ///
999 /// Default is `usize::MAX` (no limit).
1000 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 // HTTP/1 options
1006
1007 /// Sets the exact size of the read buffer to *always* use.
1008 ///
1009 /// Note that setting this option unsets the `http1_max_buf_size` option.
1010 ///
1011 /// Default is an adaptive read buffer.
1012 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 /// Set the maximum buffer size for the connection.
1018 ///
1019 /// Default is ~400kb.
1020 ///
1021 /// Note that setting this option unsets the `http1_read_exact_buf_size` option.
1022 ///
1023 /// # Panics
1024 ///
1025 /// The minimum value allowed is 8192. This method panics if the passed `max` is less than the minimum.
1026 #[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 /// Set whether HTTP/1 connections will accept spaces between header names
1034 /// and the colon that follow them in responses.
1035 ///
1036 /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
1037 /// parsing.
1038 ///
1039 /// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
1040 /// to say about it:
1041 ///
1042 /// > No whitespace is allowed between the header field-name and colon. In
1043 /// > the past, differences in the handling of such whitespace have led to
1044 /// > security vulnerabilities in request routing and response handling. A
1045 /// > server MUST reject any received request message that contains
1046 /// > whitespace between a header field-name and colon with a response code
1047 /// > of 400 (Bad Request). A proxy MUST remove any such whitespace from a
1048 /// > response message before forwarding the message downstream.
1049 ///
1050 /// Note that this setting does not affect HTTP/2.
1051 ///
1052 /// Default is false.
1053 ///
1054 /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
1055 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 /// Set whether HTTP/1 connections will accept obsolete line folding for
1062 /// header values.
1063 ///
1064 /// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
1065 /// to say about it:
1066 ///
1067 /// > A server that receives an obs-fold in a request message that is not
1068 /// > within a message/http container MUST either reject the message by
1069 /// > sending a 400 (Bad Request), preferably with a representation
1070 /// > explaining that obsolete line folding is unacceptable, or replace
1071 /// > each received obs-fold with one or more SP octets prior to
1072 /// > interpreting the field value or forwarding the message downstream.
1073 ///
1074 /// > A proxy or gateway that receives an obs-fold in a response message
1075 /// > that is not within a message/http container MUST either discard the
1076 /// > message and replace it with a 502 (Bad Gateway) response, preferably
1077 /// > with a representation explaining that unacceptable line folding was
1078 /// > received, or replace each received obs-fold with one or more SP
1079 /// > octets prior to interpreting the field value or forwarding the
1080 /// > message downstream.
1081 ///
1082 /// > A user agent that receives an obs-fold in a response message that is
1083 /// > not within a message/http container MUST replace each received
1084 /// > obs-fold with one or more SP octets prior to interpreting the field
1085 /// > value.
1086 ///
1087 /// Note that this setting does not affect HTTP/2.
1088 ///
1089 /// Default is false.
1090 ///
1091 /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
1092 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 /// Sets whether invalid header lines should be silently ignored in HTTP/1 responses.
1099 ///
1100 /// This mimicks the behaviour of major browsers. You probably don't want this.
1101 /// You should only want this if you are implementing a proxy whose main
1102 /// purpose is to sit in front of browsers whose users access arbitrary content
1103 /// which may be malformed, and they expect everything that works without
1104 /// the proxy to keep working with the proxy.
1105 ///
1106 /// This option will prevent Hyper's client from returning an error encountered
1107 /// when parsing a header, except if the error was caused by the character NUL
1108 /// (ASCII code 0), as Chrome specifically always reject those.
1109 ///
1110 /// The ignorable errors are:
1111 /// * empty header names;
1112 /// * characters that are not allowed in header names, except for `\0` and `\r`;
1113 /// * when `allow_spaces_after_header_name_in_responses` is not enabled,
1114 /// spaces and tabs between the header name and the colon;
1115 /// * missing colon between header name and colon;
1116 /// * characters that are not allowed in header values except for `\0` and `\r`.
1117 ///
1118 /// If an ignorable error is encountered, the parser tries to find the next
1119 /// line in the input to resume parsing the rest of the headers. An error
1120 /// will be emitted nonetheless if it finds `\0` or a lone `\r` while
1121 /// looking for the next line.
1122 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 /// Set whether HTTP/1 connections should try to use vectored writes,
1129 /// or always flatten into a single buffer.
1130 ///
1131 /// Note that setting this to false may mean more copies of body data,
1132 /// but may also improve performance when an IO transport doesn't
1133 /// support vectored writes well, such as most TLS implementations.
1134 ///
1135 /// Setting this to true will force hyper to use queued strategy
1136 /// which may eliminate unnecessary cloning on some TLS backends
1137 ///
1138 /// Default is `auto`. In this mode hyper will try to guess which
1139 /// mode to use
1140 pub fn http1_writev(&mut self, enabled: bool) -> &mut Builder {
1141 self.conn_builder.http1_writev(enabled);
1142 self
1143 }
1144
1145 /// Set whether HTTP/1 connections will write header names as title case at
1146 /// the socket level.
1147 ///
1148 /// Note that this setting does not affect HTTP/2.
1149 ///
1150 /// Default is false.
1151 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 /// Set whether to support preserving original header cases.
1157 ///
1158 /// Currently, this will record the original cases received, and store them
1159 /// in a private extension on the `Response`. It will also look for and use
1160 /// such an extension in any provided `Request`.
1161 ///
1162 /// Since the relevant extension is still private, there is no way to
1163 /// interact with the original cases. The only effect this can have now is
1164 /// to forward the cases in a proxy-like fashion.
1165 ///
1166 /// Note that this setting does not affect HTTP/2.
1167 ///
1168 /// Default is false.
1169 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 /// Set whether HTTP/0.9 responses should be tolerated.
1175 ///
1176 /// Default is false.
1177 pub fn http09_responses(&mut self, val: bool) -> &mut Self {
1178 self.conn_builder.http09_responses(val);
1179 self
1180 }
1181
1182 /// Set whether the connection **must** use HTTP/2.
1183 ///
1184 /// The destination must either allow HTTP2 Prior Knowledge, or the
1185 /// `Connect` should be configured to do use ALPN to upgrade to `h2`
1186 /// as part of the connection process. This will not make the `Client`
1187 /// utilize ALPN by itself.
1188 ///
1189 /// Note that setting this to true prevents HTTP/1 from being allowed.
1190 ///
1191 /// Default is false.
1192 #[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 /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
1200 /// stream-level flow control.
1201 ///
1202 /// Passing `None` will do nothing.
1203 ///
1204 /// If not set, hyper will use a default.
1205 ///
1206 /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE
1207 #[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 /// Sets the max connection-level flow control for HTTP2
1216 ///
1217 /// Passing `None` will do nothing.
1218 ///
1219 /// If not set, hyper will use a default.
1220 #[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 /// Sets whether to use an adaptive flow control.
1232 ///
1233 /// Enabling this will override the limits set in
1234 /// `http2_initial_stream_window_size` and
1235 /// `http2_initial_connection_window_size`.
1236 #[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 /// Sets the maximum frame size to use for HTTP2.
1244 ///
1245 /// Passing `None` will do nothing.
1246 ///
1247 /// If not set, hyper will use a default.
1248 #[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 /// Sets an interval for HTTP2 Ping frames should be sent to keep a
1256 /// connection alive.
1257 ///
1258 /// Pass `None` to disable HTTP2 keep-alive.
1259 ///
1260 /// Default is currently disabled.
1261 ///
1262 /// # Cargo Feature
1263 ///
1264 /// Requires the `runtime` cargo feature to be enabled.
1265 #[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 /// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
1277 ///
1278 /// If the ping is not acknowledged within the timeout, the connection will
1279 /// be closed. Does nothing if `http2_keep_alive_interval` is disabled.
1280 ///
1281 /// Default is 20 seconds.
1282 ///
1283 /// # Cargo Feature
1284 ///
1285 /// Requires the `runtime` cargo feature to be enabled.
1286 #[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 /// Sets whether HTTP2 keep-alive should apply while the connection is idle.
1295 ///
1296 /// If disabled, keep-alive pings are only sent while there are open
1297 /// request/responses streams. If enabled, pings are also sent when no
1298 /// streams are active. Does nothing if `http2_keep_alive_interval` is
1299 /// disabled.
1300 ///
1301 /// Default is `false`.
1302 ///
1303 /// # Cargo Feature
1304 ///
1305 /// Requires the `runtime` cargo feature to be enabled.
1306 #[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 /// Sets the maximum number of HTTP2 concurrent locally reset streams.
1315 ///
1316 /// See the documentation of [`h2::client::Builder::max_concurrent_reset_streams`] for more
1317 /// details.
1318 ///
1319 /// The default value is determined by the `h2` crate.
1320 ///
1321 /// [`h2::client::Builder::max_concurrent_reset_streams`]: https://docs.rs/h2/client/struct.Builder.html#method.max_concurrent_reset_streams
1322 #[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 /// Set the maximum write buffer size for each HTTP/2 stream.
1330 ///
1331 /// Default is currently 1MB, but may change.
1332 ///
1333 /// # Panics
1334 ///
1335 /// The value must be no larger than `u32::MAX`.
1336 #[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 /// Set whether to retry requests that get disrupted before ever starting
1344 /// to write.
1345 ///
1346 /// This means a request that is queued, and gets given an idle, reused
1347 /// connection, and then encounters an error immediately as the idle
1348 /// connection was found to be unusable.
1349 ///
1350 /// When this is set to `false`, the related `ResponseFuture` would instead
1351 /// resolve to an `Error::Cancel`.
1352 ///
1353 /// Default is `true`.
1354 #[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 /// Set whether to automatically add the `Host` header to requests.
1361 ///
1362 /// If true, and a request does not include a `Host` header, one will be
1363 /// added automatically, derived from the authority of the `Uri`.
1364 ///
1365 /// Default is `true`.
1366 #[inline]
1367 pub fn set_host(&mut self, val: bool) -> &mut Self {
1368 self.client_config.set_host = val;
1369 self
1370 }
1371
1372 /// Provide an executor to execute background `Connection` tasks.
1373 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 /// Builder a client with this configuration and the default `HttpConnector`.
1382 #[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 /// Combine the configuration of this builder with a connector to create a `Client`.
1396 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&mut DebugStruct<'_, '_>.debug_struct("Builder")
1415 .field("client_config", &self.client_config)
1416 .field("conn_builder", &self.conn_builder)
1417 .field(name:"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}
1526