From 6c4ef11f04cc35b61417bf08d5e7592d44197c75 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 18:20:58 -0700 Subject: refactor(ext/fetch): use concrete error types (#26220) --- ext/fetch/lib.rs | 244 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 154 insertions(+), 90 deletions(-) (limited to 'ext/fetch/lib.rs') diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 88f303852..4df8dc3d7 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -17,10 +17,6 @@ use std::sync::Arc; use std::task::Context; use std::task::Poll; -use deno_core::anyhow::anyhow; -use deno_core::anyhow::Error; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::futures::stream::Peekable; use deno_core::futures::Future; use deno_core::futures::FutureExt; @@ -28,6 +24,7 @@ use deno_core::futures::Stream; use deno_core::futures::StreamExt; use deno_core::futures::TryFutureExt; use deno_core::op2; +use deno_core::url; use deno_core::url::Url; use deno_core::AsyncRefCell; use deno_core::AsyncResult; @@ -87,15 +84,18 @@ pub struct Options { pub root_cert_store_provider: Option>, pub proxy: Option, #[allow(clippy::type_complexity)] - pub request_builder_hook: - Option) -> Result<(), AnyError>>, + pub request_builder_hook: Option< + fn(&mut http::Request) -> Result<(), deno_core::error::AnyError>, + >, pub unsafely_ignore_certificate_errors: Option>, pub client_cert_chain_and_key: TlsKeys, pub file_fetch_handler: Rc, } impl Options { - pub fn root_cert_store(&self) -> Result, AnyError> { + pub fn root_cert_store( + &self, + ) -> Result, deno_core::error::AnyError> { Ok(match &self.root_cert_store_provider { Some(provider) => Some(provider.get_or_try_init()?.clone()), None => None, @@ -144,6 +144,51 @@ deno_core::extension!(deno_fetch, }, ); +#[derive(Debug, thiserror::Error)] +pub enum FetchError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error("NetworkError when attempting to fetch resource")] + NetworkError, + #[error("Fetching files only supports the GET method: received {0}")] + FsNotGet(Method), + #[error("Invalid URL {0}")] + InvalidUrl(Url), + #[error(transparent)] + InvalidHeaderName(#[from] http::header::InvalidHeaderName), + #[error(transparent)] + InvalidHeaderValue(#[from] http::header::InvalidHeaderValue), + #[error("{0:?}")] + DataUrl(data_url::DataUrlError), + #[error("{0:?}")] + Base64(data_url::forgiving_base64::InvalidBase64), + #[error("Blob for the given URL not found.")] + BlobNotFound, + #[error("Url scheme '{0}' not supported")] + SchemeNotSupported(String), + #[error("Request was cancelled")] + RequestCanceled, + #[error(transparent)] + Http(#[from] http::Error), + #[error(transparent)] + ClientCreate(#[from] HttpClientCreateError), + #[error(transparent)] + Url(#[from] url::ParseError), + #[error(transparent)] + Method(#[from] http::method::InvalidMethod), + #[error(transparent)] + ClientSend(#[from] ClientSendError), + #[error(transparent)] + RequestBuilderHook(deno_core::error::AnyError), + #[error(transparent)] + Io(#[from] std::io::Error), + // Only used for node upgrade + #[error(transparent)] + Hyper(#[from] hyper::Error), +} + pub type CancelableResponseFuture = Pin>>; @@ -170,11 +215,7 @@ impl FetchHandler for DefaultFileFetchHandler { _state: &mut OpState, _url: &Url, ) -> (CancelableResponseFuture, Option>) { - let fut = async move { - Ok(Err(type_error( - "NetworkError when attempting to fetch resource", - ))) - }; + let fut = async move { Ok(Err(FetchError::NetworkError)) }; (Box::pin(fut), None) } } @@ -191,7 +232,7 @@ pub struct FetchReturn { pub fn get_or_create_client_from_state( state: &mut OpState, -) -> Result { +) -> Result { if let Some(client) = state.try_borrow::() { Ok(client.clone()) } else { @@ -204,11 +245,13 @@ pub fn get_or_create_client_from_state( pub fn create_client_from_options( options: &Options, -) -> Result { +) -> Result { create_http_client( &options.user_agent, CreateHttpClientOptions { - root_cert_store: options.root_cert_store()?, + root_cert_store: options + .root_cert_store() + .map_err(HttpClientCreateError::RootCertStore)?, ca_certs: vec![], proxy: options.proxy.clone(), unsafely_ignore_certificate_errors: options @@ -230,7 +273,9 @@ pub fn create_client_from_options( #[allow(clippy::type_complexity)] pub struct ResourceToBodyAdapter( Rc, - Option>>>>, + Option< + Pin>>>, + >, ); impl ResourceToBodyAdapter { @@ -246,7 +291,7 @@ unsafe impl Send for ResourceToBodyAdapter {} unsafe impl Sync for ResourceToBodyAdapter {} impl Stream for ResourceToBodyAdapter { - type Item = Result; + type Item = Result; fn poll_next( self: Pin<&mut Self>, @@ -276,7 +321,7 @@ impl Stream for ResourceToBodyAdapter { impl hyper::body::Body for ResourceToBodyAdapter { type Data = Bytes; - type Error = Error; + type Error = deno_core::error::AnyError; fn poll_frame( self: Pin<&mut Self>, @@ -301,13 +346,13 @@ pub trait FetchPermissions { &mut self, url: &Url, api_name: &str, - ) -> Result<(), AnyError>; + ) -> Result<(), deno_core::error::AnyError>; #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_read<'a>( &mut self, p: &'a Path, api_name: &str, - ) -> Result, AnyError>; + ) -> Result, deno_core::error::AnyError>; } impl FetchPermissions for deno_permissions::PermissionsContainer { @@ -316,7 +361,7 @@ impl FetchPermissions for deno_permissions::PermissionsContainer { &mut self, url: &Url, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), deno_core::error::AnyError> { deno_permissions::PermissionsContainer::check_net_url(self, url, api_name) } @@ -325,7 +370,7 @@ impl FetchPermissions for deno_permissions::PermissionsContainer { &mut self, path: &'a Path, api_name: &str, - ) -> Result, AnyError> { + ) -> Result, deno_core::error::AnyError> { deno_permissions::PermissionsContainer::check_read_path( self, path, @@ -346,12 +391,15 @@ pub fn op_fetch( has_body: bool, #[buffer] data: Option, #[smi] resource: Option, -) -> Result +) -> Result where FP: FetchPermissions + 'static, { let (client, allow_host) = if let Some(rid) = client_rid { - let r = state.resource_table.get::(rid)?; + let r = state + .resource_table + .get::(rid) + .map_err(FetchError::Resource)?; (r.client.clone(), r.allow_host) } else { (get_or_create_client_from_state(state)?, false) @@ -364,20 +412,18 @@ where let scheme = url.scheme(); let (request_rid, cancel_handle_rid) = match scheme { "file" => { - let path = url.to_file_path().map_err(|_| { - type_error("NetworkError when attempting to fetch resource") - })?; + let path = url.to_file_path().map_err(|_| FetchError::NetworkError)?; let permissions = state.borrow_mut::(); - let path = permissions.check_read(&path, "fetch()")?; + let path = permissions + .check_read(&path, "fetch()") + .map_err(FetchError::Permission)?; let url = match path { Cow::Owned(path) => Url::from_file_path(path).unwrap(), Cow::Borrowed(_) => url, }; if method != Method::GET { - return Err(type_error(format!( - "Fetching files only supports the GET method: received {method}" - ))); + return Err(FetchError::FsNotGet(method)); } let Options { @@ -396,13 +442,15 @@ where } "http" | "https" => { let permissions = state.borrow_mut::(); - permissions.check_net_url(&url, "fetch()")?; + permissions + .check_net_url(&url, "fetch()") + .map_err(FetchError::Resource)?; let maybe_authority = extract_authority(&mut url); let uri = url .as_str() .parse::() - .map_err(|_| type_error(format!("Invalid URL {url}")))?; + .map_err(|_| FetchError::InvalidUrl(url.clone()))?; let mut con_len = None; let body = if has_body { @@ -416,7 +464,10 @@ where .boxed() } (_, Some(resource)) => { - let resource = state.resource_table.take_any(resource)?; + let resource = state + .resource_table + .take_any(resource) + .map_err(FetchError::Resource)?; match resource.size_hint() { (body_size, Some(n)) if body_size == n && body_size > 0 => { con_len = Some(body_size); @@ -453,10 +504,8 @@ where } for (key, value) in headers { - let name = HeaderName::from_bytes(&key) - .map_err(|err| type_error(err.to_string()))?; - let v = HeaderValue::from_bytes(&value) - .map_err(|err| type_error(err.to_string()))?; + let name = HeaderName::from_bytes(&key)?; + let v = HeaderValue::from_bytes(&value)?; if (name != HOST || allow_host) && name != CONTENT_LENGTH { request.headers_mut().append(name, v); @@ -474,20 +523,18 @@ where let options = state.borrow::(); if let Some(request_builder_hook) = options.request_builder_hook { request_builder_hook(&mut request) - .map_err(|err| type_error(err.to_string()))?; + .map_err(FetchError::RequestBuilderHook)?; } let cancel_handle = CancelHandle::new_rc(); let cancel_handle_ = cancel_handle.clone(); - let fut = { - async move { - client - .send(request) - .map_err(Into::into) - .or_cancel(cancel_handle_) - .await - } + let fut = async move { + client + .send(request) + .map_err(Into::into) + .or_cancel(cancel_handle_) + .await }; let request_rid = state.resource_table.add(FetchRequestResource { @@ -501,12 +548,10 @@ where (request_rid, Some(cancel_handle_rid)) } "data" => { - let data_url = DataUrl::process(url.as_str()) - .map_err(|e| type_error(format!("{e:?}")))?; + let data_url = + DataUrl::process(url.as_str()).map_err(FetchError::DataUrl)?; - let (body, _) = data_url - .decode_to_vec() - .map_err(|e| type_error(format!("{e:?}")))?; + let (body, _) = data_url.decode_to_vec().map_err(FetchError::Base64)?; let body = http_body_util::Full::new(body.into()) .map_err(|never| match never {}) .boxed(); @@ -528,11 +573,9 @@ where "blob" => { // Blob URL resolution happens in the JS side of fetch. If we got here is // because the URL isn't an object URL. - return Err(type_error("Blob for the given URL not found.")); - } - _ => { - return Err(type_error(format!("Url scheme '{scheme}' not supported"))) + return Err(FetchError::BlobNotFound); } + _ => return Err(FetchError::SchemeNotSupported(scheme.to_string())), }; Ok(FetchReturn { @@ -564,11 +607,12 @@ pub struct FetchResponse { pub async fn op_fetch_send( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let request = state .borrow_mut() .resource_table - .take::(rid)?; + .take::(rid) + .map_err(FetchError::Resource)?; let request = Rc::try_unwrap(request) .ok() @@ -581,22 +625,23 @@ pub async fn op_fetch_send( // If any error in the chain is a hyper body error, return that as a special result we can use to // reconstruct an error chain (eg: `new TypeError(..., { cause: new Error(...) })`). // TODO(mmastrac): it would be a lot easier if we just passed a v8::Global through here instead - let mut err_ref: &dyn std::error::Error = err.as_ref(); - while let Some(err_src) = std::error::Error::source(err_ref) { - if let Some(err_src) = err_src.downcast_ref::() { - if let Some(err_src) = std::error::Error::source(err_src) { - return Ok(FetchResponse { - error: Some((err.to_string(), err_src.to_string())), - ..Default::default() - }); + + if let FetchError::ClientSend(err_src) = &err { + if let Some(client_err) = std::error::Error::source(&err_src.source) { + if let Some(err_src) = client_err.downcast_ref::() { + if let Some(err_src) = std::error::Error::source(err_src) { + return Ok(FetchResponse { + error: Some((err.to_string(), err_src.to_string())), + ..Default::default() + }); + } } } - err_ref = err_src; } - return Err(type_error(err.to_string())); + return Err(err); } - Err(_) => return Err(type_error("Request was cancelled")), + Err(_) => return Err(FetchError::RequestCanceled), }; let status = res.status(); @@ -636,7 +681,7 @@ pub async fn op_fetch_send( } type CancelableResponseResult = - Result, AnyError>, Canceled>; + Result, FetchError>, Canceled>; pub struct FetchRequestResource { pub future: Pin>>, @@ -691,7 +736,7 @@ impl FetchResponseResource { } } - pub async fn upgrade(self) -> Result { + pub async fn upgrade(self) -> Result { let reader = self.response_reader.into_inner(); match reader { FetchResponseReader::Start(resp) => Ok(hyper::upgrade::on(resp).await?), @@ -746,7 +791,9 @@ impl Resource for FetchResponseResource { // safely call `await` on it without creating a race condition. Some(_) => match reader.as_mut().next().await.unwrap() { Ok(chunk) => assert!(chunk.is_empty()), - Err(err) => break Err(type_error(err.to_string())), + Err(err) => { + break Err(deno_core::error::type_error(err.to_string())) + } }, None => break Ok(BufView::empty()), } @@ -809,14 +856,16 @@ pub fn op_fetch_custom_client( state: &mut OpState, #[serde] args: CreateHttpClientArgs, #[cppgc] tls_keys: &TlsKeysHolder, -) -> Result +) -> Result where FP: FetchPermissions + 'static, { if let Some(proxy) = args.proxy.clone() { let permissions = state.borrow_mut::(); let url = Url::parse(&proxy.url)?; - permissions.check_net_url(&url, "Deno.createHttpClient()")?; + permissions + .check_net_url(&url, "Deno.createHttpClient()") + .map_err(FetchError::Permission)?; } let options = state.borrow::(); @@ -829,7 +878,9 @@ where let client = create_http_client( &options.user_agent, CreateHttpClientOptions { - root_cert_store: options.root_cert_store()?, + root_cert_store: options + .root_cert_store() + .map_err(HttpClientCreateError::RootCertStore)?, ca_certs, proxy: args.proxy, unsafely_ignore_certificate_errors: options @@ -887,19 +938,34 @@ impl Default for CreateHttpClientOptions { } } +#[derive(Debug, thiserror::Error)] +pub enum HttpClientCreateError { + #[error(transparent)] + Tls(deno_tls::TlsError), + #[error("Illegal characters in User-Agent: received {0}")] + InvalidUserAgent(String), + #[error("invalid proxy url")] + InvalidProxyUrl, + #[error("Cannot create Http Client: either `http1` or `http2` needs to be set to true")] + HttpVersionSelectionInvalid, + #[error(transparent)] + RootCertStore(deno_core::error::AnyError), +} + /// Create new instance of async Client. This client supports /// proxies and doesn't follow redirects. pub fn create_http_client( user_agent: &str, options: CreateHttpClientOptions, -) -> Result { +) -> Result { let mut tls_config = deno_tls::create_client_config( options.root_cert_store, options.ca_certs, options.unsafely_ignore_certificate_errors, options.client_cert_chain_and_key.into(), deno_tls::SocketUse::Http, - )?; + ) + .map_err(HttpClientCreateError::Tls)?; // Proxy TLS should not send ALPN tls_config.alpn_protocols.clear(); @@ -919,9 +985,7 @@ pub fn create_http_client( http_connector.enforce_http(false); let user_agent = user_agent.parse::().map_err(|_| { - type_error(format!( - "Illegal characters in User-Agent: received {user_agent}" - )) + HttpClientCreateError::InvalidUserAgent(user_agent.to_string()) })?; let mut builder = @@ -932,7 +996,7 @@ pub fn create_http_client( let mut proxies = proxy::from_env(); if let Some(proxy) = options.proxy { let mut intercept = proxy::Intercept::all(&proxy.url) - .ok_or_else(|| type_error("invalid proxy url"))?; + .ok_or_else(|| HttpClientCreateError::InvalidProxyUrl)?; if let Some(basic_auth) = &proxy.basic_auth { intercept.set_auth(&basic_auth.username, &basic_auth.password); } @@ -964,7 +1028,7 @@ pub fn create_http_client( } (true, true) => {} (false, false) => { - return Err(type_error("Cannot create Http Client: either `http1` or `http2` needs to be set to true")) + return Err(HttpClientCreateError::HttpVersionSelectionInvalid) } } @@ -980,10 +1044,8 @@ pub fn create_http_client( #[op2] #[serde] -pub fn op_utf8_to_byte_string( - #[string] input: String, -) -> Result { - Ok(input.into()) +pub fn op_utf8_to_byte_string(#[string] input: String) -> ByteString { + input.into() } #[derive(Clone, Debug)] @@ -1003,7 +1065,7 @@ const STAR_STAR: HeaderValue = HeaderValue::from_static("*/*"); #[derive(Debug)] pub struct ClientSendError { uri: Uri, - source: hyper_util::client::legacy::Error, + pub source: hyper_util::client::legacy::Error, } impl ClientSendError { @@ -1075,12 +1137,14 @@ impl Client { .oneshot(req) .await .map_err(|e| ClientSendError { uri, source: e })?; - Ok(resp.map(|b| b.map_err(|e| anyhow!(e)).boxed())) + Ok(resp.map(|b| b.map_err(|e| deno_core::anyhow::anyhow!(e)).boxed())) } } -pub type ReqBody = http_body_util::combinators::BoxBody; -pub type ResBody = http_body_util::combinators::BoxBody; +pub type ReqBody = + http_body_util::combinators::BoxBody; +pub type ResBody = + http_body_util::combinators::BoxBody; /// Copied from https://github.com/seanmonstar/reqwest/blob/b9d62a0323d96f11672a61a17bf8849baec00275/src/async_impl/request.rs#L572 /// Check the request URL for a "username:password" type authority, and if -- cgit v1.2.3 From fe9f0ee5934871175758857899fe64e56c397fd5 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Mon, 4 Nov 2024 09:17:21 -0800 Subject: refactor(runtime/permissions): use concrete error types (#26464) --- ext/fetch/lib.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) (limited to 'ext/fetch/lib.rs') diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 4df8dc3d7..7ef26431c 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -39,6 +39,7 @@ use deno_core::OpState; use deno_core::RcRef; use deno_core::Resource; use deno_core::ResourceId; +use deno_permissions::PermissionCheckError; use deno_tls::rustls::RootCertStore; use deno_tls::Proxy; use deno_tls::RootCertStoreProvider; @@ -149,7 +150,7 @@ pub enum FetchError { #[error(transparent)] Resource(deno_core::error::AnyError), #[error(transparent)] - Permission(deno_core::error::AnyError), + Permission(#[from] PermissionCheckError), #[error("NetworkError when attempting to fetch resource")] NetworkError, #[error("Fetching files only supports the GET method: received {0}")] @@ -346,13 +347,13 @@ pub trait FetchPermissions { &mut self, url: &Url, api_name: &str, - ) -> Result<(), deno_core::error::AnyError>; + ) -> Result<(), PermissionCheckError>; #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_read<'a>( &mut self, p: &'a Path, api_name: &str, - ) -> Result, deno_core::error::AnyError>; + ) -> Result, PermissionCheckError>; } impl FetchPermissions for deno_permissions::PermissionsContainer { @@ -361,7 +362,7 @@ impl FetchPermissions for deno_permissions::PermissionsContainer { &mut self, url: &Url, api_name: &str, - ) -> Result<(), deno_core::error::AnyError> { + ) -> Result<(), PermissionCheckError> { deno_permissions::PermissionsContainer::check_net_url(self, url, api_name) } @@ -370,7 +371,7 @@ impl FetchPermissions for deno_permissions::PermissionsContainer { &mut self, path: &'a Path, api_name: &str, - ) -> Result, deno_core::error::AnyError> { + ) -> Result, PermissionCheckError> { deno_permissions::PermissionsContainer::check_read_path( self, path, @@ -414,9 +415,7 @@ where "file" => { let path = url.to_file_path().map_err(|_| FetchError::NetworkError)?; let permissions = state.borrow_mut::(); - let path = permissions - .check_read(&path, "fetch()") - .map_err(FetchError::Permission)?; + let path = permissions.check_read(&path, "fetch()")?; let url = match path { Cow::Owned(path) => Url::from_file_path(path).unwrap(), Cow::Borrowed(_) => url, @@ -442,9 +441,7 @@ where } "http" | "https" => { let permissions = state.borrow_mut::(); - permissions - .check_net_url(&url, "fetch()") - .map_err(FetchError::Resource)?; + permissions.check_net_url(&url, "fetch()")?; let maybe_authority = extract_authority(&mut url); let uri = url @@ -863,9 +860,7 @@ where if let Some(proxy) = args.proxy.clone() { let permissions = state.borrow_mut::(); let url = Url::parse(&proxy.url)?; - permissions - .check_net_url(&url, "Deno.createHttpClient()") - .map_err(FetchError::Permission)?; + permissions.check_net_url(&url, "Deno.createHttpClient()")?; } let options = state.borrow::(); -- cgit v1.2.3 From 032ae7fb19bd01c1de28515facd5c3b2ce821924 Mon Sep 17 00:00:00 2001 From: Sahand Akbarzadeh Date: Fri, 15 Nov 2024 14:14:11 +0330 Subject: feat(ext/fetch): allow embedders to use `hickory_dns_resolver` instead of default `GaiResolver` (#26740) Allows embedders to use `hickory-dns-resolver` instead of threaded "getaddrinfo" resolver in the `fetch()` implementation. --- ext/fetch/lib.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'ext/fetch/lib.rs') diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 7ef26431c..5949f9f75 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +pub mod dns; mod fs_fetch_handler; mod proxy; #[cfg(test)] @@ -91,6 +92,7 @@ pub struct Options { pub unsafely_ignore_certificate_errors: Option>, pub client_cert_chain_and_key: TlsKeys, pub file_fetch_handler: Rc, + pub resolver: dns::Resolver, } impl Options { @@ -114,6 +116,7 @@ impl Default for Options { unsafely_ignore_certificate_errors: None, client_cert_chain_and_key: TlsKeys::Null, file_fetch_handler: Rc::new(DefaultFileFetchHandler), + resolver: dns::Resolver::default(), } } } @@ -255,6 +258,7 @@ pub fn create_client_from_options( .map_err(HttpClientCreateError::RootCertStore)?, ca_certs: vec![], proxy: options.proxy.clone(), + dns_resolver: options.resolver.clone(), unsafely_ignore_certificate_errors: options .unsafely_ignore_certificate_errors .clone(), @@ -835,6 +839,8 @@ pub struct CreateHttpClientArgs { proxy: Option, pool_max_idle_per_host: Option, pool_idle_timeout: Option, + #[serde(default)] + use_hickory_resolver: bool, #[serde(default = "default_true")] http1: bool, #[serde(default = "default_true")] @@ -878,6 +884,13 @@ where .map_err(HttpClientCreateError::RootCertStore)?, ca_certs, proxy: args.proxy, + dns_resolver: if args.use_hickory_resolver { + dns::Resolver::hickory() + .map_err(deno_core::error::AnyError::new) + .map_err(FetchError::Resource)? + } else { + dns::Resolver::default() + }, unsafely_ignore_certificate_errors: options .unsafely_ignore_certificate_errors .clone(), @@ -909,6 +922,7 @@ pub struct CreateHttpClientOptions { pub root_cert_store: Option, pub ca_certs: Vec>, pub proxy: Option, + pub dns_resolver: dns::Resolver, pub unsafely_ignore_certificate_errors: Option>, pub client_cert_chain_and_key: Option, pub pool_max_idle_per_host: Option, @@ -923,6 +937,7 @@ impl Default for CreateHttpClientOptions { root_cert_store: None, ca_certs: vec![], proxy: None, + dns_resolver: dns::Resolver::default(), unsafely_ignore_certificate_errors: None, client_cert_chain_and_key: None, pool_max_idle_per_host: None, @@ -976,7 +991,8 @@ pub fn create_http_client( tls_config.alpn_protocols = alpn_protocols; let tls_config = Arc::from(tls_config); - let mut http_connector = HttpConnector::new(); + let mut http_connector = + HttpConnector::new_with_resolver(options.dns_resolver.clone()); http_connector.enforce_http(false); let user_agent = user_agent.parse::().map_err(|_| { @@ -1051,7 +1067,7 @@ pub struct Client { user_agent: HeaderValue, } -type Connector = proxy::ProxyConnector; +type Connector = proxy::ProxyConnector>; // clippy is wrong here #[allow(clippy::declare_interior_mutable_const)] -- cgit v1.2.3 From 0e2f6e38e7b93e42099f546ef2c01629964d095a Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 19 Nov 2024 10:48:57 +0900 Subject: feat(ext/fetch): Make fetch client parameters configurable (#26909) 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 a hook function. If not provided, the default configuration from hyper crate will be used. Ref #26785 --- ext/fetch/lib.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'ext/fetch/lib.rs') diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 5949f9f75..c8e93b9fe 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -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>, pub proxy: Option, + /// 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 HyperClientBuilder>, #[allow(clippy::type_complexity)] pub request_builder_hook: Option< fn(&mut http::Request) -> 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>, pub http1: bool, pub http2: bool, + pub client_builder_hook: Option 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) -- cgit v1.2.3