Skip to content

Commit e6083a1

Browse files
perf: load resources of tauri:// protocol asynchronously (#15220)
* fix: async loading of resources for tauri protocol * Move things under one Arc * Move out to `proxy_dev_request` * Clean up and don't return empty if cache not hit * Add change file --------- Co-authored-by: Tony <legendmastertony@gmail.com>
1 parent fca4a31 commit e6083a1

2 files changed

Lines changed: 147 additions & 95 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
tauri: patch:perf
3+
---
4+
5+
Load `tauri://` custom protocol handlers asynchronously to speed up load time

crates/tauri/src/protocol/tauri.rs

Lines changed: 142 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ use std::{collections::HashMap, sync::Mutex};
2121
struct CachedResponse {
2222
status: http::StatusCode,
2323
headers: http::HeaderMap,
24-
body: bytes::Bytes,
24+
body: Vec<u8>,
2525
}
2626

2727
pub fn get<R: Runtime>(
28-
#[allow(unused_variables)] manager: Arc<AppManager<R>>,
28+
manager: Arc<AppManager<R>>,
2929
window_origin: &str,
3030
web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
3131
) -> UriSchemeProtocolHandler {
@@ -39,6 +39,7 @@ pub fn get<R: Runtime>(
3939
url.pop();
4040
}
4141

42+
#[allow(unused_mut)]
4243
let mut client_builder = reqwest::ClientBuilder::new();
4344
if use_https {
4445
#[cfg(feature = "rustls-tls")]
@@ -47,6 +48,7 @@ pub fn get<R: Runtime>(
4748
}
4849

4950
// we can't load env vars at runtime, gotta embed them in the lib
51+
#[allow(unused_variables)]
5052
if let Some(cert_pem) = option_env!("TAURI_DEV_ROOT_CERTIFICATE") {
5153
#[cfg(any(
5254
feature = "native-tls",
@@ -78,44 +80,70 @@ pub fn get<R: Runtime>(
7880
}
7981
let client = client_builder.build().unwrap();
8082

81-
let response_cache = Arc::new(Mutex::new(HashMap::new()));
83+
let response_cache = Mutex::new(HashMap::new());
8284

8385
(url, client, response_cache)
8486
};
8587

88+
let context = Arc::new(Context {
89+
manager,
90+
web_resource_request_handler,
91+
window_origin,
92+
#[cfg(all(dev, mobile))]
93+
client,
94+
#[cfg(all(dev, mobile))]
95+
url,
96+
#[cfg(all(dev, mobile))]
97+
response_cache,
98+
});
99+
86100
Box::new(move |_, request, responder| {
87-
match get_response(
88-
request,
89-
&manager,
90-
&window_origin,
91-
web_resource_request_handler.as_deref(),
92-
#[cfg(all(dev, mobile))]
93-
(&url, &client, &response_cache),
94-
) {
95-
Ok(response) => responder.respond(response),
96-
Err(e) => responder.respond(
97-
HttpResponse::builder()
98-
.status(StatusCode::INTERNAL_SERVER_ERROR)
99-
.header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
100-
.header("Access-Control-Allow-Origin", &window_origin)
101-
.body(e.to_string().into_bytes())
102-
.unwrap(),
103-
),
104-
}
101+
let context = context.clone();
102+
crate::async_runtime::spawn(async move {
103+
match get_response(&context, request).await {
104+
Ok(response) => responder.respond(response),
105+
Err(e) => responder.respond(
106+
HttpResponse::builder()
107+
.status(StatusCode::INTERNAL_SERVER_ERROR)
108+
.header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
109+
.header("Access-Control-Allow-Origin", &context.window_origin)
110+
.body(e.to_string().into_bytes())
111+
.unwrap(),
112+
),
113+
}
114+
});
105115
})
106116
}
107117

108-
fn get_response<R: Runtime>(
109-
#[allow(unused_mut)] mut request: Request<Vec<u8>>,
110-
#[allow(unused_variables)] manager: &AppManager<R>,
111-
window_origin: &str,
112-
web_resource_request_handler: Option<&WebResourceRequestHandler>,
113-
#[cfg(all(dev, mobile))] (url, client, response_cache): (
114-
&str,
115-
&reqwest::Client,
116-
&Arc<Mutex<HashMap<String, CachedResponse>>>,
117-
),
118+
struct Context<R: Runtime> {
119+
manager: Arc<AppManager<R>>,
120+
window_origin: String,
121+
web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
122+
123+
#[cfg(all(dev, mobile))]
124+
url: String,
125+
#[cfg(all(dev, mobile))]
126+
client: reqwest::Client,
127+
#[cfg(all(dev, mobile))]
128+
response_cache: Mutex<HashMap<String, CachedResponse>>,
129+
}
130+
131+
async fn get_response<R: Runtime>(
132+
context: &Context<R>,
133+
request: Request<Vec<u8>>,
118134
) -> Result<HttpResponse<Cow<'static, [u8]>>, Box<dyn std::error::Error>> {
135+
let Context {
136+
manager,
137+
web_resource_request_handler,
138+
window_origin,
139+
#[cfg(all(dev, mobile))]
140+
client,
141+
#[cfg(all(dev, mobile))]
142+
url,
143+
#[cfg(all(dev, mobile))]
144+
response_cache,
145+
} = context;
146+
119147
// use the entire URI as we are going to proxy the request
120148
let path = if PROXY_DEV_SERVER {
121149
request.uri().to_string()
@@ -137,86 +165,105 @@ fn get_response<R: Runtime>(
137165
// where `$P` is not `localhost/*`
138166
.unwrap_or_default();
139167

168+
#[allow(unused_mut)]
140169
let mut builder = HttpResponse::builder()
141170
.add_configured_headers(manager.config.app.security.headers.as_ref())
142171
.header("Access-Control-Allow-Origin", window_origin);
143172

144173
#[cfg(all(dev, mobile))]
145-
let mut response = {
146-
let decoded_path = percent_encoding::percent_decode(path.as_bytes())
147-
.decode_utf8_lossy()
148-
.to_string();
149-
let url = format!(
150-
"{}/{}",
151-
url.trim_end_matches('/'),
152-
decoded_path.trim_start_matches('/')
153-
);
154-
155-
let mut proxy_builder = client.request(request.method().clone(), &url);
156-
for (name, value) in request.headers() {
157-
proxy_builder = proxy_builder.header(name, value);
158-
}
159-
proxy_builder = proxy_builder.body(request.body().clone());
160-
match crate::async_runtime::safe_block_on(proxy_builder.send()) {
161-
Ok(r) => {
162-
let mut response_cache_ = response_cache.lock().unwrap();
163-
let mut response = None;
164-
if r.status() == http::StatusCode::NOT_MODIFIED {
165-
response = response_cache_.get(&url);
166-
}
167-
let response = if let Some(r) = response {
168-
r
169-
} else {
170-
let status = r.status();
171-
let headers = r.headers().clone();
172-
let body = crate::async_runtime::safe_block_on(r.bytes())?;
173-
let response = CachedResponse {
174-
status,
175-
headers,
176-
body,
177-
};
178-
response_cache_.insert(url.clone(), response);
179-
response_cache_.get(&url).unwrap()
180-
};
181-
for (name, value) in &response.headers {
182-
builder = builder.header(name, value);
183-
}
184-
builder
185-
.status(response.status)
186-
.body(response.body.to_vec().into())?
187-
}
188-
Err(e) => {
189-
let error_message = format!(
190-
"Failed to request {}: {}{}",
191-
url.as_str(),
192-
e,
193-
if let Some(s) = e.status() {
194-
format!("status code: {}", s.as_u16())
195-
} else if cfg!(target_os = "ios") {
196-
", did you grant local network permissions? That is required to reach the development server. Please grant the permission via the prompt or in `Settings > Privacy & Security > Local Network` and restart the app. See https://support.apple.com/en-us/102229 for more information.".to_string()
197-
} else {
198-
"".to_string()
199-
}
200-
);
201-
log::error!("{error_message}");
202-
return Err(error_message.into());
203-
}
204-
}
205-
};
174+
let mut response =
175+
proxy_dev_request(client, url, response_cache, path, builder, &request).await?;
206176

207177
#[cfg(not(all(dev, mobile)))]
208178
let mut response = {
209-
let use_https_scheme = request.uri().scheme() == Some(&http::uri::Scheme::HTTPS);
210-
let asset = manager.get_asset(path, use_https_scheme)?;
179+
let asset = manager.get_asset(
180+
path,
181+
request.uri().scheme() == Some(&http::uri::Scheme::HTTPS),
182+
)?;
211183
builder = builder.header(CONTENT_TYPE, &asset.mime_type);
212184
if let Some(csp) = &asset.csp_header {
213185
builder = builder.header("Content-Security-Policy", csp);
214186
}
215187
builder.body(asset.bytes.into())?
216188
};
217-
if let Some(handler) = &web_resource_request_handler {
189+
190+
if let Some(handler) = web_resource_request_handler {
218191
handler(request, &mut response);
219192
}
220193

221194
Ok(response)
222195
}
196+
197+
#[cfg(all(dev, mobile))]
198+
async fn proxy_dev_request(
199+
client: &reqwest::Client,
200+
url: &String,
201+
response_cache: &Mutex<HashMap<String, CachedResponse>>,
202+
path: String,
203+
mut builder: http::response::Builder,
204+
request: &Request<Vec<u8>>,
205+
) -> Result<HttpResponse<Cow<'static, [u8]>>, Box<dyn std::error::Error>> {
206+
let decoded_path = percent_encoding::percent_decode(path.as_bytes())
207+
.decode_utf8_lossy()
208+
.to_string();
209+
let url = format!(
210+
"{}/{}",
211+
url.trim_end_matches('/'),
212+
decoded_path.trim_start_matches('/')
213+
);
214+
215+
let mut proxy_builder = client.request(request.method().clone(), &url);
216+
for (name, value) in request.headers() {
217+
proxy_builder = proxy_builder.header(name, value);
218+
}
219+
proxy_builder = proxy_builder.body(request.body().clone());
220+
221+
let response = proxy_builder.send().await.map_err(|e|{
222+
let error_message = format!(
223+
"Failed to request {url}: {e}{}",
224+
if let Some(s) = e.status() {
225+
format!("status code: {}", s.as_u16())
226+
} else if cfg!(target_os = "ios") {
227+
", did you grant local network permissions? That is required to reach the development server. Please grant the permission via the prompt or in `Settings > Privacy & Security > Local Network` and restart the app. See https://support.apple.com/en-us/102229 for more information.".to_string()
228+
} else {
229+
"".to_string()
230+
}
231+
);
232+
log::error!("{error_message}");
233+
error_message
234+
})?;
235+
236+
let status = response.status();
237+
238+
if status == http::StatusCode::NOT_MODIFIED {
239+
if let Some(response) = response_cache.lock().unwrap().get(&url).cloned() {
240+
for (name, value) in &response.headers {
241+
builder = builder.header(name, value);
242+
}
243+
244+
return Ok(builder.status(response.status).body(response.body.into())?);
245+
}
246+
}
247+
248+
let headers = response.headers().clone();
249+
let body = response.bytes().await?.to_vec();
250+
let response = CachedResponse {
251+
status,
252+
headers,
253+
body,
254+
};
255+
256+
response_cache
257+
.lock()
258+
.unwrap()
259+
.insert(url.clone(), response.clone());
260+
261+
for (name, value) in &response.headers {
262+
builder = builder.header(name, value);
263+
}
264+
265+
builder
266+
.status(response.status)
267+
.body(response.body.into())
268+
.map_err(Into::into)
269+
}

0 commit comments

Comments
 (0)