1use std::error::Error as StdError;
2use std::fmt;
3use std::mem;
4use std::time::Duration;
5
6use futures_channel::oneshot;
7use futures_util::future::{self, Either, FutureExt as _, TryFutureExt as _};
8use http::header::{HeaderValue, HOST};
9use http::uri::{Port, Scheme};
10use http::{Method, Request, Response, Uri, Version};
11use tracing::{debug, trace, warn};
12
13use super::conn;
14use super::connect::{self, sealed::Connect, Alpn, Connected, Connection};
15use super::pool::{
16 self, CheckoutIsClosedError, Key as PoolKey, Pool, Poolable, Pooled, Reservation,
17};
18#[cfg(feature = "tcp")]
19use super::HttpConnector;
20use crate::body::{Body, HttpBody};
21use crate::common::{exec::BoxSendFuture, sync_wrapper::SyncWrapper, lazy as hyper_lazy, task, Future, Lazy, Pin, Poll};
22use crate::rt::Executor;
23
24/// A Client to make outgoing HTTP requests.
25///
26/// `Client` is cheap to clone and cloning is the recommended way to share a `Client`. The
27/// underlying connection pool will be reused.
28#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
29pub struct Client<C, B = Body> {
30 config: Config,
31 conn_builder: conn::Builder,
32 connector: C,
33 pool: Pool<PoolClient<B>>,
34}
35
36#[derive(Clone, Copy, Debug)]
37struct Config {
38 retry_canceled_requests: bool,
39 set_host: bool,
40 ver: Ver,
41}
42
43/// A `Future` that will resolve to an HTTP Response.
44///
45/// This is returned by `Client::request` (and `Client::get`).
46#[must_use = "futures do nothing unless polled"]
47pub struct ResponseFuture {
48 inner: SyncWrapper<Pin<Box<dyn Future<Output = crate::Result<Response<Body>>> + Send>>>,
49}
50
51// ===== impl Client =====
52
53#[cfg(feature = "tcp")]
54impl Client<HttpConnector, Body> {
55 /// Create a new Client with the default [config](Builder).
56 ///
57 /// # Note
58 ///
59 /// The default connector does **not** handle TLS. Speaking to `https`
60 /// destinations will require [configuring a connector that implements
61 /// TLS](https://hyper.rs/guides/client/configuration).
62 #[cfg_attr(docsrs, doc(cfg(feature = "tcp")))]
63 #[inline]
64 pub fn new() -> Client<HttpConnector, Body> {
65 Builder::default().build_http()
66 }
67}
68
69#[cfg(feature = "tcp")]
70impl Default for Client<HttpConnector, Body> {
71 fn default() -> Client<HttpConnector, Body> {
72 Client::new()
73 }
74}
75
76impl Client<(), Body> {
77 /// Create a builder to configure a new `Client`.
78 ///
79 /// # Example
80 ///
81 /// ```
82 /// # #[cfg(feature = "runtime")]
83 /// # fn run () {
84 /// use std::time::Duration;
85 /// use hyper::Client;
86 ///
87 /// let client = Client::builder()
88 /// .pool_idle_timeout(Duration::from_secs(30))
89 /// .http2_only(true)
90 /// .build_http();
91 /// # let infer: Client<_, hyper::Body> = client;
92 /// # drop(infer);
93 /// # }
94 /// # fn main() {}
95 /// ```
96 #[inline]
97 pub fn builder() -> Builder {
98 Builder::default()
99 }
100}
101
102impl<C, B> Client<C, B>
103where
104 C: Connect + Clone + Send + Sync + 'static,
105 B: HttpBody + Send + 'static,
106 B::Data: Send,
107 B::Error: Into<Box<dyn StdError + Send + Sync>>,
108{
109 /// Send a `GET` request to the supplied `Uri`.
110 ///
111 /// # Note
112 ///
113 /// This requires that the `HttpBody` type have a `Default` implementation.
114 /// It *should* return an "empty" version of itself, such that
115 /// `HttpBody::is_end_stream` is `true`.
116 ///
117 /// # Example
118 ///
119 /// ```
120 /// # #[cfg(feature = "runtime")]
121 /// # fn run () {
122 /// use hyper::{Client, Uri};
123 ///
124 /// let client = Client::new();
125 ///
126 /// let future = client.get(Uri::from_static("http://httpbin.org/ip"));
127 /// # }
128 /// # fn main() {}
129 /// ```
130 pub fn get(&self, uri: Uri) -> ResponseFuture
131 where
132 B: Default,
133 {
134 let body = B::default();
135 if !body.is_end_stream() {
136 warn!("default HttpBody used for get() does not return true for is_end_stream");
137 }
138
139 let mut req = Request::new(body);
140 *req.uri_mut() = uri;
141 self.request(req)
142 }
143
144 /// Send a constructed `Request` using this `Client`.
145 ///
146 /// # Example
147 ///
148 /// ```
149 /// # #[cfg(feature = "runtime")]
150 /// # fn run () {
151 /// use hyper::{Body, Method, Client, Request};
152 ///
153 /// let client = Client::new();
154 ///
155 /// let req = Request::builder()
156 /// .method(Method::POST)
157 /// .uri("http://httpbin.org/post")
158 /// .body(Body::from("Hallo!"))
159 /// .expect("request builder");
160 ///
161 /// let future = client.request(req);
162 /// # }
163 /// # fn main() {}
164 /// ```
165 pub fn request(&self, mut req: Request<B>) -> ResponseFuture {
166 let is_http_connect = req.method() == Method::CONNECT;
167 match req.version() {
168 Version::HTTP_11 => (),
169 Version::HTTP_10 => {
170 if is_http_connect {
171 warn!("CONNECT is not allowed for HTTP/1.0");
172 return ResponseFuture::new(future::err(
173 crate::Error::new_user_unsupported_request_method(),
174 ));
175 }
176 }
177 Version::HTTP_2 => (),
178 // completely unsupported HTTP version (like HTTP/0.9)!
179 other => return ResponseFuture::error_version(other),
180 };
181
182 let pool_key = match extract_domain(req.uri_mut(), is_http_connect) {
183 Ok(s) => s,
184 Err(err) => {
185 return ResponseFuture::new(future::err(err));
186 }
187 };
188
189 ResponseFuture::new(self.clone().retryably_send_request(req, pool_key))
190 }
191
192 async fn retryably_send_request(
193 self,
194 mut req: Request<B>,
195 pool_key: PoolKey,
196 ) -> crate::Result<Response<Body>> {
197 let uri = req.uri().clone();
198
199 loop {
200 req = match self.send_request(req, pool_key.clone()).await {
201 Ok(resp) => return Ok(resp),
202 Err(ClientError::Normal(err)) => return Err(err),
203 Err(ClientError::Canceled {
204 connection_reused,
205 mut req,
206 reason,
207 }) => {
208 if !self.config.retry_canceled_requests || !connection_reused {
209 // if client disabled, don't retry
210 // a fresh connection means we definitely can't retry
211 return Err(reason);
212 }
213
214 trace!(
215 "unstarted request canceled, trying again (reason={:?})",
216 reason
217 );
218 *req.uri_mut() = uri.clone();
219 req
220 }
221 }
222 }
223 }
224
225 async fn send_request(
226 &self,
227 mut req: Request<B>,
228 pool_key: PoolKey,
229 ) -> Result<Response<Body>, ClientError<B>> {
230 let mut pooled = match self.connection_for(pool_key).await {
231 Ok(pooled) => pooled,
232 Err(ClientConnectError::Normal(err)) => return Err(ClientError::Normal(err)),
233 Err(ClientConnectError::H2CheckoutIsClosed(reason)) => {
234 return Err(ClientError::Canceled {
235 connection_reused: true,
236 req,
237 reason,
238 })
239 }
240 };
241
242 if pooled.is_http1() {
243 if req.version() == Version::HTTP_2 {
244 warn!("Connection is HTTP/1, but request requires HTTP/2");
245 return Err(ClientError::Normal(
246 crate::Error::new_user_unsupported_version(),
247 ));
248 }
249
250 if self.config.set_host {
251 let uri = req.uri().clone();
252 req.headers_mut().entry(HOST).or_insert_with(|| {
253 let hostname = uri.host().expect("authority implies host");
254 if let Some(port) = get_non_default_port(&uri) {
255 let s = format!("{}:{}", hostname, port);
256 HeaderValue::from_str(&s)
257 } else {
258 HeaderValue::from_str(hostname)
259 }
260 .expect("uri host is valid header value")
261 });
262 }
263
264 // CONNECT always sends authority-form, so check it first...
265 if req.method() == Method::CONNECT {
266 authority_form(req.uri_mut());
267 } else if pooled.conn_info.is_proxied {
268 absolute_form(req.uri_mut());
269 } else {
270 origin_form(req.uri_mut());
271 }
272 } else if req.method() == Method::CONNECT {
273 authority_form(req.uri_mut());
274 }
275
276 let fut = pooled
277 .send_request_retryable(req)
278 .map_err(ClientError::map_with_reused(pooled.is_reused()));
279
280 // If the Connector included 'extra' info, add to Response...
281 let extra_info = pooled.conn_info.extra.clone();
282 let fut = fut.map_ok(move |mut res| {
283 if let Some(extra) = extra_info {
284 extra.set(res.extensions_mut());
285 }
286 res
287 });
288
289 // As of futures@0.1.21, there is a race condition in the mpsc
290 // channel, such that sending when the receiver is closing can
291 // result in the message being stuck inside the queue. It won't
292 // ever notify until the Sender side is dropped.
293 //
294 // To counteract this, we must check if our senders 'want' channel
295 // has been closed after having tried to send. If so, error out...
296 if pooled.is_closed() {
297 return fut.await;
298 }
299
300 let mut res = fut.await?;
301
302 // If pooled is HTTP/2, we can toss this reference immediately.
303 //
304 // when pooled is dropped, it will try to insert back into the
305 // pool. To delay that, spawn a future that completes once the
306 // sender is ready again.
307 //
308 // This *should* only be once the related `Connection` has polled
309 // for a new request to start.
310 //
311 // It won't be ready if there is a body to stream.
312 if pooled.is_http2() || !pooled.is_pool_enabled() || pooled.is_ready() {
313 drop(pooled);
314 } else if !res.body().is_end_stream() {
315 let (delayed_tx, delayed_rx) = oneshot::channel();
316 res.body_mut().delayed_eof(delayed_rx);
317 let on_idle = future::poll_fn(move |cx| pooled.poll_ready(cx)).map(move |_| {
318 // At this point, `pooled` is dropped, and had a chance
319 // to insert into the pool (if conn was idle)
320 drop(delayed_tx);
321 });
322
323 self.conn_builder.exec.execute(on_idle);
324 } else {
325 // There's no body to delay, but the connection isn't
326 // ready yet. Only re-insert when it's ready
327 let on_idle = future::poll_fn(move |cx| pooled.poll_ready(cx)).map(|_| ());
328
329 self.conn_builder.exec.execute(on_idle);
330 }
331
332 Ok(res)
333 }
334
335 async fn connection_for(
336 &self,
337 pool_key: PoolKey,
338 ) -> Result<Pooled<PoolClient<B>>, ClientConnectError> {
339 // This actually races 2 different futures to try to get a ready
340 // connection the fastest, and to reduce connection churn.
341 //
342 // - If the pool has an idle connection waiting, that's used
343 // immediately.
344 // - Otherwise, the Connector is asked to start connecting to
345 // the destination Uri.
346 // - Meanwhile, the pool Checkout is watching to see if any other
347 // request finishes and tries to insert an idle connection.
348 // - If a new connection is started, but the Checkout wins after
349 // (an idle connection became available first), the started
350 // connection future is spawned into the runtime to complete,
351 // and then be inserted into the pool as an idle connection.
352 let checkout = self.pool.checkout(pool_key.clone());
353 let connect = self.connect_to(pool_key);
354 let is_ver_h2 = self.config.ver == Ver::Http2;
355
356 // The order of the `select` is depended on below...
357
358 match future::select(checkout, connect).await {
359 // Checkout won, connect future may have been started or not.
360 //
361 // If it has, let it finish and insert back into the pool,
362 // so as to not waste the socket...
363 Either::Left((Ok(checked_out), connecting)) => {
364 // This depends on the `select` above having the correct
365 // order, such that if the checkout future were ready
366 // immediately, the connect future will never have been
367 // started.
368 //
369 // If it *wasn't* ready yet, then the connect future will
370 // have been started...
371 if connecting.started() {
372 let bg = connecting
373 .map_err(|err| {
374 trace!("background connect error: {}", err);
375 })
376 .map(|_pooled| {
377 // dropping here should just place it in
378 // the Pool for us...
379 });
380 // An execute error here isn't important, we're just trying
381 // to prevent a waste of a socket...
382 self.conn_builder.exec.execute(bg);
383 }
384 Ok(checked_out)
385 }
386 // Connect won, checkout can just be dropped.
387 Either::Right((Ok(connected), _checkout)) => Ok(connected),
388 // Either checkout or connect could get canceled:
389 //
390 // 1. Connect is canceled if this is HTTP/2 and there is
391 // an outstanding HTTP/2 connecting task.
392 // 2. Checkout is canceled if the pool cannot deliver an
393 // idle connection reliably.
394 //
395 // In both cases, we should just wait for the other future.
396 Either::Left((Err(err), connecting)) => {
397 if err.is_canceled() {
398 connecting.await.map_err(ClientConnectError::Normal)
399 } else {
400 Err(ClientConnectError::Normal(err))
401 }
402 }
403 Either::Right((Err(err), checkout)) => {
404 if err.is_canceled() {
405 checkout.await.map_err(move |err| {
406 if is_ver_h2
407 && err.is_canceled()
408 && err.find_source::<CheckoutIsClosedError>().is_some()
409 {
410 ClientConnectError::H2CheckoutIsClosed(err)
411 } else {
412 ClientConnectError::Normal(err)
413 }
414 })
415 } else {
416 Err(ClientConnectError::Normal(err))
417 }
418 }
419 }
420 }
421
422 fn connect_to(
423 &self,
424 pool_key: PoolKey,
425 ) -> impl Lazy<Output = crate::Result<Pooled<PoolClient<B>>>> + Unpin {
426 let executor = self.conn_builder.exec.clone();
427 let pool = self.pool.clone();
428 #[cfg(not(feature = "http2"))]
429 let conn_builder = self.conn_builder.clone();
430 #[cfg(feature = "http2")]
431 let mut conn_builder = self.conn_builder.clone();
432 let ver = self.config.ver;
433 let is_ver_h2 = ver == Ver::Http2;
434 let connector = self.connector.clone();
435 let dst = domain_as_uri(pool_key.clone());
436 hyper_lazy(move || {
437 // Try to take a "connecting lock".
438 //
439 // If the pool_key is for HTTP/2, and there is already a
440 // connection being established, then this can't take a
441 // second lock. The "connect_to" future is Canceled.
442 let connecting = match pool.connecting(&pool_key, ver) {
443 Some(lock) => lock,
444 None => {
445 let canceled =
446 crate::Error::new_canceled().with("HTTP/2 connection in progress");
447 return Either::Right(future::err(canceled));
448 }
449 };
450 Either::Left(
451 connector
452 .connect(connect::sealed::Internal, dst)
453 .map_err(crate::Error::new_connect)
454 .and_then(move |io| {
455 let connected = io.connected();
456 // If ALPN is h2 and we aren't http2_only already,
457 // then we need to convert our pool checkout into
458 // a single HTTP2 one.
459 let connecting = if connected.alpn == Alpn::H2 && !is_ver_h2 {
460 match connecting.alpn_h2(&pool) {
461 Some(lock) => {
462 trace!("ALPN negotiated h2, updating pool");
463 lock
464 }
465 None => {
466 // Another connection has already upgraded,
467 // the pool checkout should finish up for us.
468 let canceled = crate::Error::new_canceled()
469 .with("ALPN upgraded to HTTP/2");
470 return Either::Right(future::err(canceled));
471 }
472 }
473 } else {
474 connecting
475 };
476
477 #[cfg_attr(not(feature = "http2"), allow(unused))]
478 let is_h2 = is_ver_h2 || connected.alpn == Alpn::H2;
479 #[cfg(feature = "http2")]
480 {
481 conn_builder.http2_only(is_h2);
482 }
483
484 Either::Left(Box::pin(async move {
485 let (tx, conn) = conn_builder.handshake(io).await?;
486
487 trace!("handshake complete, spawning background dispatcher task");
488 executor.execute(
489 conn.map_err(|e| debug!("client connection error: {}", e))
490 .map(|_| ()),
491 );
492
493 // Wait for 'conn' to ready up before we
494 // declare this tx as usable
495 let tx = tx.when_ready().await?;
496
497 let tx = {
498 #[cfg(feature = "http2")]
499 {
500 if is_h2 {
501 PoolTx::Http2(tx.into_http2())
502 } else {
503 PoolTx::Http1(tx)
504 }
505 }
506 #[cfg(not(feature = "http2"))]
507 PoolTx::Http1(tx)
508 };
509
510 Ok(pool.pooled(
511 connecting,
512 PoolClient {
513 conn_info: connected,
514 tx,
515 },
516 ))
517 }))
518 }),
519 )
520 })
521 }
522}
523
524impl<C, B> tower_service::Service<Request<B>> for Client<C, B>
525where
526 C: Connect + Clone + Send + Sync + 'static,
527 B: HttpBody + Send + 'static,
528 B::Data: Send,
529 B::Error: Into<Box<dyn StdError + Send + Sync>>,
530{
531 type Response = Response<Body>;
532 type Error = crate::Error;
533 type Future = ResponseFuture;
534
535 fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
536 Poll::Ready(Ok(()))
537 }
538
539 fn call(&mut self, req: Request<B>) -> Self::Future {
540 self.request(req)
541 }
542}
543
544impl<C, B> tower_service::Service<Request<B>> for &'_ Client<C, B>
545where
546 C: Connect + Clone + Send + Sync + 'static,
547 B: HttpBody + Send + 'static,
548 B::Data: Send,
549 B::Error: Into<Box<dyn StdError + Send + Sync>>,
550{
551 type Response = Response<Body>;
552 type Error = crate::Error;
553 type Future = ResponseFuture;
554
555 fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
556 Poll::Ready(Ok(()))
557 }
558
559 fn call(&mut self, req: Request<B>) -> Self::Future {
560 self.request(req)
561 }
562}
563
564impl<C: Clone, B> Clone for Client<C, B> {
565 fn clone(&self) -> Client<C, B> {
566 Client {
567 config: self.config.clone(),
568 conn_builder: self.conn_builder.clone(),
569 connector: self.connector.clone(),
570 pool: self.pool.clone(),
571 }
572 }
573}
574
575impl<C, B> fmt::Debug for Client<C, B> {
576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577 f.debug_struct(name:"Client").finish()
578 }
579}
580
581// ===== impl ResponseFuture =====
582
583impl ResponseFuture {
584 fn new<F>(value: F) -> Self
585 where
586 F: Future<Output = crate::Result<Response<Body>>> + Send + 'static,
587 {
588 Self {
589 inner: SyncWrapper::new(Box::pin(value))
590 }
591 }
592
593 fn error_version(ver: Version) -> Self {
594 warn!("Request has unsupported version \"{:?}\"", ver);
595 ResponseFuture::new(Box::pin(future::err(
596 crate::Error::new_user_unsupported_version(),
597 )))
598 }
599}
600
601impl fmt::Debug for ResponseFuture {
602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603 f.pad("Future<Response>")
604 }
605}
606
607impl Future for ResponseFuture {
608 type Output = crate::Result<Response<Body>>;
609
610 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
611 self.inner.get_mut().as_mut().poll(cx)
612 }
613}
614
615// ===== impl PoolClient =====
616
617// FIXME: allow() required due to `impl Trait` leaking types to this lint
618#[allow(missing_debug_implementations)]
619struct PoolClient<B> {
620 conn_info: Connected,
621 tx: PoolTx<B>,
622}
623
624enum PoolTx<B> {
625 Http1(conn::SendRequest<B>),
626 #[cfg(feature = "http2")]
627 Http2(conn::Http2SendRequest<B>),
628}
629
630impl<B> PoolClient<B> {
631 fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
632 match self.tx {
633 PoolTx::Http1(ref mut tx) => tx.poll_ready(cx),
634 #[cfg(feature = "http2")]
635 PoolTx::Http2(_) => Poll::Ready(Ok(())),
636 }
637 }
638
639 fn is_http1(&self) -> bool {
640 !self.is_http2()
641 }
642
643 fn is_http2(&self) -> bool {
644 match self.tx {
645 PoolTx::Http1(_) => false,
646 #[cfg(feature = "http2")]
647 PoolTx::Http2(_) => true,
648 }
649 }
650
651 fn is_ready(&self) -> bool {
652 match self.tx {
653 PoolTx::Http1(ref tx) => tx.is_ready(),
654 #[cfg(feature = "http2")]
655 PoolTx::Http2(ref tx) => tx.is_ready(),
656 }
657 }
658
659 fn is_closed(&self) -> bool {
660 match self.tx {
661 PoolTx::Http1(ref tx) => tx.is_closed(),
662 #[cfg(feature = "http2")]
663 PoolTx::Http2(ref tx) => tx.is_closed(),
664 }
665 }
666}
667
668impl<B: HttpBody + 'static> PoolClient<B> {
669 fn send_request_retryable(
670 &mut self,
671 req: Request<B>,
672 ) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>>
673 where
674 B: Send,
675 {
676 match self.tx {
677 #[cfg(not(feature = "http2"))]
678 PoolTx::Http1(ref mut tx) => tx.send_request_retryable(req),
679 #[cfg(feature = "http2")]
680 PoolTx::Http1(ref mut tx: &mut SendRequest) => Either::Left(tx.send_request_retryable(req)),
681 #[cfg(feature = "http2")]
682 PoolTx::Http2(ref mut tx: &mut Http2SendRequest) => Either::Right(tx.send_request_retryable(req)),
683 }
684 }
685}
686
687impl<B> Poolable for PoolClient<B>
688where
689 B: Send + 'static,
690{
691 fn is_open(&self) -> bool {
692 match self.tx {
693 PoolTx::Http1(ref tx) => tx.is_ready(),
694 #[cfg(feature = "http2")]
695 PoolTx::Http2(ref tx) => tx.is_ready(),
696 }
697 }
698
699 fn reserve(self) -> Reservation<Self> {
700 match self.tx {
701 PoolTx::Http1(tx) => Reservation::Unique(PoolClient {
702 conn_info: self.conn_info,
703 tx: PoolTx::Http1(tx),
704 }),
705 #[cfg(feature = "http2")]
706 PoolTx::Http2(tx) => {
707 let b = PoolClient {
708 conn_info: self.conn_info.clone(),
709 tx: PoolTx::Http2(tx.clone()),
710 };
711 let a = PoolClient {
712 conn_info: self.conn_info,
713 tx: PoolTx::Http2(tx),
714 };
715 Reservation::Shared(a, b)
716 }
717 }
718 }
719
720 fn can_share(&self) -> bool {
721 self.is_http2()
722 }
723}
724
725// ===== impl ClientError =====
726
727// FIXME: allow() required due to `impl Trait` leaking types to this lint
728#[allow(missing_debug_implementations)]
729enum ClientError<B> {
730 Normal(crate::Error),
731 Canceled {
732 connection_reused: bool,
733 req: Request<B>,
734 reason: crate::Error,
735 },
736}
737
738impl<B> ClientError<B> {
739 fn map_with_reused(conn_reused: bool) -> impl Fn((crate::Error, Option<Request<B>>)) -> Self {
740 move |(err: Error, orig_req: Option>)| {
741 if let Some(req: Request) = orig_req {
742 ClientError::Canceled {
743 connection_reused: conn_reused,
744 reason: err,
745 req,
746 }
747 } else {
748 ClientError::Normal(err)
749 }
750 }
751 }
752}
753
754enum ClientConnectError {
755 Normal(crate::Error),
756 H2CheckoutIsClosed(crate::Error),
757}
758
759/// A marker to identify what version a pooled connection is.
760#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
761pub(super) enum Ver {
762 Auto,
763 Http2,
764}
765
766fn origin_form(uri: &mut Uri) {
767 let path: Uri = match uri.path_and_query() {
768 Some(path: &PathAndQuery) if path.as_str() != "/" => {
769 let mut parts: Parts = ::http::uri::Parts::default();
770 parts.path_and_query = Some(path.clone());
771 Uri::from_parts(parts).expect(msg:"path is valid uri")
772 }
773 _none_or_just_slash: Option<&PathAndQuery> => {
774 debug_assert!(Uri::default() == "/");
775 Uri::default()
776 }
777 };
778 *uri = path
779}
780
781fn absolute_form(uri: &mut Uri) {
782 debug_assert!(uri.scheme().is_some(), "absolute_form needs a scheme");
783 debug_assert!(
784 uri.authority().is_some(),
785 "absolute_form needs an authority"
786 );
787 // If the URI is to HTTPS, and the connector claimed to be a proxy,
788 // then it *should* have tunneled, and so we don't want to send
789 // absolute-form in that case.
790 if uri.scheme() == Some(&Scheme::HTTPS) {
791 origin_form(uri);
792 }
793}
794
795fn authority_form(uri: &mut Uri) {
796 if let Some(path: &PathAndQuery) = uri.path_and_query() {
797 // `https://hyper.rs` would parse with `/` path, don't
798 // annoy people about that...
799 if path != "/" {
800 warn!("HTTP/1.1 CONNECT request stripping path: {:?}", path);
801 }
802 }
803 *uri = match uri.authority() {
804 Some(auth: &Authority) => {
805 let mut parts: Parts = ::http::uri::Parts::default();
806 parts.authority = Some(auth.clone());
807 Uri::from_parts(parts).expect(msg:"authority is valid")
808 }
809 None => {
810 unreachable!("authority_form with relative uri");
811 }
812 };
813}
814
815fn extract_domain(uri: &mut Uri, is_http_connect: bool) -> crate::Result<PoolKey> {
816 let uri_clone: Uri = uri.clone();
817 match (uri_clone.scheme(), uri_clone.authority()) {
818 (Some(scheme: &Scheme), Some(auth: &Authority)) => Ok((scheme.clone(), auth.clone())),
819 (None, Some(auth: &Authority)) if is_http_connect => {
820 let scheme: Scheme = match auth.port_u16() {
821 Some(443) => {
822 set_scheme(uri, scheme:Scheme::HTTPS);
823 Scheme::HTTPS
824 }
825 _ => {
826 set_scheme(uri, scheme:Scheme::HTTP);
827 Scheme::HTTP
828 }
829 };
830 Ok((scheme, auth.clone()))
831 }
832 _ => {
833 debug!("Client requires absolute-form URIs, received: {:?}", uri);
834 Err(crate::Error::new_user_absolute_uri_required())
835 }
836 }
837}
838
839fn domain_as_uri((scheme: Scheme, auth: Authority): PoolKey) -> Uri {
840 http::uri::Builder::new()
841 .scheme(scheme)
842 .authority(auth)
843 .path_and_query("/")
844 .build()
845 .expect(msg:"domain is valid Uri")
846}
847
848fn set_scheme(uri: &mut Uri, scheme: Scheme) {
849 debug_assert!(
850 uri.scheme().is_none(),
851 "set_scheme expects no existing scheme"
852 );
853 let old: Uri = mem::replace(dest:uri, src:Uri::default());
854 let mut parts: ::http::uri::Parts = old.into();
855 parts.scheme = Some(scheme);
856 parts.path_and_query = Some("/".parse().expect(msg:"slash is a valid path"));
857 *uri = Uri::from_parts(parts).expect(msg:"scheme is valid");
858}
859
860fn get_non_default_port(uri: &Uri) -> Option<Port<&str>> {
861 match (uri.port().map(|p: Port<&str>| p.as_u16()), is_schema_secure(uri)) {
862 (Some(443), true) => None,
863 (Some(80), false) => None,
864 _ => uri.port(),
865 }
866}
867
868fn is_schema_secure(uri: &Uri) -> bool {
869 uriOption.scheme_str()
870 .map(|scheme_str: &str| matches!(scheme_str, "wss" | "https"))
871 .unwrap_or_default()
872}
873
874/// A builder to configure a new [`Client`](Client).
875///
876/// # Example
877///
878/// ```
879/// # #[cfg(feature = "runtime")]
880/// # fn run () {
881/// use std::time::Duration;
882/// use hyper::Client;
883///
884/// let client = Client::builder()
885/// .pool_idle_timeout(Duration::from_secs(30))
886/// .http2_only(true)
887/// .build_http();
888/// # let infer: Client<_, hyper::Body> = client;
889/// # drop(infer);
890/// # }
891/// # fn main() {}
892/// ```
893#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
894#[derive(Clone)]
895pub struct Builder {
896 client_config: Config,
897 conn_builder: conn::Builder,
898 pool_config: pool::Config,
899}
900
901impl Default for Builder {
902 fn default() -> Self {
903 Self {
904 client_config: Config {
905 retry_canceled_requests: true,
906 set_host: true,
907 ver: Ver::Auto,
908 },
909 conn_builder: conn::Builder::new(),
910 pool_config: pool::Config {
911 idle_timeout: Some(Duration::from_secs(90)),
912 max_idle_per_host: std::usize::MAX,
913 },
914 }
915 }
916}
917
918impl Builder {
919 #[doc(hidden)]
920 #[deprecated(
921 note = "name is confusing, to disable the connection pool, call pool_max_idle_per_host(0)"
922 )]
923 pub fn keep_alive(&mut self, val: bool) -> &mut Self {
924 if !val {
925 // disable
926 self.pool_max_idle_per_host(0)
927 } else if self.pool_config.max_idle_per_host == 0 {
928 // enable
929 self.pool_max_idle_per_host(std::usize::MAX)
930 } else {
931 // already enabled
932 self
933 }
934 }
935
936 #[doc(hidden)]
937 #[deprecated(note = "renamed to `pool_idle_timeout`")]
938 pub fn keep_alive_timeout<D>(&mut self, val: D) -> &mut Self
939 where
940 D: Into<Option<Duration>>,
941 {
942 self.pool_idle_timeout(val)
943 }
944
945 /// Set an optional timeout for idle sockets being kept-alive.
946 ///
947 /// Pass `None` to disable timeout.
948 ///
949 /// Default is 90 seconds.
950 pub fn pool_idle_timeout<D>(&mut self, val: D) -> &mut Self
951 where
952 D: Into<Option<Duration>>,
953 {
954 self.pool_config.idle_timeout = val.into();
955 self
956 }
957
958 #[doc(hidden)]
959 #[deprecated(note = "renamed to `pool_max_idle_per_host`")]
960 pub fn max_idle_per_host(&mut self, max_idle: usize) -> &mut Self {
961 self.pool_config.max_idle_per_host = max_idle;
962 self
963 }
964
965 /// Sets the maximum idle connection per host allowed in the pool.
966 ///
967 /// Default is `usize::MAX` (no limit).
968 pub fn pool_max_idle_per_host(&mut self, max_idle: usize) -> &mut Self {
969 self.pool_config.max_idle_per_host = max_idle;
970 self
971 }
972
973 // HTTP/1 options
974
975 /// Sets the exact size of the read buffer to *always* use.
976 ///
977 /// Note that setting this option unsets the `http1_max_buf_size` option.
978 ///
979 /// Default is an adaptive read buffer.
980 pub fn http1_read_buf_exact_size(&mut self, sz: usize) -> &mut Self {
981 self.conn_builder.http1_read_buf_exact_size(Some(sz));
982 self
983 }
984
985 /// Set the maximum buffer size for the connection.
986 ///
987 /// Default is ~400kb.
988 ///
989 /// Note that setting this option unsets the `http1_read_exact_buf_size` option.
990 ///
991 /// # Panics
992 ///
993 /// The minimum value allowed is 8192. This method panics if the passed `max` is less than the minimum.
994 #[cfg(feature = "http1")]
995 #[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
996 pub fn http1_max_buf_size(&mut self, max: usize) -> &mut Self {
997 self.conn_builder.http1_max_buf_size(max);
998 self
999 }
1000
1001 /// Set whether HTTP/1 connections will accept spaces between header names
1002 /// and the colon that follow them in responses.
1003 ///
1004 /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
1005 /// parsing.
1006 ///
1007 /// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
1008 /// to say about it:
1009 ///
1010 /// > No whitespace is allowed between the header field-name and colon. In
1011 /// > the past, differences in the handling of such whitespace have led to
1012 /// > security vulnerabilities in request routing and response handling. A
1013 /// > server MUST reject any received request message that contains
1014 /// > whitespace between a header field-name and colon with a response code
1015 /// > of 400 (Bad Request). A proxy MUST remove any such whitespace from a
1016 /// > response message before forwarding the message downstream.
1017 ///
1018 /// Note that this setting does not affect HTTP/2.
1019 ///
1020 /// Default is false.
1021 ///
1022 /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
1023 pub fn http1_allow_spaces_after_header_name_in_responses(&mut self, val: bool) -> &mut Self {
1024 self.conn_builder
1025 .http1_allow_spaces_after_header_name_in_responses(val);
1026 self
1027 }
1028
1029 /// Set whether HTTP/1 connections will accept obsolete line folding for
1030 /// header values.
1031 ///
1032 /// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
1033 /// to say about it:
1034 ///
1035 /// > A server that receives an obs-fold in a request message that is not
1036 /// > within a message/http container MUST either reject the message by
1037 /// > sending a 400 (Bad Request), preferably with a representation
1038 /// > explaining that obsolete line folding is unacceptable, or replace
1039 /// > each received obs-fold with one or more SP octets prior to
1040 /// > interpreting the field value or forwarding the message downstream.
1041 ///
1042 /// > A proxy or gateway that receives an obs-fold in a response message
1043 /// > that is not within a message/http container MUST either discard the
1044 /// > message and replace it with a 502 (Bad Gateway) response, preferably
1045 /// > with a representation explaining that unacceptable line folding was
1046 /// > received, or replace each received obs-fold with one or more SP
1047 /// > octets prior to interpreting the field value or forwarding the
1048 /// > message downstream.
1049 ///
1050 /// > A user agent that receives an obs-fold in a response message that is
1051 /// > not within a message/http container MUST replace each received
1052 /// > obs-fold with one or more SP octets prior to interpreting the field
1053 /// > value.
1054 ///
1055 /// Note that this setting does not affect HTTP/2.
1056 ///
1057 /// Default is false.
1058 ///
1059 /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
1060 pub fn http1_allow_obsolete_multiline_headers_in_responses(&mut self, val: bool) -> &mut Self {
1061 self.conn_builder
1062 .http1_allow_obsolete_multiline_headers_in_responses(val);
1063 self
1064 }
1065
1066 /// Sets whether invalid header lines should be silently ignored in HTTP/1 responses.
1067 ///
1068 /// This mimicks the behaviour of major browsers. You probably don't want this.
1069 /// You should only want this if you are implementing a proxy whose main
1070 /// purpose is to sit in front of browsers whose users access arbitrary content
1071 /// which may be malformed, and they expect everything that works without
1072 /// the proxy to keep working with the proxy.
1073 ///
1074 /// This option will prevent Hyper's client from returning an error encountered
1075 /// when parsing a header, except if the error was caused by the character NUL
1076 /// (ASCII code 0), as Chrome specifically always reject those.
1077 ///
1078 /// The ignorable errors are:
1079 /// * empty header names;
1080 /// * characters that are not allowed in header names, except for `\0` and `\r`;
1081 /// * when `allow_spaces_after_header_name_in_responses` is not enabled,
1082 /// spaces and tabs between the header name and the colon;
1083 /// * missing colon between header name and colon;
1084 /// * characters that are not allowed in header values except for `\0` and `\r`.
1085 ///
1086 /// If an ignorable error is encountered, the parser tries to find the next
1087 /// line in the input to resume parsing the rest of the headers. An error
1088 /// will be emitted nonetheless if it finds `\0` or a lone `\r` while
1089 /// looking for the next line.
1090 pub fn http1_ignore_invalid_headers_in_responses(
1091 &mut self,
1092 val: bool,
1093 ) -> &mut Builder {
1094 self.conn_builder
1095 .http1_ignore_invalid_headers_in_responses(val);
1096 self
1097 }
1098
1099 /// Set whether HTTP/1 connections should try to use vectored writes,
1100 /// or always flatten into a single buffer.
1101 ///
1102 /// Note that setting this to false may mean more copies of body data,
1103 /// but may also improve performance when an IO transport doesn't
1104 /// support vectored writes well, such as most TLS implementations.
1105 ///
1106 /// Setting this to true will force hyper to use queued strategy
1107 /// which may eliminate unnecessary cloning on some TLS backends
1108 ///
1109 /// Default is `auto`. In this mode hyper will try to guess which
1110 /// mode to use
1111 pub fn http1_writev(&mut self, enabled: bool) -> &mut Builder {
1112 self.conn_builder.http1_writev(enabled);
1113 self
1114 }
1115
1116 /// Set whether HTTP/1 connections will write header names as title case at
1117 /// the socket level.
1118 ///
1119 /// Note that this setting does not affect HTTP/2.
1120 ///
1121 /// Default is false.
1122 pub fn http1_title_case_headers(&mut self, val: bool) -> &mut Self {
1123 self.conn_builder.http1_title_case_headers(val);
1124 self
1125 }
1126
1127 /// Set whether to support preserving original header cases.
1128 ///
1129 /// Currently, this will record the original cases received, and store them
1130 /// in a private extension on the `Response`. It will also look for and use
1131 /// such an extension in any provided `Request`.
1132 ///
1133 /// Since the relevant extension is still private, there is no way to
1134 /// interact with the original cases. The only effect this can have now is
1135 /// to forward the cases in a proxy-like fashion.
1136 ///
1137 /// Note that this setting does not affect HTTP/2.
1138 ///
1139 /// Default is false.
1140 pub fn http1_preserve_header_case(&mut self, val: bool) -> &mut Self {
1141 self.conn_builder.http1_preserve_header_case(val);
1142 self
1143 }
1144
1145 /// Set whether HTTP/0.9 responses should be tolerated.
1146 ///
1147 /// Default is false.
1148 pub fn http09_responses(&mut self, val: bool) -> &mut Self {
1149 self.conn_builder.http09_responses(val);
1150 self
1151 }
1152
1153 /// Set whether the connection **must** use HTTP/2.
1154 ///
1155 /// The destination must either allow HTTP2 Prior Knowledge, or the
1156 /// `Connect` should be configured to do use ALPN to upgrade to `h2`
1157 /// as part of the connection process. This will not make the `Client`
1158 /// utilize ALPN by itself.
1159 ///
1160 /// Note that setting this to true prevents HTTP/1 from being allowed.
1161 ///
1162 /// Default is false.
1163 #[cfg(feature = "http2")]
1164 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1165 pub fn http2_only(&mut self, val: bool) -> &mut Self {
1166 self.client_config.ver = if val { Ver::Http2 } else { Ver::Auto };
1167 self
1168 }
1169
1170 /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
1171 /// stream-level flow control.
1172 ///
1173 /// Passing `None` will do nothing.
1174 ///
1175 /// If not set, hyper will use a default.
1176 ///
1177 /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE
1178 #[cfg(feature = "http2")]
1179 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1180 pub fn http2_initial_stream_window_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
1181 self.conn_builder
1182 .http2_initial_stream_window_size(sz.into());
1183 self
1184 }
1185
1186 /// Sets the max connection-level flow control for HTTP2
1187 ///
1188 /// Passing `None` will do nothing.
1189 ///
1190 /// If not set, hyper will use a default.
1191 #[cfg(feature = "http2")]
1192 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1193 pub fn http2_initial_connection_window_size(
1194 &mut self,
1195 sz: impl Into<Option<u32>>,
1196 ) -> &mut Self {
1197 self.conn_builder
1198 .http2_initial_connection_window_size(sz.into());
1199 self
1200 }
1201
1202 /// Sets whether to use an adaptive flow control.
1203 ///
1204 /// Enabling this will override the limits set in
1205 /// `http2_initial_stream_window_size` and
1206 /// `http2_initial_connection_window_size`.
1207 #[cfg(feature = "http2")]
1208 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1209 pub fn http2_adaptive_window(&mut self, enabled: bool) -> &mut Self {
1210 self.conn_builder.http2_adaptive_window(enabled);
1211 self
1212 }
1213
1214 /// Sets the maximum frame size to use for HTTP2.
1215 ///
1216 /// Passing `None` will do nothing.
1217 ///
1218 /// If not set, hyper will use a default.
1219 #[cfg(feature = "http2")]
1220 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1221 pub fn http2_max_frame_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
1222 self.conn_builder.http2_max_frame_size(sz);
1223 self
1224 }
1225
1226 /// Sets an interval for HTTP2 Ping frames should be sent to keep a
1227 /// connection alive.
1228 ///
1229 /// Pass `None` to disable HTTP2 keep-alive.
1230 ///
1231 /// Default is currently disabled.
1232 ///
1233 /// # Cargo Feature
1234 ///
1235 /// Requires the `runtime` cargo feature to be enabled.
1236 #[cfg(feature = "runtime")]
1237 #[cfg(feature = "http2")]
1238 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1239 pub fn http2_keep_alive_interval(
1240 &mut self,
1241 interval: impl Into<Option<Duration>>,
1242 ) -> &mut Self {
1243 self.conn_builder.http2_keep_alive_interval(interval);
1244 self
1245 }
1246
1247 /// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
1248 ///
1249 /// If the ping is not acknowledged within the timeout, the connection will
1250 /// be closed. Does nothing if `http2_keep_alive_interval` is disabled.
1251 ///
1252 /// Default is 20 seconds.
1253 ///
1254 /// # Cargo Feature
1255 ///
1256 /// Requires the `runtime` cargo feature to be enabled.
1257 #[cfg(feature = "runtime")]
1258 #[cfg(feature = "http2")]
1259 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1260 pub fn http2_keep_alive_timeout(&mut self, timeout: Duration) -> &mut Self {
1261 self.conn_builder.http2_keep_alive_timeout(timeout);
1262 self
1263 }
1264
1265 /// Sets whether HTTP2 keep-alive should apply while the connection is idle.
1266 ///
1267 /// If disabled, keep-alive pings are only sent while there are open
1268 /// request/responses streams. If enabled, pings are also sent when no
1269 /// streams are active. Does nothing if `http2_keep_alive_interval` is
1270 /// disabled.
1271 ///
1272 /// Default is `false`.
1273 ///
1274 /// # Cargo Feature
1275 ///
1276 /// Requires the `runtime` cargo feature to be enabled.
1277 #[cfg(feature = "runtime")]
1278 #[cfg(feature = "http2")]
1279 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1280 pub fn http2_keep_alive_while_idle(&mut self, enabled: bool) -> &mut Self {
1281 self.conn_builder.http2_keep_alive_while_idle(enabled);
1282 self
1283 }
1284
1285 /// Sets the maximum number of HTTP2 concurrent locally reset streams.
1286 ///
1287 /// See the documentation of [`h2::client::Builder::max_concurrent_reset_streams`] for more
1288 /// details.
1289 ///
1290 /// The default value is determined by the `h2` crate.
1291 ///
1292 /// [`h2::client::Builder::max_concurrent_reset_streams`]: https://docs.rs/h2/client/struct.Builder.html#method.max_concurrent_reset_streams
1293 #[cfg(feature = "http2")]
1294 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1295 pub fn http2_max_concurrent_reset_streams(&mut self, max: usize) -> &mut Self {
1296 self.conn_builder.http2_max_concurrent_reset_streams(max);
1297 self
1298 }
1299
1300 /// Set the maximum write buffer size for each HTTP/2 stream.
1301 ///
1302 /// Default is currently 1MB, but may change.
1303 ///
1304 /// # Panics
1305 ///
1306 /// The value must be no larger than `u32::MAX`.
1307 #[cfg(feature = "http2")]
1308 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1309 pub fn http2_max_send_buf_size(&mut self, max: usize) -> &mut Self {
1310 self.conn_builder.http2_max_send_buf_size(max);
1311 self
1312 }
1313
1314 /// Set whether to retry requests that get disrupted before ever starting
1315 /// to write.
1316 ///
1317 /// This means a request that is queued, and gets given an idle, reused
1318 /// connection, and then encounters an error immediately as the idle
1319 /// connection was found to be unusable.
1320 ///
1321 /// When this is set to `false`, the related `ResponseFuture` would instead
1322 /// resolve to an `Error::Cancel`.
1323 ///
1324 /// Default is `true`.
1325 #[inline]
1326 pub fn retry_canceled_requests(&mut self, val: bool) -> &mut Self {
1327 self.client_config.retry_canceled_requests = val;
1328 self
1329 }
1330
1331 /// Set whether to automatically add the `Host` header to requests.
1332 ///
1333 /// If true, and a request does not include a `Host` header, one will be
1334 /// added automatically, derived from the authority of the `Uri`.
1335 ///
1336 /// Default is `true`.
1337 #[inline]
1338 pub fn set_host(&mut self, val: bool) -> &mut Self {
1339 self.client_config.set_host = val;
1340 self
1341 }
1342
1343 /// Provide an executor to execute background `Connection` tasks.
1344 pub fn executor<E>(&mut self, exec: E) -> &mut Self
1345 where
1346 E: Executor<BoxSendFuture> + Send + Sync + 'static,
1347 {
1348 self.conn_builder.executor(exec);
1349 self
1350 }
1351
1352 /// Builder a client with this configuration and the default `HttpConnector`.
1353 #[cfg(feature = "tcp")]
1354 pub fn build_http<B>(&self) -> Client<HttpConnector, B>
1355 where
1356 B: HttpBody + Send,
1357 B::Data: Send,
1358 {
1359 let mut connector = HttpConnector::new();
1360 if self.pool_config.is_enabled() {
1361 connector.set_keepalive(self.pool_config.idle_timeout);
1362 }
1363 self.build(connector)
1364 }
1365
1366 /// Combine the configuration of this builder with a connector to create a `Client`.
1367 pub fn build<C, B>(&self, connector: C) -> Client<C, B>
1368 where
1369 C: Connect + Clone,
1370 B: HttpBody + Send,
1371 B::Data: Send,
1372 {
1373 Client {
1374 config: self.client_config,
1375 conn_builder: self.conn_builder.clone(),
1376 connector,
1377 pool: Pool::new(self.pool_config, &self.conn_builder.exec),
1378 }
1379 }
1380}
1381
1382impl fmt::Debug for Builder {
1383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1384 f&mut DebugStruct<'_, '_>.debug_struct("Builder")
1385 .field("client_config", &self.client_config)
1386 .field("conn_builder", &self.conn_builder)
1387 .field(name:"pool_config", &self.pool_config)
1388 .finish()
1389 }
1390}
1391
1392#[cfg(test)]
1393mod unit_tests {
1394 use super::*;
1395
1396 #[test]
1397 fn response_future_is_sync() {
1398 fn assert_sync<T: Sync>() {}
1399 assert_sync::<ResponseFuture>();
1400 }
1401
1402 #[test]
1403 fn set_relative_uri_with_implicit_path() {
1404 let mut uri = "http://hyper.rs".parse().unwrap();
1405 origin_form(&mut uri);
1406 assert_eq!(uri.to_string(), "/");
1407 }
1408
1409 #[test]
1410 fn test_origin_form() {
1411 let mut uri = "http://hyper.rs/guides".parse().unwrap();
1412 origin_form(&mut uri);
1413 assert_eq!(uri.to_string(), "/guides");
1414
1415 let mut uri = "http://hyper.rs/guides?foo=bar".parse().unwrap();
1416 origin_form(&mut uri);
1417 assert_eq!(uri.to_string(), "/guides?foo=bar");
1418 }
1419
1420 #[test]
1421 fn test_absolute_form() {
1422 let mut uri = "http://hyper.rs/guides".parse().unwrap();
1423 absolute_form(&mut uri);
1424 assert_eq!(uri.to_string(), "http://hyper.rs/guides");
1425
1426 let mut uri = "https://hyper.rs/guides".parse().unwrap();
1427 absolute_form(&mut uri);
1428 assert_eq!(uri.to_string(), "/guides");
1429 }
1430
1431 #[test]
1432 fn test_authority_form() {
1433 let _ = pretty_env_logger::try_init();
1434
1435 let mut uri = "http://hyper.rs".parse().unwrap();
1436 authority_form(&mut uri);
1437 assert_eq!(uri.to_string(), "hyper.rs");
1438
1439 let mut uri = "hyper.rs".parse().unwrap();
1440 authority_form(&mut uri);
1441 assert_eq!(uri.to_string(), "hyper.rs");
1442 }
1443
1444 #[test]
1445 fn test_extract_domain_connect_no_port() {
1446 let mut uri = "hyper.rs".parse().unwrap();
1447 let (scheme, host) = extract_domain(&mut uri, true).expect("extract domain");
1448 assert_eq!(scheme, *"http");
1449 assert_eq!(host, "hyper.rs");
1450 }
1451
1452 #[test]
1453 fn test_is_secure() {
1454 assert_eq!(
1455 is_schema_secure(&"http://hyper.rs".parse::<Uri>().unwrap()),
1456 false
1457 );
1458 assert_eq!(is_schema_secure(&"hyper.rs".parse::<Uri>().unwrap()), false);
1459 assert_eq!(
1460 is_schema_secure(&"wss://hyper.rs".parse::<Uri>().unwrap()),
1461 true
1462 );
1463 assert_eq!(
1464 is_schema_secure(&"ws://hyper.rs".parse::<Uri>().unwrap()),
1465 false
1466 );
1467 }
1468
1469 #[test]
1470 fn test_get_non_default_port() {
1471 assert!(get_non_default_port(&"http://hyper.rs".parse::<Uri>().unwrap()).is_none());
1472 assert!(get_non_default_port(&"http://hyper.rs:80".parse::<Uri>().unwrap()).is_none());
1473 assert!(get_non_default_port(&"https://hyper.rs:443".parse::<Uri>().unwrap()).is_none());
1474 assert!(get_non_default_port(&"hyper.rs:80".parse::<Uri>().unwrap()).is_none());
1475
1476 assert_eq!(
1477 get_non_default_port(&"http://hyper.rs:123".parse::<Uri>().unwrap())
1478 .unwrap()
1479 .as_u16(),
1480 123
1481 );
1482 assert_eq!(
1483 get_non_default_port(&"https://hyper.rs:80".parse::<Uri>().unwrap())
1484 .unwrap()
1485 .as_u16(),
1486 80
1487 );
1488 assert_eq!(
1489 get_non_default_port(&"hyper.rs:123".parse::<Uri>().unwrap())
1490 .unwrap()
1491 .as_u16(),
1492 123
1493 );
1494 }
1495}
1496