refactor(client): Use async/await more (#2437)
* refactor: Use async/await in client.rs * refactor: Simplify client.rs a bit more * refactor: Allow !Unpin in Lazy * Remove some impl Future * Remove some combinator use
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							42587059e6
						
					
				
				
					commit
					f01de8e503
				
			| @@ -162,7 +162,7 @@ where | ||||
|             Version::HTTP_10 => { | ||||
|                 if is_http_connect { | ||||
|                     warn!("CONNECT is not allowed for HTTP/1.0"); | ||||
|                     return ResponseFuture::new(Box::new(future::err( | ||||
|                     return ResponseFuture::new(Box::pin(future::err( | ||||
|                         crate::Error::new_user_unsupported_request_method(), | ||||
|                     ))); | ||||
|                 } | ||||
| @@ -179,35 +179,33 @@ where | ||||
|         let pool_key = match extract_domain(req.uri_mut(), is_http_connect) { | ||||
|             Ok(s) => s, | ||||
|             Err(err) => { | ||||
|                 return ResponseFuture::new(Box::new(future::err(err))); | ||||
|                 return ResponseFuture::new(Box::pin(future::err(err))); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         ResponseFuture::new(Box::new(self.retryably_send_request(req, pool_key))) | ||||
|         ResponseFuture::new(Box::pin(self.clone().retryably_send_request(req, pool_key))) | ||||
|     } | ||||
|  | ||||
|     fn retryably_send_request( | ||||
|         &self, | ||||
|         req: Request<B>, | ||||
|     async fn retryably_send_request( | ||||
|         self, | ||||
|         mut req: Request<B>, | ||||
|         pool_key: PoolKey, | ||||
|     ) -> impl Future<Output = crate::Result<Response<Body>>> { | ||||
|         let client = self.clone(); | ||||
|     ) -> crate::Result<Response<Body>> { | ||||
|         let uri = req.uri().clone(); | ||||
|  | ||||
|         let mut send_fut = client.send_request(req, pool_key.clone()); | ||||
|         future::poll_fn(move |cx| loop { | ||||
|             match ready!(Pin::new(&mut send_fut).poll(cx)) { | ||||
|                 Ok(resp) => return Poll::Ready(Ok(resp)), | ||||
|                 Err(ClientError::Normal(err)) => return Poll::Ready(Err(err)), | ||||
|         loop { | ||||
|             req = match self.send_request(req, pool_key.clone()).await { | ||||
|                 Ok(resp) => return Ok(resp), | ||||
|                 Err(ClientError::Normal(err)) => return Err(err), | ||||
|                 Err(ClientError::Canceled { | ||||
|                     connection_reused, | ||||
|                     mut req, | ||||
|                     reason, | ||||
|                 }) => { | ||||
|                     if !client.config.retry_canceled_requests || !connection_reused { | ||||
|                     if !self.config.retry_canceled_requests || !connection_reused { | ||||
|                         // if client disabled, don't retry | ||||
|                         // a fresh connection means we definitely can't retry | ||||
|                         return Poll::Ready(Err(reason)); | ||||
|                         return Err(reason); | ||||
|                     } | ||||
|  | ||||
|                     trace!( | ||||
| @@ -215,115 +213,112 @@ where | ||||
|                         reason | ||||
|                     ); | ||||
|                     *req.uri_mut() = uri.clone(); | ||||
|                     send_fut = client.send_request(req, pool_key.clone()); | ||||
|                     req | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn send_request( | ||||
|     async fn send_request( | ||||
|         &self, | ||||
|         mut req: Request<B>, | ||||
|         pool_key: PoolKey, | ||||
|     ) -> impl Future<Output = Result<Response<Body>, ClientError<B>>> + Unpin { | ||||
|         let conn = self.connection_for(pool_key); | ||||
|     ) -> Result<Response<Body>, ClientError<B>> { | ||||
|         let mut pooled = self.connection_for(pool_key).await?; | ||||
|  | ||||
|         let set_host = self.config.set_host; | ||||
|         let executor = self.conn_builder.exec.clone(); | ||||
|         conn.and_then(move |mut pooled| { | ||||
|             if pooled.is_http1() { | ||||
|                 if set_host { | ||||
|                     let uri = req.uri().clone(); | ||||
|                     req.headers_mut().entry(HOST).or_insert_with(|| { | ||||
|                         let hostname = uri.host().expect("authority implies host"); | ||||
|                         if let Some(port) = uri.port() { | ||||
|                             let s = format!("{}:{}", hostname, port); | ||||
|                             HeaderValue::from_str(&s) | ||||
|                         } else { | ||||
|                             HeaderValue::from_str(hostname) | ||||
|                         } | ||||
|                         .expect("uri host is valid header value") | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 // CONNECT always sends authority-form, so check it first... | ||||
|                 if req.method() == Method::CONNECT { | ||||
|                     authority_form(req.uri_mut()); | ||||
|                 } else if pooled.conn_info.is_proxied { | ||||
|                     absolute_form(req.uri_mut()); | ||||
|                 } else { | ||||
|                     origin_form(req.uri_mut()); | ||||
|                 }; | ||||
|             } else if req.method() == Method::CONNECT { | ||||
|                 debug!("client does not support CONNECT requests over HTTP2"); | ||||
|                 return Either::Left(future::err(ClientError::Normal( | ||||
|                     crate::Error::new_user_unsupported_request_method(), | ||||
|                 ))); | ||||
|         if pooled.is_http1() { | ||||
|             if self.config.set_host { | ||||
|                 let uri = req.uri().clone(); | ||||
|                 req.headers_mut().entry(HOST).or_insert_with(|| { | ||||
|                     let hostname = uri.host().expect("authority implies host"); | ||||
|                     if let Some(port) = uri.port() { | ||||
|                         let s = format!("{}:{}", hostname, port); | ||||
|                         HeaderValue::from_str(&s) | ||||
|                     } else { | ||||
|                         HeaderValue::from_str(hostname) | ||||
|                     } | ||||
|                     .expect("uri host is valid header value") | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             let fut = pooled | ||||
|                 .send_request_retryable(req) | ||||
|                 .map_err(ClientError::map_with_reused(pooled.is_reused())); | ||||
|             // CONNECT always sends authority-form, so check it first... | ||||
|             if req.method() == Method::CONNECT { | ||||
|                 authority_form(req.uri_mut()); | ||||
|             } else if pooled.conn_info.is_proxied { | ||||
|                 absolute_form(req.uri_mut()); | ||||
|             } else { | ||||
|                 origin_form(req.uri_mut()); | ||||
|             }; | ||||
|         } else if req.method() == Method::CONNECT { | ||||
|             debug!("client does not support CONNECT requests over HTTP2"); | ||||
|             return Err(ClientError::Normal( | ||||
|                 crate::Error::new_user_unsupported_request_method(), | ||||
|             )); | ||||
|         } | ||||
|  | ||||
|             // If the Connector included 'extra' info, add to Response... | ||||
|             let extra_info = pooled.conn_info.extra.clone(); | ||||
|             let fut = fut.map_ok(move |mut res| { | ||||
|                 if let Some(extra) = extra_info { | ||||
|                     extra.set(res.extensions_mut()); | ||||
|                 } | ||||
|                 res | ||||
|         let fut = pooled | ||||
|             .send_request_retryable(req) | ||||
|             .map_err(ClientError::map_with_reused(pooled.is_reused())); | ||||
|  | ||||
|         // If the Connector included 'extra' info, add to Response... | ||||
|         let extra_info = pooled.conn_info.extra.clone(); | ||||
|         let fut = fut.map_ok(move |mut res| { | ||||
|             if let Some(extra) = extra_info { | ||||
|                 extra.set(res.extensions_mut()); | ||||
|             } | ||||
|             res | ||||
|         }); | ||||
|  | ||||
|         // As of futures@0.1.21, there is a race condition in the mpsc | ||||
|         // channel, such that sending when the receiver is closing can | ||||
|         // result in the message being stuck inside the queue. It won't | ||||
|         // ever notify until the Sender side is dropped. | ||||
|         // | ||||
|         // To counteract this, we must check if our senders 'want' channel | ||||
|         // has been closed after having tried to send. If so, error out... | ||||
|         if pooled.is_closed() { | ||||
|             return fut.await; | ||||
|         } | ||||
|  | ||||
|         let mut res = fut.await?; | ||||
|  | ||||
|         // If pooled is HTTP/2, we can toss this reference immediately. | ||||
|         // | ||||
|         // when pooled is dropped, it will try to insert back into the | ||||
|         // pool. To delay that, spawn a future that completes once the | ||||
|         // sender is ready again. | ||||
|         // | ||||
|         // This *should* only be once the related `Connection` has polled | ||||
|         // for a new request to start. | ||||
|         // | ||||
|         // It won't be ready if there is a body to stream. | ||||
|         if pooled.is_http2() || !pooled.is_pool_enabled() || pooled.is_ready() { | ||||
|             drop(pooled); | ||||
|         } else if !res.body().is_end_stream() { | ||||
|             let (delayed_tx, delayed_rx) = oneshot::channel(); | ||||
|             res.body_mut().delayed_eof(delayed_rx); | ||||
|             let on_idle = future::poll_fn(move |cx| pooled.poll_ready(cx)).map(move |_| { | ||||
|                 // At this point, `pooled` is dropped, and had a chance | ||||
|                 // to insert into the pool (if conn was idle) | ||||
|                 drop(delayed_tx); | ||||
|             }); | ||||
|  | ||||
|             // As of futures@0.1.21, there is a race condition in the mpsc | ||||
|             // channel, such that sending when the receiver is closing can | ||||
|             // result in the message being stuck inside the queue. It won't | ||||
|             // ever notify until the Sender side is dropped. | ||||
|             // | ||||
|             // To counteract this, we must check if our senders 'want' channel | ||||
|             // has been closed after having tried to send. If so, error out... | ||||
|             if pooled.is_closed() { | ||||
|                 return Either::Right(Either::Left(fut)); | ||||
|             } | ||||
|             self.conn_builder.exec.execute(on_idle); | ||||
|         } else { | ||||
|             // There's no body to delay, but the connection isn't | ||||
|             // ready yet. Only re-insert when it's ready | ||||
|             let on_idle = future::poll_fn(move |cx| pooled.poll_ready(cx)).map(|_| ()); | ||||
|  | ||||
|             Either::Right(Either::Right(fut.map_ok(move |mut res| { | ||||
|                 // If pooled is HTTP/2, we can toss this reference immediately. | ||||
|                 // | ||||
|                 // when pooled is dropped, it will try to insert back into the | ||||
|                 // pool. To delay that, spawn a future that completes once the | ||||
|                 // sender is ready again. | ||||
|                 // | ||||
|                 // This *should* only be once the related `Connection` has polled | ||||
|                 // for a new request to start. | ||||
|                 // | ||||
|                 // It won't be ready if there is a body to stream. | ||||
|                 if pooled.is_http2() || !pooled.is_pool_enabled() || pooled.is_ready() { | ||||
|                     drop(pooled); | ||||
|                 } else if !res.body().is_end_stream() { | ||||
|                     let (delayed_tx, delayed_rx) = oneshot::channel(); | ||||
|                     res.body_mut().delayed_eof(delayed_rx); | ||||
|                     let on_idle = future::poll_fn(move |cx| pooled.poll_ready(cx)).map(move |_| { | ||||
|                         // At this point, `pooled` is dropped, and had a chance | ||||
|                         // to insert into the pool (if conn was idle) | ||||
|                         drop(delayed_tx); | ||||
|                     }); | ||||
|             self.conn_builder.exec.execute(on_idle); | ||||
|         } | ||||
|  | ||||
|                     executor.execute(on_idle); | ||||
|                 } else { | ||||
|                     // There's no body to delay, but the connection isn't | ||||
|                     // ready yet. Only re-insert when it's ready | ||||
|                     let on_idle = future::poll_fn(move |cx| pooled.poll_ready(cx)).map(|_| ()); | ||||
|  | ||||
|                     executor.execute(on_idle); | ||||
|                 } | ||||
|                 res | ||||
|             }))) | ||||
|         }) | ||||
|         Ok(res) | ||||
|     } | ||||
|  | ||||
|     fn connection_for( | ||||
|     async fn connection_for( | ||||
|         &self, | ||||
|         pool_key: PoolKey, | ||||
|     ) -> impl Future<Output = Result<Pooled<PoolClient<B>>, ClientError<B>>> { | ||||
|     ) -> Result<Pooled<PoolClient<B>>, ClientError<B>> { | ||||
|         // This actually races 2 different futures to try to get a ready | ||||
|         // connection the fastest, and to reduce connection churn. | ||||
|         // | ||||
| @@ -340,9 +335,9 @@ where | ||||
|         let checkout = self.pool.checkout(pool_key.clone()); | ||||
|         let connect = self.connect_to(pool_key); | ||||
|  | ||||
|         let executor = self.conn_builder.exec.clone(); | ||||
|         // The order of the `select` is depended on below... | ||||
|         future::select(checkout, connect).then(move |either| match either { | ||||
|  | ||||
|         match future::select(checkout, connect).await { | ||||
|             // Checkout won, connect future may have been started or not. | ||||
|             // | ||||
|             // If it has, let it finish and insert back into the pool, | ||||
| @@ -366,12 +361,12 @@ where | ||||
|                         }); | ||||
|                     // An execute error here isn't important, we're just trying | ||||
|                     // to prevent a waste of a socket... | ||||
|                     executor.execute(bg); | ||||
|                     self.conn_builder.exec.execute(bg); | ||||
|                 } | ||||
|                 Either::Left(future::ok(checked_out)) | ||||
|                 Ok(checked_out) | ||||
|             } | ||||
|             // Connect won, checkout can just be dropped. | ||||
|             Either::Right((Ok(connected), _checkout)) => Either::Left(future::ok(connected)), | ||||
|             Either::Right((Ok(connected), _checkout)) => Ok(connected), | ||||
|             // Either checkout or connect could get canceled: | ||||
|             // | ||||
|             // 1. Connect is canceled if this is HTTP/2 and there is | ||||
| @@ -380,21 +375,21 @@ where | ||||
|             //    idle connection reliably. | ||||
|             // | ||||
|             // In both cases, we should just wait for the other future. | ||||
|             Either::Left((Err(err), connecting)) => Either::Right(Either::Left({ | ||||
|             Either::Left((Err(err), connecting)) => { | ||||
|                 if err.is_canceled() { | ||||
|                     Either::Left(connecting.map_err(ClientError::Normal)) | ||||
|                     connecting.await.map_err(ClientError::Normal) | ||||
|                 } else { | ||||
|                     Either::Right(future::err(ClientError::Normal(err))) | ||||
|                     Err(ClientError::Normal(err)) | ||||
|                 } | ||||
|             })), | ||||
|             Either::Right((Err(err), checkout)) => Either::Right(Either::Right({ | ||||
|             } | ||||
|             Either::Right((Err(err), checkout)) => { | ||||
|                 if err.is_canceled() { | ||||
|                     Either::Left(checkout.map_err(ClientError::Normal)) | ||||
|                     checkout.await.map_err(ClientError::Normal) | ||||
|                 } else { | ||||
|                     Either::Right(future::err(ClientError::Normal(err))) | ||||
|                     Err(ClientError::Normal(err)) | ||||
|                 } | ||||
|             })), | ||||
|         }) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn connect_to( | ||||
| @@ -459,44 +454,40 @@ where | ||||
|                             conn_builder.http2_only(is_h2); | ||||
|                         } | ||||
|  | ||||
|                         Either::Left(Box::pin( | ||||
|                             conn_builder | ||||
|                                 .handshake(io) | ||||
|                                 .and_then(move |(tx, conn)| { | ||||
|                                     trace!( | ||||
|                                         "handshake complete, spawning background dispatcher task" | ||||
|                                     ); | ||||
|                                     executor.execute( | ||||
|                                         conn.map_err(|e| debug!("client connection error: {}", e)) | ||||
|                                             .map(|_| ()), | ||||
|                                     ); | ||||
|                         Either::Left(Box::pin(async move { | ||||
|                             let (tx, conn) = conn_builder.handshake(io).await?; | ||||
|  | ||||
|                                     // Wait for 'conn' to ready up before we | ||||
|                                     // declare this tx as usable | ||||
|                                     tx.when_ready() | ||||
|                                 }) | ||||
|                                 .map_ok(move |tx| { | ||||
|                                     let tx = { | ||||
|                                         #[cfg(feature = "http2")] | ||||
|                                         { | ||||
|                                             if is_h2 { | ||||
|                                                 PoolTx::Http2(tx.into_http2()) | ||||
|                                             } else { | ||||
|                                                 PoolTx::Http1(tx) | ||||
|                                             } | ||||
|                                         } | ||||
|                                         #[cfg(not(feature = "http2"))] | ||||
|                             trace!("handshake complete, spawning background dispatcher task"); | ||||
|                             executor.execute( | ||||
|                                 conn.map_err(|e| debug!("client connection error: {}", e)) | ||||
|                                     .map(|_| ()), | ||||
|                             ); | ||||
|  | ||||
|                             // Wait for 'conn' to ready up before we | ||||
|                             // declare this tx as usable | ||||
|                             let tx = tx.when_ready().await?; | ||||
|  | ||||
|                             let tx = { | ||||
|                                 #[cfg(feature = "http2")] | ||||
|                                 { | ||||
|                                     if is_h2 { | ||||
|                                         PoolTx::Http2(tx.into_http2()) | ||||
|                                     } else { | ||||
|                                         PoolTx::Http1(tx) | ||||
|                                     }; | ||||
|                                     pool.pooled( | ||||
|                                         connecting, | ||||
|                                         PoolClient { | ||||
|                                             conn_info: connected, | ||||
|                                             tx, | ||||
|                                         }, | ||||
|                                     ) | ||||
|                                 }), | ||||
|                         )) | ||||
|                                     } | ||||
|                                 } | ||||
|                                 #[cfg(not(feature = "http2"))] | ||||
|                                 PoolTx::Http1(tx) | ||||
|                             }; | ||||
|  | ||||
|                             Ok(pool.pooled( | ||||
|                                 connecting, | ||||
|                                 PoolClient { | ||||
|                                     conn_info: connected, | ||||
|                                     tx, | ||||
|                                 }, | ||||
|                             )) | ||||
|                         })) | ||||
|                     }), | ||||
|             ) | ||||
|         }) | ||||
| @@ -563,13 +554,13 @@ impl<C, B> fmt::Debug for Client<C, B> { | ||||
| // ===== impl ResponseFuture ===== | ||||
|  | ||||
| impl ResponseFuture { | ||||
|     fn new(fut: Box<dyn Future<Output = crate::Result<Response<Body>>> + Send>) -> Self { | ||||
|         Self { inner: fut.into() } | ||||
|     fn new(fut: Pin<Box<dyn Future<Output = crate::Result<Response<Body>>> + Send>>) -> Self { | ||||
|         Self { inner: fut } | ||||
|     } | ||||
|  | ||||
|     fn error_version(ver: Version) -> Self { | ||||
|         warn!("Request has unsupported version \"{:?}\"", ver); | ||||
|         ResponseFuture::new(Box::new(future::err( | ||||
|         ResponseFuture::new(Box::pin(future::err( | ||||
|             crate::Error::new_user_unsupported_version(), | ||||
|         ))) | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user