1 | use std::error::Error as StdError; |
2 | use std::fmt; |
3 | use std::mem; |
4 | use std::time::Duration; |
5 | |
6 | use futures_channel::oneshot; |
7 | use futures_util::future::{self, Either, FutureExt as _, TryFutureExt as _}; |
8 | use http::header::{HeaderValue, HOST}; |
9 | use http::uri::{Port, Scheme}; |
10 | use http::{Method, Request, Response, Uri, Version}; |
11 | use tracing::{debug, trace, warn}; |
12 | |
13 | use super::conn; |
14 | use super::connect::{self, sealed::Connect, Alpn, Connected, Connection}; |
15 | use super::pool::{ |
16 | self, CheckoutIsClosedError, Key as PoolKey, Pool, Poolable, Pooled, Reservation, |
17 | }; |
18 | #[cfg (feature = "tcp" )] |
19 | use super::HttpConnector; |
20 | use crate::body::{Body, HttpBody}; |
21 | use crate::common::{exec::BoxSendFuture, sync_wrapper::SyncWrapper, lazy as hyper_lazy, task, Future, Lazy, Pin, Poll}; |
22 | use 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" ))))] |
29 | pub 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)] |
37 | struct 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" ] |
47 | pub 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" )] |
54 | impl 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" )] |
70 | impl Default for Client<HttpConnector, Body> { |
71 | fn default() -> Client<HttpConnector, Body> { |
72 | Client::new() |
73 | } |
74 | } |
75 | |
76 | impl 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 | |
102 | impl<C, B> Client<C, B> |
103 | where |
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 | |
524 | impl<C, B> tower_service::Service<Request<B>> for Client<C, B> |
525 | where |
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 | |
544 | impl<C, B> tower_service::Service<Request<B>> for &'_ Client<C, B> |
545 | where |
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 | |
564 | impl<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 | |
575 | impl<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 | |
583 | impl 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 | |
601 | impl fmt::Debug for ResponseFuture { |
602 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
603 | f.pad("Future<Response>" ) |
604 | } |
605 | } |
606 | |
607 | impl 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)] |
619 | struct PoolClient<B> { |
620 | conn_info: Connected, |
621 | tx: PoolTx<B>, |
622 | } |
623 | |
624 | enum PoolTx<B> { |
625 | Http1(conn::SendRequest<B>), |
626 | #[cfg (feature = "http2" )] |
627 | Http2(conn::Http2SendRequest<B>), |
628 | } |
629 | |
630 | impl<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 | |
668 | impl<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 | |
687 | impl<B> Poolable for PoolClient<B> |
688 | where |
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)] |
729 | enum ClientError<B> { |
730 | Normal(crate::Error), |
731 | Canceled { |
732 | connection_reused: bool, |
733 | req: Request<B>, |
734 | reason: crate::Error, |
735 | }, |
736 | } |
737 | |
738 | impl<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 | |
754 | enum 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)] |
761 | pub(super) enum Ver { |
762 | Auto, |
763 | Http2, |
764 | } |
765 | |
766 | fn 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 | |
781 | fn 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 | |
795 | fn 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 | |
815 | fn 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 | |
839 | fn 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 | |
848 | fn 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 | |
860 | fn 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 | |
868 | fn 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)] |
895 | pub struct Builder { |
896 | client_config: Config, |
897 | conn_builder: conn::Builder, |
898 | pool_config: pool::Config, |
899 | } |
900 | |
901 | impl 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 | |
918 | impl 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 | |
1382 | impl 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)] |
1393 | mod 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 | |