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, Default)]
23pub struct FetchClientConfig {}
24
25impl FetchClientConfig {
26 pub fn try_get_cached_reqwest_client(
39 self: ReadRef<FetchClientConfig>,
40 ) -> reqwest::Result<reqwest::Client> {
41 CLIENT_CACHE.get_or_insert_with(&self, {
42 let this = ReadRef::clone(&self);
43 move || this.try_build_uncached_reqwest_client()
44 })
45 }
46
47 fn try_build_uncached_reqwest_client(&self) -> reqwest::Result<reqwest::Client> {
48 #[allow(unused_mut)]
49 let mut builder = reqwest::Client::builder();
50 #[cfg(any(target_os = "linux", all(windows, not(target_arch = "aarch64"))))]
51 {
52 use std::sync::Once;
53 static ONCE: Once = Once::new();
54 ONCE.call_once(|| {
55 rustls::crypto::ring::default_provider()
56 .install_default()
57 .unwrap()
58 });
59 builder = builder.tls_backend_rustls();
60 }
61 #[cfg(all(windows, target_arch = "aarch64"))]
62 {
63 builder = builder.tls_backend_native();
64 }
65 #[cfg(target_os = "linux")]
66 {
67 builder = builder.tls_certs_merge(webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().map(
71 |der| {
72 reqwest::Certificate::from_der(der)
73 .expect("webpki_root_certs should parse correctly")
74 },
75 ))
76 }
77 builder.build()
78 }
79}
80
81#[turbo_tasks::value_impl]
82impl FetchClientConfig {
83 #[turbo_tasks::function(network)]
84 pub async fn fetch(
85 self: Vc<FetchClientConfig>,
86 url: RcStr,
87 user_agent: Option<RcStr>,
88 ) -> Result<Vc<FetchResult>> {
89 let url_ref = &*url;
90 let this = self.await?;
91 let response_result: reqwest::Result<HttpResponse> = async move {
92 let reqwest_client = this.try_get_cached_reqwest_client()?;
93
94 let mut builder = reqwest_client.get(url_ref);
95 if let Some(user_agent) = user_agent {
96 builder = builder.header("User-Agent", user_agent.as_str());
97 }
98
99 let response = {
100 let _span = duration_span!("fetch request", url = url_ref);
101 builder.send().await
102 }
103 .and_then(|r| r.error_for_status())?;
104
105 let status = response.status().as_u16();
106
107 let body = {
108 let _span = duration_span!("fetch response", url = url_ref);
109 response.bytes().await?
110 }
111 .to_vec();
112
113 Ok(HttpResponse {
114 status,
115 body: HttpResponseBody(body).resolved_cell(),
116 })
117 }
118 .await;
119
120 match response_result {
121 Ok(resp) => Ok(Vc::cell(Ok(resp.resolved_cell()))),
122 Err(err) => {
123 mark_session_dependent();
125 Ok(Vc::cell(Err(
126 FetchError::from_reqwest_error(&err, &url).resolved_cell()
127 )))
128 }
129 }
130 }
131}
132
133#[doc(hidden)]
134pub fn __test_only_reqwest_client_cache_clear() {
135 CLIENT_CACHE.clear()
136}
137
138#[doc(hidden)]
139pub fn __test_only_reqwest_client_cache_len() -> usize {
140 CLIENT_CACHE.len()
141}