Repository: denoland/deno
Pull request: feat(ext/fetch): Make fetch client parameters configurable

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->

This commit makes HTTP client parameters used in `fetch` API configurable on the extension initialization via a callback `client_builder_hook` that  users can provide.

The main motivation behind this change is to allow `deno_fetch` users to tune the HTTP/2 client to suit their needs, although Deno CLI users will not benefit from it as no JavaScript interface is exposed to set these parameters currently.

It is up to users whether to provide hook functions. If not provided, the default configuration from hyper crate will be used.

Ref #26785


Changed files:
filename: ext/fetch/lib.rs
patch: @@ -67,6 +67,7 @@ use http_body_util::BodyExt;
 use hyper::body::Frame;
 use hyper_util::client::legacy::connect::HttpConnector;
 use hyper_util::client::legacy::connect::HttpInfo;
+use hyper_util::client::legacy::Builder as HyperClientBuilder;
 use hyper_util::rt::TokioExecutor;
 use hyper_util::rt::TokioTimer;
 use serde::Deserialize;
@@ -85,6 +86,16 @@ pub struct Options {
   pub user_agent: String,
   pub root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>>,
   pub proxy: Option<Proxy>,
+  /// A callback to customize HTTP client configuration.
+  ///
+  /// The settings applied with this hook may be overridden by the options
+  /// provided through `Deno.createHttpClient()` API. For instance, if the hook
+  /// calls [`hyper_util::client::legacy::Builder::pool_max_idle_per_host`] with
+  /// a value of 99, and a user calls `Deno.createHttpClient({ poolMaxIdlePerHost: 42 })`,
+  /// the value that will take effect is 42.
+  ///
+  /// For more info on what can be configured, see [`hyper_util::client::legacy::Builder`].
+  pub client_builder_hook: Option<fn(HyperClientBuilder) -> HyperClientBuilder>,
   #[allow(clippy::type_complexity)]
   pub request_builder_hook: Option<
     fn(&mut http::Request<ReqBody>) -> Result<(), deno_core::error::AnyError>,
@@ -112,6 +123,7 @@ impl Default for Options {
       user_agent: "".to_string(),
       root_cert_store_provider: None,
       proxy: None,
+      client_builder_hook: None,
       request_builder_hook: None,
       unsafely_ignore_certificate_errors: None,
       client_cert_chain_and_key: TlsKeys::Null,
@@ -271,6 +283,7 @@ pub fn create_client_from_options(
       pool_idle_timeout: None,
       http1: true,
       http2: true,
+      client_builder_hook: options.client_builder_hook,
     },
   )
 }
@@ -908,6 +921,7 @@ where
       ),
       http1: args.http1,
       http2: args.http2,
+      client_builder_hook: options.client_builder_hook,
     },
   )?;
 
@@ -929,6 +943,7 @@ pub struct CreateHttpClientOptions {
   pub pool_idle_timeout: Option<Option<u64>>,
   pub http1: bool,
   pub http2: bool,
+  pub client_builder_hook: Option<fn(HyperClientBuilder) -> HyperClientBuilder>,
 }
 
 impl Default for CreateHttpClientOptions {
@@ -944,6 +959,7 @@ impl Default for CreateHttpClientOptions {
       pool_idle_timeout: None,
       http1: true,
       http2: true,
+      client_builder_hook: None,
     }
   }
 }
@@ -999,11 +1015,14 @@ pub fn create_http_client(
     HttpClientCreateError::InvalidUserAgent(user_agent.to_string())
   })?;
 
-  let mut builder =
-    hyper_util::client::legacy::Builder::new(TokioExecutor::new());
+  let mut builder = HyperClientBuilder::new(TokioExecutor::new());
   builder.timer(TokioTimer::new());
   builder.pool_timer(TokioTimer::new());
 
+  if let Some(client_builder_hook) = options.client_builder_hook {
+    builder = client_builder_hook(builder);
+  }
+
   let mut proxies = proxy::from_env();
   if let Some(proxy) = options.proxy {
     let mut intercept = proxy::Intercept::all(&proxy.url)

filename: ext/fetch/tests.rs
patch: @@ -126,6 +126,7 @@ async fn rust_test_client_with_resolver(
       dns_resolver: resolver,
       http1: true,
       http2: true,
+      client_builder_hook: None,
     },
   )
   .unwrap();

filename: ext/kv/remote.rs
patch: @@ -210,6 +210,7 @@ impl<P: RemoteDbHandlerPermissions + 'static> DatabaseHandler
         pool_idle_timeout: None,
         http1: false,
         http2: true,
+        client_builder_hook: None,
       },
     )?;
     let fetch_client = FetchClient(client);