turbo_tasks_fetch/
client.rs1use std::{hash::Hash, sync::LazyLock};
2
3use anyhow::Result;
4use quick_cache::sync::Cache;
5use turbo_rcstr::RcStr;
6use turbo_tasks::{ReadRef, Vc, duration_span, mark_session_dependent};
7
8use crate::{FetchError, FetchResult, HttpResponse, HttpResponseBody};
9
10const MAX_CLIENTS: usize = 16;
11static CLIENT_CACHE: LazyLock<Cache<ReadRef<FetchClientConfig>, reqwest::Client>> =
12 LazyLock::new(|| Cache::new(MAX_CLIENTS));
13
14#[turbo_tasks::value(shared)]
22#[derive(Hash)]
23pub struct FetchClientConfig {
24 pub tls_built_in_webpki_certs: bool,
30 pub tls_built_in_native_certs: bool,
37}
38
39impl Default for FetchClientConfig {
40 fn default() -> Self {
41 Self {
42 tls_built_in_webpki_certs: true,
43 tls_built_in_native_certs: false,
44 }
45 }
46}
47
48impl FetchClientConfig {
49 pub fn try_get_cached_reqwest_client(
62 self: ReadRef<FetchClientConfig>,
63 ) -> reqwest::Result<reqwest::Client> {
64 CLIENT_CACHE.get_or_insert_with(&self, {
65 let this = ReadRef::clone(&self);
66 move || this.try_build_uncached_reqwest_client()
67 })
68 }
69
70 fn try_build_uncached_reqwest_client(&self) -> reqwest::Result<reqwest::Client> {
71 let client_builder = reqwest::Client::builder();
72
73 #[cfg(not(any(
75 all(target_os = "windows", target_arch = "aarch64"),
76 target_arch = "wasm32"
77 )))]
78 let client_builder = client_builder
79 .use_rustls_tls()
80 .tls_built_in_root_certs(false)
81 .tls_built_in_webpki_certs(self.tls_built_in_webpki_certs)
82 .tls_built_in_native_certs(self.tls_built_in_native_certs);
83
84 client_builder.build()
85 }
86}
87
88#[turbo_tasks::value_impl]
89impl FetchClientConfig {
90 #[turbo_tasks::function(network)]
91 pub async fn fetch(
92 self: Vc<FetchClientConfig>,
93 url: RcStr,
94 user_agent: Option<RcStr>,
95 ) -> Result<Vc<FetchResult>> {
96 let url_ref = &*url;
97 let this = self.await?;
98 let tls_built_in_native_certs = this.tls_built_in_native_certs;
99 let response_result: reqwest::Result<HttpResponse> = async move {
100 let reqwest_client = this.try_get_cached_reqwest_client()?;
101
102 let mut builder = reqwest_client.get(url_ref);
103 if let Some(user_agent) = user_agent {
104 builder = builder.header("User-Agent", user_agent.as_str());
105 }
106
107 let response = {
108 let _span = duration_span!("fetch request", url = url_ref);
109 builder.send().await
110 }
111 .and_then(|r| r.error_for_status())?;
112
113 let status = response.status().as_u16();
114
115 let body = {
116 let _span = duration_span!("fetch response", url = url_ref);
117 response.bytes().await?
118 }
119 .to_vec();
120
121 Ok(HttpResponse {
122 status,
123 body: HttpResponseBody(body).resolved_cell(),
124 })
125 }
126 .await;
127
128 match response_result {
129 Ok(resp) => Ok(Vc::cell(Ok(resp.resolved_cell()))),
130 Err(err) => {
131 mark_session_dependent();
133 Ok(Vc::cell(Err(FetchError::from_reqwest_error(
134 &err,
135 &url,
136 tls_built_in_native_certs,
137 )
138 .resolved_cell())))
139 }
140 }
141 }
142}
143
144#[doc(hidden)]
145pub fn __test_only_reqwest_client_cache_clear() {
146 CLIENT_CACHE.clear()
147}
148
149#[doc(hidden)]
150pub fn __test_only_reqwest_client_cache_len() -> usize {
151 CLIENT_CACHE.len()
152}