@@ -48,6 +48,13 @@ struct AppState {
4848 resolve_cache : RwLock < HashMap < ResolveCacheKey , CachedResolvedUpstream > > ,
4949 doh_cache : RwLock < HashMap < DohCacheKey , CachedDohAnswers > > ,
5050 preferred_upstream_addr : RwLock < HashMap < ResolveCacheKey , SocketAddr > > ,
51+ upstream_h2_pool : RwLock < HashMap < H2PoolKey , client_http2:: SendRequest < Full < Bytes > > > > ,
52+ }
53+
54+ #[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
55+ struct H2PoolKey {
56+ host : String ,
57+ addr : SocketAddr ,
5158}
5259
5360#[ derive( Clone , Debug ) ]
@@ -143,6 +150,7 @@ pub async fn run_proxy(
143150 resolve_cache : RwLock :: new ( HashMap :: new ( ) ) ,
144151 doh_cache : RwLock :: new ( HashMap :: new ( ) ) ,
145152 preferred_upstream_addr : RwLock :: new ( HashMap :: new ( ) ) ,
153+ upstream_h2_pool : RwLock :: new ( HashMap :: new ( ) ) ,
146154 } ) ;
147155 let http_state = state. clone ( ) ;
148156 let https_state = state. clone ( ) ;
@@ -482,30 +490,99 @@ async fn send_once(
482490 body : Bytes ,
483491 path_and_query : & str ,
484492) -> Result < UpstreamResponse > {
485- let request = build_upstream_request ( request_host, method, headers, body, path_and_query) ?;
486-
487493 if upstream_scheme. eq_ignore_ascii_case ( "http" ) {
494+ let request =
495+ build_upstream_request ( request_host, method, headers, body, path_and_query) ?;
488496 let stream = connect_tcp ( addr) . await ?;
489497 return Ok ( UpstreamResponse {
490498 response : send_over_io ( TokioIo :: new ( stream) , request) . await ?,
491499 negotiated_protocol : "http/1.1" ,
492500 } ) ;
493501 }
494502
503+ let pool_key = H2PoolKey {
504+ host : request_host. to_ascii_lowercase ( ) ,
505+ addr,
506+ } ;
507+
508+ if let Some ( mut sender) = get_pooled_h2_sender ( state, & pool_key) . await {
509+ if sender. ready ( ) . await . is_ok ( ) {
510+ let request = build_upstream_request (
511+ request_host,
512+ method. clone ( ) ,
513+ headers. clone ( ) ,
514+ body. clone ( ) ,
515+ path_and_query,
516+ ) ?;
517+ match sender. send_request ( request) . await {
518+ Ok ( response) => {
519+ return Ok ( UpstreamResponse {
520+ response,
521+ negotiated_protocol : "h2" ,
522+ } ) ;
523+ }
524+ Err ( _) => {
525+ remove_h2_sender ( state, & pool_key) . await ;
526+ }
527+ }
528+ } else {
529+ remove_h2_sender ( state, & pool_key) . await ;
530+ }
531+ }
532+
495533 let tls_stream = connect_tls ( state, request_host, outer_sni, ech_config, addr) . await ?;
496534 let negotiated_h2 = tls_stream. get_ref ( ) . 1 . alpn_protocol ( ) == Some ( b"h2" ) ;
535+ let request = build_upstream_request ( request_host, method, headers, body, path_and_query) ?;
536+
497537 if negotiated_h2 {
538+ let mut builder = client_http2:: Builder :: new ( TokioExecutor :: new ( ) ) ;
539+ builder. adaptive_window ( true ) ;
540+ let ( mut sender, connection) = builder
541+ . handshake ( TokioIo :: new ( tls_stream) )
542+ . await
543+ . context ( "failed to initialize upstream HTTP/2 client" ) ?;
544+ tokio:: spawn ( async move {
545+ let _ = connection. await ;
546+ } ) ;
547+ let response = sender
548+ . send_request ( request)
549+ . await
550+ . context ( "failed to contact upstream" ) ?;
551+ store_h2_sender ( state, pool_key, sender) . await ;
498552 return Ok ( UpstreamResponse {
499- response : send_over_io_http2 ( TokioIo :: new ( tls_stream ) , request ) . await ? ,
553+ response,
500554 negotiated_protocol : "h2" ,
501555 } ) ;
502556 }
557+
503558 Ok ( UpstreamResponse {
504559 response : send_over_io ( TokioIo :: new ( tls_stream) , request) . await ?,
505560 negotiated_protocol : "http/1.1" ,
506561 } )
507562}
508563
564+ async fn get_pooled_h2_sender (
565+ state : & AppState ,
566+ key : & H2PoolKey ,
567+ ) -> Option < client_http2:: SendRequest < Full < Bytes > > > {
568+ let pool = state. upstream_h2_pool . read ( ) . await ;
569+ pool. get ( key) . cloned ( )
570+ }
571+
572+ async fn store_h2_sender (
573+ state : & AppState ,
574+ key : H2PoolKey ,
575+ sender : client_http2:: SendRequest < Full < Bytes > > ,
576+ ) {
577+ let mut pool = state. upstream_h2_pool . write ( ) . await ;
578+ pool. insert ( key, sender) ;
579+ }
580+
581+ async fn remove_h2_sender ( state : & AppState , key : & H2PoolKey ) {
582+ let mut pool = state. upstream_h2_pool . write ( ) . await ;
583+ pool. remove ( key) ;
584+ }
585+
509586fn build_upstream_request (
510587 request_host : & str ,
511588 method : Method ,
@@ -545,28 +622,6 @@ where
545622 . context ( "failed to contact upstream" )
546623}
547624
548- async fn send_over_io_http2 < T > (
549- io : TokioIo < T > ,
550- request : Request < Full < Bytes > > ,
551- ) -> Result < Response < Incoming > >
552- where
553- T : tokio:: io:: AsyncRead + tokio:: io:: AsyncWrite + Unpin + Send + ' static ,
554- {
555- let mut builder = client_http2:: Builder :: new ( TokioExecutor :: new ( ) ) ;
556- builder. adaptive_window ( true ) ;
557- let ( mut sender, connection) = builder
558- . handshake ( io)
559- . await
560- . context ( "failed to initialize upstream HTTP/2 client" ) ?;
561- tokio:: spawn ( async move {
562- let _ = connection. await ;
563- } ) ;
564- sender
565- . send_request ( request)
566- . await
567- . context ( "failed to contact upstream" )
568- }
569-
570625async fn connect_tls (
571626 state : & AppState ,
572627 request_host : & str ,
@@ -744,7 +799,12 @@ async fn resolve_upstream(state: &AppState, host: &str, port: u16) -> Result<Res
744799 . into_iter ( )
745800 . map ( |ip| SocketAddr :: new ( ip, port) )
746801 . collect :: < Vec < _ > > ( ) ;
747- let mut upstream = ResolvedUpstream { addrs, ech_config } ;
802+ let upstream = ResolvedUpstream { addrs, ech_config } ;
803+ let resolve_ttl = min_duration_options ( binding_ttl, min_duration_options ( addr_ttl, extra_ttl) )
804+ . unwrap_or ( FALLBACK_RESOLVE_CACHE_TTL ) ;
805+ write_cached_upstream ( state, cache_key. clone ( ) , upstream. clone ( ) , resolve_ttl) . await ;
806+
807+ let mut upstream = upstream;
748808 prioritize_preferred_upstream ( state, & cache_key, & mut upstream. addrs ) . await ;
749809 log_upstream_debug (
750810 state,
@@ -765,9 +825,6 @@ async fn resolve_upstream(state: &AppState, host: &str, port: u16) -> Result<Res
765825 . unwrap_or( "-" )
766826 ) ,
767827 ) ;
768- let resolve_ttl = min_duration_options ( binding_ttl, min_duration_options ( addr_ttl, extra_ttl) )
769- . unwrap_or ( FALLBACK_RESOLVE_CACHE_TTL ) ;
770- write_cached_upstream ( state, cache_key, upstream. clone ( ) , resolve_ttl) . await ;
771828 Ok ( upstream)
772829}
773830
0 commit comments