99//! | | |
1010//! | true (npmmirror) false (npmjs.org)
1111//! | | |
12- //! | fetch_version_manifest resolve_full_manifest
12+ //! | fetch version job fetch full manifest job
1313//! | GET /{name}/{spec} GET /{name}
1414//! | Accept: abbreviated Accept: abbreviated
1515//! | | + If-None-Match: {etag}
2929//! v
3030//! +-----------------------------------------------------------------+
3131//! | http.rs -- HTTP Client (this file) |
32- //! | global singleton reqwest::Client (LazyLock) |
32+ //! | global reqwest::Client pool (LazyLock) |
3333//! | rustls TLS + no_proxy + env proxy + CachingResolver |
3434//! +-----------------------------------------------------------------+
3535//! |
7676//! WASM targets skip DNS entirely (browser handles it).
7777
7878use std:: sync:: LazyLock ;
79+ #[ cfg( not( target_arch = "wasm32" ) ) ]
80+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
7981use std:: time:: Duration ;
8082
8183use anyhow:: { Context , Result , anyhow} ;
@@ -85,16 +87,48 @@ use anyhow::{Context, Result, anyhow};
8587/// error, which silently-stalled sockets never raise.
8688const CONNECT_TIMEOUT : Duration = Duration :: from_secs ( 5 ) ;
8789
88- /// Global HTTP client with connection pooling and DNS caching.
90+ /// Number of independent registry HTTP client pools.
91+ ///
92+ /// GHA npmjs pcap showed bun spreading resolve traffic across a few
93+ /// Cloudflare edge IPs while a single reqwest pool concentrated requests on
94+ /// one IP. Four pools keeps the model small but gives the resolver enough
95+ /// independent keep-alive pools to fan out when npmjs/full-manifest
96+ /// concurrency is raised.
97+ #[ cfg( not( target_arch = "wasm32" ) ) ]
98+ const CLIENT_POOL_SIZE : usize = 4 ;
99+
100+ /// Global HTTP clients with connection pooling and DNS caching.
89101///
90- /// Stores `Result<Client, String>` so that proxy-configuration errors are
102+ /// Stores `Result<Vec< Client> , String>` so that proxy-configuration errors are
91103/// surfaced to callers instead of panicking or calling `process::exit`.
104+ #[ cfg( not( target_arch = "wasm32" ) ) ]
105+ static HTTP_CLIENTS : LazyLock < Result < Vec < reqwest:: Client > , String > > = LazyLock :: new ( || {
106+ ( 0 ..CLIENT_POOL_SIZE )
107+ . map ( |_| client_builder ( ) . and_then ( |b| b. build ( ) . context ( "Failed to build reqwest client" ) ) )
108+ . collect :: < Result < Vec < _ > > > ( )
109+ . map_err ( |e| e. to_string ( ) )
110+ } ) ;
111+
112+ #[ cfg( not( target_arch = "wasm32" ) ) ]
113+ static CLIENT_COUNTER : AtomicUsize = AtomicUsize :: new ( 0 ) ;
114+
115+ #[ cfg( not( target_arch = "wasm32" ) ) ]
116+ pub ( crate ) fn get_client ( ) -> Result < & ' static reqwest:: Client > {
117+ let clients = HTTP_CLIENTS . as_ref ( ) . map_err ( |e| anyhow ! ( "{e}" ) ) ?;
118+ let idx = CLIENT_COUNTER . fetch_add ( 1 , Ordering :: Relaxed ) % clients. len ( ) ;
119+ Ok ( & clients[ idx] )
120+ }
121+
122+ /// WASM targets retain a single browser-backed client; there is no native TCP
123+ /// connection pool to fan out.
124+ #[ cfg( target_arch = "wasm32" ) ]
92125static HTTP_CLIENT : LazyLock < Result < reqwest:: Client , String > > = LazyLock :: new ( || {
93126 client_builder ( )
94127 . and_then ( |b| b. build ( ) . context ( "Failed to build reqwest client" ) )
95128 . map_err ( |e| e. to_string ( ) )
96129} ) ;
97130
131+ #[ cfg( target_arch = "wasm32" ) ]
98132pub ( crate ) fn get_client ( ) -> Result < & ' static reqwest:: Client > {
99133 HTTP_CLIENT . as_ref ( ) . map_err ( |e| anyhow ! ( "{e}" ) )
100134}
0 commit comments