1010
1111use super :: proxy:: ProxyConnector ;
1212
13+ use crate :: pool:: conn:: PermittedRecvStream ;
14+ use crate :: pool:: tls:: TlsConnector ;
15+ use crate :: pool:: { self , Pool , TcpConnector } ;
1316use crate :: utils:: ErrorExt ;
1417
1518use bytes:: Bytes ;
1619use futures:: FutureExt ;
17- use futures:: future:: Either ;
20+ use futures:: future:: { self , Either } ;
1821use http:: Version ;
19- use http_body_util:: BodyExt ;
20- use hyper:: body:: Body ;
22+ use http_body_util:: { BodyExt , Either as EitherBody } ;
23+ use hyper:: body:: { Body , Incoming } ;
2124use hyper:: http:: HeaderValue ;
2225use hyper:: http:: uri:: PathAndQuery ;
2326use hyper:: { HeaderMap , Method , Request , Response , Uri } ;
@@ -26,10 +29,13 @@ use hyper_util::client::legacy::connect::HttpConnector;
2629use restate_types:: config:: HttpOptions ;
2730use rustls:: { ClientConfig , KeyLogFile } ;
2831use std:: error:: Error ;
32+ use std:: fmt;
2933use std:: fmt:: Debug ;
30- use std:: future:: Future ;
34+ use std:: num:: NonZeroUsize ;
35+ use std:: pin:: Pin ;
3136use std:: sync:: { Arc , LazyLock } ;
32- use std:: { fmt, future} ;
37+ use std:: task:: { Context , Poll , ready} ;
38+ use tower:: Layer ;
3339
3440type ProxiedHttpsConnector = ProxyConnector < HttpsConnector < HttpConnector > > ;
3541
@@ -55,7 +61,7 @@ static TLS_CLIENT_CONFIG: LazyLock<ClientConfig> = LazyLock::new(|| {
5561type BoxError = Box < dyn Error + Send + Sync + ' static > ;
5662type BoxBody = http_body_util:: combinators:: BoxBody < Bytes , BoxError > ;
5763
58- #[ derive( Clone , Debug ) ]
64+ #[ derive( Clone ) ]
5965pub struct HttpClient {
6066 /// Client used for HTTPS as long as HTTP1.1 or HTTP2 was not specifically requested.
6167 /// All HTTP versions are possible.
@@ -68,7 +74,7 @@ pub struct HttpClient {
6874 /// Client when HTTP2 was specifically requested - for cleartext, we use h2c,
6975 /// and for HTTPS, we will fail unless the ALPN supports h2.
7076 /// In practice, at discovery time we never force h2 for HTTPS.
71- h2_client : hyper_util :: client :: legacy :: Client < ProxiedHttpsConnector , BoxBody > ,
77+ h2_pool : Pool < ProxyConnector < TlsConnector < TcpConnector > > > ,
7278}
7379
7480impl HttpClient {
@@ -101,11 +107,19 @@ impl HttpClient {
101107 . enable_http1 ( )
102108 . wrap_connector ( http_connector. clone ( ) ) ;
103109
104- let https_h2_connector = hyper_rustls:: HttpsConnectorBuilder :: new ( )
105- . with_tls_config ( TLS_CLIENT_CONFIG . clone ( ) )
106- . https_or_http ( )
107- . enable_http2 ( )
108- . wrap_connector ( http_connector. clone ( ) ) ;
110+ let h2_pool = {
111+ let connector = pool:: tls:: TlsConnectorLayer :: new ( TLS_CLIENT_CONFIG . clone ( ) )
112+ . layer ( pool:: TcpConnector ) ;
113+ let connector = ProxyConnector :: new (
114+ options. http_proxy . clone ( ) ,
115+ options. no_proxy . clone ( ) ,
116+ connector,
117+ ) ;
118+
119+ pool:: PoolBuilder :: default ( )
120+ . max_connections ( NonZeroUsize :: new ( 20 ) . unwrap ( ) )
121+ . build ( connector)
122+ } ;
109123
110124 HttpClient {
111125 alpn_client : builder. clone ( ) . build :: < _ , BoxBody > ( ProxyConnector :: new (
@@ -118,14 +132,7 @@ impl HttpClient {
118132 options. no_proxy . clone ( ) ,
119133 https_h1_connector,
120134 ) ) ,
121- h2_client : {
122- builder. http2_only ( true ) ;
123- builder. build :: < _ , BoxBody > ( ProxyConnector :: new (
124- options. http_proxy . clone ( ) ,
125- options. no_proxy . clone ( ) ,
126- https_h2_connector,
127- ) )
128- } ,
135+ h2_pool,
129136 }
130137 }
131138
@@ -186,10 +193,10 @@ impl HttpClient {
186193 body : B ,
187194 path : PathAndQuery ,
188195 headers : HeaderMap < HeaderValue > ,
189- ) -> impl Future < Output = Result < Response < hyper :: body :: Incoming > , HttpError > > + Send + ' static
196+ ) -> impl Future < Output = Result < Response < ResponseBody > , HttpError > > + Send + ' static
190197 where
191198 B : Body < Data = Bytes > + Send + Sync + Unpin + Sized + ' static ,
192- < B as Body > :: Error : Error + Send + Sync + ' static ,
199+ B :: Error : std :: error :: Error + Send + Sync + ' static ,
193200 {
194201 let request = match Self :: build_request ( uri, version, body, method, path, headers) {
195202 Ok ( request) => request,
@@ -198,21 +205,98 @@ impl HttpClient {
198205
199206 let fut = match version {
200207 // version is set to http1.1 when use_http1.1 is set
201- Some ( Version :: HTTP_11 ) => self . h1_client . request ( request) ,
208+ Some ( Version :: HTTP_11 ) => ResponseMapper {
209+ fut : self . h1_client . request ( request) ,
210+ }
211+ . left_future ( ) ,
202212 // version is set to http2 for cleartext urls when use_http1.1 is not set
203- Some ( Version :: HTTP_2 ) => self . h2_client . request ( request) ,
213+ Some ( Version :: HTTP_2 ) => ResponseMapper {
214+ fut : self . h2_pool . request ( request) ,
215+ }
216+ . right_future ( ) ,
204217 // version is currently set to none for https urls when use_http1.1 is not set
205- None => self . alpn_client . request ( request) ,
218+ None => ResponseMapper {
219+ fut : self . alpn_client . request ( request) ,
220+ }
221+ . left_future ( ) ,
206222 // nothing currently sets a different version, but the alpn client is a sensible default
207- Some ( _) => self . alpn_client . request ( request) ,
223+ Some ( _) => ResponseMapper {
224+ fut : self . alpn_client . request ( request) ,
225+ }
226+ . left_future ( ) ,
208227 } ;
209228
210- Either :: Left ( async move {
211- match fut. await {
212- Ok ( res) => Ok ( res) ,
213- Err ( err) => Err ( err. into ( ) ) ,
214- }
215- } )
229+ Either :: Left ( fut)
230+ }
231+ }
232+
233+ #[ pin_project:: pin_project]
234+ struct ResponseMapper < F , B , E >
235+ where
236+ F : Future < Output = Result < Response < B > , E > > ,
237+ E : Into < HttpError > ,
238+ B : Into < ResponseBody > ,
239+ {
240+ #[ pin]
241+ fut : F ,
242+ }
243+
244+ impl < F , B , E > Future for ResponseMapper < F , B , E >
245+ where
246+ F : Future < Output = Result < Response < B > , E > > ,
247+ E : Into < HttpError > ,
248+ B : Into < ResponseBody > ,
249+ {
250+ type Output = Result < Response < ResponseBody > , HttpError > ;
251+
252+ fn poll ( self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Self :: Output > {
253+ let result = ready ! ( self . project( ) . fut. poll( cx) )
254+ . map_err ( Into :: into)
255+ . map ( |response| response. map ( Into :: into) ) ;
256+
257+ Poll :: Ready ( result)
258+ }
259+ }
260+
261+ /// A wrapper around [`http_body_util::Either`] to hide
262+ /// type complexity for higher layer
263+ #[ pin_project:: pin_project]
264+ pub struct ResponseBody {
265+ #[ pin]
266+ inner : EitherBody < Incoming , PermittedRecvStream > ,
267+ }
268+
269+ impl From < Incoming > for ResponseBody {
270+ fn from ( value : Incoming ) -> Self {
271+ Self {
272+ inner : EitherBody :: Left ( value) ,
273+ }
274+ }
275+ }
276+
277+ impl From < PermittedRecvStream > for ResponseBody {
278+ fn from ( value : PermittedRecvStream ) -> Self {
279+ Self {
280+ inner : EitherBody :: Right ( value) ,
281+ }
282+ }
283+ }
284+
285+ impl Body for ResponseBody {
286+ type Data = Bytes ;
287+ type Error = Box < dyn std:: error:: Error + Send + Sync > ;
288+
289+ fn is_end_stream ( & self ) -> bool {
290+ self . inner . is_end_stream ( )
291+ }
292+ fn poll_frame (
293+ self : std:: pin:: Pin < & mut Self > ,
294+ cx : & mut std:: task:: Context < ' _ > ,
295+ ) -> std:: task:: Poll < Option < Result < http_body:: Frame < Self :: Data > , Self :: Error > > > {
296+ self . project ( ) . inner . poll_frame ( cx)
297+ }
298+ fn size_hint ( & self ) -> http_body:: SizeHint {
299+ self . inner . size_hint ( )
216300 }
217301}
218302
@@ -228,6 +312,8 @@ pub enum HttpError {
228312 Connect ( #[ source] hyper_util:: client:: legacy:: Error ) ,
229313 #[ error( "{}" , FormatHyperError ( . 0 ) ) ]
230314 Hyper ( #[ source] hyper_util:: client:: legacy:: Error ) ,
315+ #[ error( "h2 pool connection error: {0}" ) ]
316+ PoolError ( #[ from] pool:: ConnectionError ) ,
231317}
232318
233319impl HttpError {
@@ -240,6 +326,7 @@ impl HttpError {
240326 HttpError :: PossibleHTTP11Only ( _) => false ,
241327 HttpError :: PossibleHTTP2Only ( _) => false ,
242328 HttpError :: Connect ( _) => true ,
329+ HttpError :: PoolError ( _) => true ,
243330 }
244331 }
245332
0 commit comments