From f12862e0cf166ec04caffb5870b20cf4a4e294e5 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 16:50:10 -0300 Subject: [PATCH 01/27] refactor(core): allow custom protocol handler to resolve async --- .changes/custom-protocol-response-refactor.md | 5 + .changes/invoke-system-args.md | 5 + .changes/runtime-custom-protocol-async.md | 5 + .changes/wry-0.32.md | 5 + core/tauri-runtime-wry/Cargo.toml | 2 +- core/tauri-runtime-wry/src/lib.rs | 24 +- core/tauri-runtime/src/window.rs | 26 +- core/tauri/src/app.rs | 8 +- core/tauri/src/asset_protocol.rs | 2 +- core/tauri/src/ipc/mod.rs | 27 +- core/tauri/src/ipc/protocol.rs | 313 +++++++++++------- core/tauri/src/manager.rs | 37 ++- core/tauri/src/menu/icon.rs | 2 +- core/tauri/src/scope/ipc.rs | 12 +- core/tauri/src/test/mod.rs | 29 +- core/tauri/src/window.rs | 95 ++---- examples/streaming/main.rs | 15 +- 17 files changed, 353 insertions(+), 259 deletions(-) create mode 100644 .changes/custom-protocol-response-refactor.md create mode 100644 .changes/invoke-system-args.md create mode 100644 .changes/runtime-custom-protocol-async.md create mode 100644 .changes/wry-0.32.md diff --git a/.changes/custom-protocol-response-refactor.md b/.changes/custom-protocol-response-refactor.md new file mode 100644 index 000000000000..2c7dcfed06f9 --- /dev/null +++ b/.changes/custom-protocol-response-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:breaking +--- + +Changed `Builder::register_uri_scheme_protocol` to take a second parameter to resolve the request instead of returning a response object. diff --git a/.changes/invoke-system-args.md b/.changes/invoke-system-args.md new file mode 100644 index 000000000000..b104100078d2 --- /dev/null +++ b/.changes/invoke-system-args.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:breaking +--- + +Changed `Builder::invoke_system` to take references instead of owned values. diff --git a/.changes/runtime-custom-protocol-async.md b/.changes/runtime-custom-protocol-async.md new file mode 100644 index 000000000000..76967cca0c2c --- /dev/null +++ b/.changes/runtime-custom-protocol-async.md @@ -0,0 +1,5 @@ +--- +"tauri-runtime": patch:enhance +--- + +Changed custom protocol closure type to enable asynchronous usage. diff --git a/.changes/wry-0.32.md b/.changes/wry-0.32.md new file mode 100644 index 000000000000..a9bfc8f0a2e1 --- /dev/null +++ b/.changes/wry-0.32.md @@ -0,0 +1,5 @@ +--- +"tauri-runtime-wry": patch:enhance +--- + +Update wry to 0.32 to include asynchronous custom protocol support. diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 3d17bf3a2b99..5a20ec132c74 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -16,7 +16,7 @@ rust-version = { workspace = true } features = [ "dox" ] [dependencies] -wry = { version = "0.31", default-features = false, features = [ "file-drop", "protocol" ] } +wry = { git = "https://github.com/tauri-apps/wry", branch = "refactor/custom-protocol-response", default-features = false, features = [ "file-drop", "protocol" ] } tauri-runtime = { version = "1.0.0-alpha.0", path = "../tauri-runtime" } tauri-utils = { version = "2.0.0-alpha.7", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 2e9752c9e68e..902f846dfc84 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -261,14 +261,15 @@ impl fmt::Debug for Context { struct HttpRequestWrapper(HttpRequest); -impl From<&WryRequest>> for HttpRequestWrapper { - fn from(req: &WryRequest>) -> Self { +impl From>> for HttpRequestWrapper { + fn from(req: WryRequest>) -> Self { + let (parts, body) = req.into_parts(); let parts = RequestParts { - uri: req.uri().to_string(), - method: req.method().clone(), - headers: req.headers().clone(), + uri: parts.uri.to_string(), + method: parts.method, + headers: parts.headers, }; - Self(HttpRequest::new_internal(parts, req.body().clone())) + Self(HttpRequest::new_internal(parts, body)) } } @@ -2701,11 +2702,14 @@ fn create_webview( } for (scheme, protocol) in uri_scheme_protocols { - webview_builder = webview_builder.with_custom_protocol(scheme, move |wry_request| { - protocol(&HttpRequestWrapper::from(wry_request).0) - .map(|tauri_response| HttpResponseWrapper::from(tauri_response).0) + webview_builder = + webview_builder.with_custom_protocol(scheme, move |wry_request: WryRequest>, api| { + protocol( + HttpRequestWrapper::from(wry_request).0, + Box::new(move |response| api.respond(HttpResponseWrapper::from(response).0)), + ) .map_err(|_| wry::Error::InitScriptError) - }); + }); } for script in webview_attributes.initialization_scripts { diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 5f06b6219eef..be55b1ae9dd6 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -24,10 +24,15 @@ use std::{ use self::dpi::PhysicalPosition; -type UriSchemeProtocol = - dyn Fn(&HttpRequest) -> Result> + Send + Sync + 'static; +type UriSchemeProtocol = dyn Fn( + HttpRequest, + Box, + ) -> Result<(), Box> + + Send + + Sync + + 'static; -type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; +type WebResourceRequestHandler = dyn Fn(HttpRequest, &mut HttpResponse) + Send + Sync; type NavigationHandler = dyn Fn(&Url) -> bool + Send; @@ -306,16 +311,23 @@ impl> PendingWindow { pub fn register_uri_scheme_protocol< N: Into, - H: Fn(&HttpRequest) -> Result> + Send + Sync + 'static, + H: Fn( + HttpRequest, + Box, + ) -> Result<(), Box> + + Send + + Sync + + 'static, >( &mut self, uri_scheme: N, protocol: H, ) { let uri_scheme = uri_scheme.into(); - self - .uri_scheme_protocols - .insert(uri_scheme, Box::new(move |data| (protocol)(data))); + self.uri_scheme_protocols.insert( + uri_scheme, + Box::new(move |data, responder| (protocol)(data, responder)), + ); } #[cfg(target_os = "android")] diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index e6dc32998911..c87b879f9a76 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1097,7 +1097,7 @@ impl Builder { #[must_use] pub fn invoke_system(mut self, initialization_script: String, responder: F) -> Self where - F: Fn(Window, String, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static, + F: Fn(&Window, &str, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static, { self.invoke_initialization_script = initialization_script; self.invoke_responder.replace(Arc::new(responder)); @@ -1352,7 +1352,11 @@ impl Builder { #[must_use] pub fn register_uri_scheme_protocol< N: Into, - H: Fn(&AppHandle, &HttpRequest) -> Result> + H: Fn( + &AppHandle, + HttpRequest, + Box, + ) -> Result<(), Box> + Send + Sync + 'static, diff --git a/core/tauri/src/asset_protocol.rs b/core/tauri/src/asset_protocol.rs index ec961a9a1169..73f31966eb23 100644 --- a/core/tauri/src/asset_protocol.rs +++ b/core/tauri/src/asset_protocol.rs @@ -17,7 +17,7 @@ use url::Position; use url::Url; pub fn asset_protocol_handler( - request: &Request, + request: Request, scope: FsScope, window_origin: String, ) -> Result> { diff --git a/core/tauri/src/ipc/mod.rs b/core/tauri/src/ipc/mod.rs index 66579fbbef7e..81e25515de6b 100644 --- a/core/tauri/src/ipc/mod.rs +++ b/core/tauri/src/ipc/mod.rs @@ -6,7 +6,7 @@ //! //! This module includes utilities to send messages to the JS layer of the webview. -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use futures_util::Future; use http::HeaderMap; @@ -31,9 +31,10 @@ pub type InvokeHandler = dyn Fn(Invoke) -> bool + Send + Sync + 'static; /// A closure that is responsible for respond a JS message. pub type InvokeResponder = - dyn Fn(Window, String, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static; -type OwnedInvokeResponder = - dyn Fn(Window, String, InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static; + dyn Fn(&Window, &str, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static; +/// Similar to [`InvokeResponder`] but taking owned arguments. +pub type OwnedInvokeResponder = + dyn FnOnce(Window, String, InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static; /// Possible values of an IPC payload. #[derive(Debug, Clone)] @@ -225,7 +226,7 @@ impl From for InvokeResponse { #[default_runtime(crate::Wry, wry)] pub struct InvokeResolver { window: Window, - responder: Arc>, + responder: Arc>>>>, cmd: String, pub(crate) callback: CallbackFn, pub(crate) error: CallbackFn, @@ -246,7 +247,7 @@ impl Clone for InvokeResolver { impl InvokeResolver { pub(crate) fn new( window: Window, - responder: Arc>, + responder: Arc>>>>, cmd: String, callback: CallbackFn, error: CallbackFn, @@ -348,7 +349,7 @@ impl InvokeResolver { /// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value. pub async fn return_task( window: Window, - responder: Arc>, + responder: Arc>>>>, task: F, cmd: String, success_callback: CallbackFn, @@ -370,7 +371,7 @@ impl InvokeResolver { pub(crate) fn return_closure Result>( window: Window, - responder: Arc>, + responder: Arc>>>>, f: F, cmd: String, success_callback: CallbackFn, @@ -388,13 +389,19 @@ impl InvokeResolver { pub(crate) fn return_result( window: Window, - responder: Arc>, + responder: Arc>>>>, response: InvokeResponse, cmd: String, success_callback: CallbackFn, error_callback: CallbackFn, ) { - (responder)(window, cmd, response, success_callback, error_callback); + (responder.lock().unwrap().take().expect("resolver consumed"))( + window, + cmd, + response, + success_callback, + error_callback, + ); } } diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index e2a0330d1751..ae9bd952c0a1 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -27,34 +27,65 @@ pub fn message_handler( } pub fn get(manager: WindowManager, label: String) -> UriSchemeProtocolHandler { - Box::new(move |request| { - let mut response = match *request.method() { + Box::new(move |request, responder| { + let manager = manager.clone(); + let label = label.clone(); + + let respond = move |mut response: tauri_runtime::http::Response| { + response + .headers_mut() + .insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); + responder(response); + }; + + match *request.method() { Method::POST => { - let (mut response, content_type) = match handle_ipc_request(request, &manager, &label) { - Ok(data) => match data { - InvokeResponse::Ok(InvokeBody::Json(v)) => ( - HttpResponse::new(serde_json::to_vec(&v)?.into()), - mime::APPLICATION_JSON, - ), - InvokeResponse::Ok(InvokeBody::Raw(v)) => { - (HttpResponse::new(v.into()), mime::APPLICATION_OCTET_STREAM) + if let Some(window) = manager.get_window(&label) { + match parse_invoke_request(&manager, request) { + Ok(request) => { + window.on_message( + request, + Box::new(move |_window, _cmd, response, _callback, _error| { + let (mut response, mime_type) = match response { + InvokeResponse::Ok(InvokeBody::Json(v)) => ( + HttpResponse::new(serde_json::to_vec(&v).unwrap().into()), + mime::APPLICATION_JSON, + ), + InvokeResponse::Ok(InvokeBody::Raw(v)) => { + (HttpResponse::new(v.into()), mime::APPLICATION_OCTET_STREAM) + } + InvokeResponse::Err(e) => { + let mut response = + HttpResponse::new(serde_json::to_vec(&e.0).unwrap().into()); + response.set_status(StatusCode::BAD_REQUEST); + (response, mime::TEXT_PLAIN) + } + }; + + response.set_mimetype(Some(mime_type.essence_str().into())); + + respond(response); + }), + ); } - InvokeResponse::Err(e) => { - let mut response = HttpResponse::new(serde_json::to_vec(&e.0)?.into()); + Err(e) => { + let mut response = HttpResponse::new(e.as_bytes().to_vec().into()); response.set_status(StatusCode::BAD_REQUEST); - (response, mime::TEXT_PLAIN) + response.set_mimetype(Some(mime::TEXT_PLAIN.essence_str().into())); + respond(response); } - }, - Err(e) => { - let mut response = HttpResponse::new(e.as_bytes().to_vec().into()); - response.set_status(StatusCode::BAD_REQUEST); - (response, mime::TEXT_PLAIN) } - }; - - response.set_mimetype(Some(content_type.essence_str().into())); - - response + } else { + let mut response = HttpResponse::new( + "failed to acquire window reference" + .as_bytes() + .to_vec() + .into(), + ); + response.set_status(StatusCode::BAD_REQUEST); + response.set_mimetype(Some(mime::TEXT_PLAIN.essence_str().into())); + respond(response); + } } Method::OPTIONS => { @@ -63,7 +94,7 @@ pub fn get(manager: WindowManager, label: String) -> UriSchemePro ACCESS_CONTROL_ALLOW_HEADERS, HeaderValue::from_static("Content-Type, Tauri-Callback, Tauri-Error, Tauri-Channel-Id"), ); - r + respond(r); } _ => { @@ -75,15 +106,11 @@ pub fn get(manager: WindowManager, label: String) -> UriSchemePro ); r.set_status(StatusCode::METHOD_NOT_ALLOWED); r.set_mimetype(Some(mime::TEXT_PLAIN.essence_str().into())); - r + respond(r); } - }; - - response - .headers_mut() - .insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); + } - Ok(response) + Ok(()) }) } @@ -166,13 +193,66 @@ fn handle_ipc_message(message: String, manager: &WindowManager, l .unwrap_or_else(|| serde_json::from_str::(&message).map_err(Into::into)) { Ok(message) => { - let _ = window.on_message(InvokeRequest { - cmd: message.cmd, - callback: message.callback, - error: message.error, - body: message.payload.into(), - headers: message.options.map(|o| o.headers.0).unwrap_or_default(), - }); + let _ = window.on_message( + InvokeRequest { + cmd: message.cmd, + callback: message.callback, + error: message.error, + body: message.payload.into(), + headers: message.options.map(|o| o.headers.0).unwrap_or_default(), + }, + Box::new(move |window, cmd, response, callback, error| { + use crate::ipc::{ + format_callback::{ + format as format_callback, format_result as format_callback_result, + }, + Channel, + }; + use serde_json::Value as JsonValue; + + // the channel data command is the only command that uses a custom protocol on Linux + if window.manager.invoke_responder().is_none() + && cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND + { + fn responder_eval( + window: &crate::Window, + js: crate::api::Result, + error: CallbackFn, + ) { + let eval_js = match js { + Ok(js) => js, + Err(e) => format_callback(error, &e.to_string()) + .expect("unable to serialize response error string to json"), + }; + + let _ = window.eval(&eval_js); + } + + match &response { + InvokeResponse::Ok(InvokeBody::Json(v)) => { + if matches!(v, JsonValue::Object(_) | JsonValue::Array(_)) { + let _ = Channel::from_ipc(window.clone(), callback).send(v); + } else { + responder_eval( + &window, + format_callback_result(Result::<_, ()>::Ok(v), callback, error), + error, + ) + } + } + InvokeResponse::Ok(InvokeBody::Raw(v)) => { + let _ = + Channel::from_ipc(window.clone(), callback).send(InvokeBody::Raw(v.clone())); + } + InvokeResponse::Err(e) => responder_eval( + &window, + format_callback_result(Result::<(), _>::Err(&e.0), callback, error), + error, + ), + } + } + }), + ); } Err(e) => { let _ = window.eval(&format!( @@ -184,90 +264,83 @@ fn handle_ipc_message(message: String, manager: &WindowManager, l } } -fn handle_ipc_request( - request: &HttpRequest, - manager: &WindowManager, - label: &str, -) -> std::result::Result { - if let Some(window) = manager.get_window(label) { - // TODO: consume instead - #[allow(unused_mut)] - let mut body = request.body().clone(); - - let cmd = request - .uri() - .strip_prefix("ipc://localhost/") - .map(|c| c.to_string()) - // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows - // where `$P` is not `localhost/*` - // in this case the IPC call is considered invalid - .unwrap_or_else(|| "".to_string()); - let cmd = percent_encoding::percent_decode(cmd.as_bytes()) - .decode_utf8_lossy() - .to_string(); - - // the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it - #[cfg(all(feature = "isolation", ipc_custom_protocol))] - if let crate::Pattern::Isolation { crypto_keys, .. } = manager.pattern() { - body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body) - .and_then(|raw| crypto_keys.decrypt(raw)) - .map_err(|e| e.to_string())?; - } +fn parse_invoke_request( + #[allow(unused_variables)] manager: &WindowManager, + request: HttpRequest, +) -> std::result::Result { + #[allow(unused_mut)] + let (parts, mut body) = request.into_parts(); - let callback = CallbackFn( - request - .headers() - .get(TAURI_CALLBACK_HEADER_NAME) - .ok_or("missing Tauri-Callback header")? - .to_str() - .map_err(|_| "Tauri callback header value must be a string")? - .parse() - .map_err(|_| "Tauri callback header value must be a numeric string")?, - ); - let error = CallbackFn( - request - .headers() - .get(TAURI_ERROR_HEADER_NAME) - .ok_or("missing Tauri-Error header")? - .to_str() - .map_err(|_| "Tauri error header value must be a string")? - .parse() - .map_err(|_| "Tauri error header value must be a numeric string")?, - ); - - let content_type = request - .headers() - .get(reqwest::header::CONTENT_TYPE) - .and_then(|h| h.to_str().ok()) - .map(|mime| mime.parse()) - .unwrap_or(Ok(mime::APPLICATION_OCTET_STREAM)) - .map_err(|_| "unknown content type")?; - let body = if content_type == mime::APPLICATION_OCTET_STREAM { - body.into() - } else if content_type == mime::APPLICATION_JSON { - if cfg!(ipc_custom_protocol) { - serde_json::from_slice::(&body) - .map_err(|e| e.to_string())? - .into() - } else { - // the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it - serde_json::Value::Object(Default::default()).into() - } - } else { - return Err(format!("content type {content_type} is not implemented")); - }; + let cmd = parts + .uri + .strip_prefix("ipc://localhost/") + .map(|c| c.to_string()) + // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows + // where `$P` is not `localhost/*` + // in this case the IPC call is considered invalid + .unwrap_or_else(|| "".to_string()); + let cmd = percent_encoding::percent_decode(cmd.as_bytes()) + .decode_utf8_lossy() + .to_string(); - let payload = InvokeRequest { - cmd, - callback, - error, - body, - headers: request.headers().clone(), - }; + // the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it + #[cfg(all(feature = "isolation", ipc_custom_protocol))] + if let crate::Pattern::Isolation { crypto_keys, .. } = manager.pattern() { + body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body) + .and_then(|raw| crypto_keys.decrypt(raw)) + .map_err(|e| e.to_string())?; + } - let rx = window.on_message(payload); - Ok(rx.recv().unwrap()) + let callback = CallbackFn( + parts + .headers + .get(TAURI_CALLBACK_HEADER_NAME) + .ok_or("missing Tauri-Callback header")? + .to_str() + .map_err(|_| "Tauri callback header value must be a string")? + .parse() + .map_err(|_| "Tauri callback header value must be a numeric string")?, + ); + let error = CallbackFn( + parts + .headers + .get(TAURI_ERROR_HEADER_NAME) + .ok_or("missing Tauri-Error header")? + .to_str() + .map_err(|_| "Tauri error header value must be a string")? + .parse() + .map_err(|_| "Tauri error header value must be a numeric string")?, + ); + + let content_type = parts + .headers + .get(reqwest::header::CONTENT_TYPE) + .and_then(|h| h.to_str().ok()) + .map(|mime| mime.parse()) + .unwrap_or(Ok(mime::APPLICATION_OCTET_STREAM)) + .map_err(|_| "unknown content type")?; + let body = if content_type == mime::APPLICATION_OCTET_STREAM { + body.into() + } else if content_type == mime::APPLICATION_JSON { + if cfg!(ipc_custom_protocol) { + serde_json::from_slice::(&body) + .map_err(|e| e.to_string())? + .into() + } else { + // the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it + serde_json::Value::Object(Default::default()).into() + } } else { - Err("window not found".into()) - } + return Err(format!("content type {content_type} is not implemented")); + }; + + let payload = InvokeRequest { + cmd, + callback, + error, + body, + headers: parts.headers, + }; + + Ok(payload) } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 90886ec3dc46..f23ff41ef29f 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -310,7 +310,11 @@ pub struct CustomProtocol { /// Handler for protocol #[allow(clippy::type_complexity)] pub protocol: Box< - dyn Fn(&AppHandle, &HttpRequest) -> Result> + dyn Fn( + &AppHandle, + HttpRequest, + Box, + ) -> Result<(), Box> + Send + Sync, >, @@ -604,8 +608,8 @@ impl WindowManager { registered_scheme_protocols.push(uri_scheme.clone()); let protocol = protocol.clone(); let app_handle = Mutex::new(app_handle.clone()); - pending.register_uri_scheme_protocol(uri_scheme.clone(), move |p| { - (protocol.protocol)(&app_handle.lock().unwrap(), p) + pending.register_uri_scheme_protocol(uri_scheme.clone(), move |p, responder| { + (protocol.protocol)(&app_handle.lock().unwrap(), p, responder) }); } @@ -646,12 +650,16 @@ impl WindowManager { #[cfg(feature = "protocol-asset")] if !registered_scheme_protocols.contains(&"asset".into()) { let asset_scope = self.state().get::().asset_protocol.clone(); - pending.register_uri_scheme_protocol("asset", move |request| { - crate::asset_protocol::asset_protocol_handler( + pending.register_uri_scheme_protocol("asset", move |request, responder| { + let response = crate::asset_protocol::asset_protocol_handler( request, asset_scope.clone(), window_origin.clone(), - ) + )?; + + responder(response); + + Ok(()) }); } @@ -668,8 +676,8 @@ impl WindowManager { let url_base = format!("{schema_}://localhost"); let aes_gcm_key = *crypto_keys.aes_gcm().raw(); - pending.register_uri_scheme_protocol(schema, move |request| { - match request_to_path(request, &url_base).as_str() { + pending.register_uri_scheme_protocol(schema, move |request, responder| { + let response = match request_to_path(&request, &url_base).as_str() { "index.html" => match assets.get(&"index.html".into()) { Some(asset) => { let asset = String::from_utf8_lossy(asset.as_ref()); @@ -697,7 +705,11 @@ impl WindowManager { .status(404) .mimetype(mime::TEXT_PLAIN.as_ref()) .body(Vec::new()), - } + }?; + + (responder)(response); + + Ok(()) }); } @@ -815,7 +827,7 @@ impl WindowManager { #[cfg(all(dev, mobile))] let response_cache = Arc::new(Mutex::new(HashMap::new())); - Box::new(move |request| { + Box::new(move |request, responder| { // use the entire URI as we are going to proxy the request let path = if PROXY_DEV_SERVER { request.uri() @@ -908,7 +920,10 @@ impl WindowManager { let body = html.replacen(tauri_utils::html::CSP_TOKEN, &response_csp, 1); *response.body_mut() = body.as_bytes().to_vec().into(); } - Ok(response) + + responder(response); + + Ok(()) }) } diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index 699a0ea9f49e..b398499e10ce 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -207,7 +207,7 @@ impl IconMenuItem { /// - **Windows / Linux**: Unsupported. pub fn set_native_icon(&mut self, _icon: Option) -> crate::Result<()> { #[cfg(target_os = "macos")] - return run_main_thread!(self, |mut self_: Self| self_ + return run_main_thread!(self, |self_: Self| self_ .inner .set_native_icon(_icon.map(Into::into))); #[allow(unreachable_code)] diff --git a/core/tauri/src/scope/ipc.rs b/core/tauri/src/scope/ipc.rs index b0eea0bbd172..363c257cdb8c 100644 --- a/core/tauri/src/scope/ipc.rs +++ b/core/tauri/src/scope/ipc.rs @@ -231,7 +231,7 @@ mod tests { assert_ipc_response( &window, path_is_absolute_request(), - Err(&crate::window::ipc_scope_not_found_error_message( + Err(crate::window::ipc_scope_not_found_error_message( "main", "https://tauri.app/", )), @@ -248,7 +248,7 @@ mod tests { assert_ipc_response( &window, path_is_absolute_request(), - Err(&crate::window::ipc_scope_window_error_message("main")), + Err(crate::window::ipc_scope_window_error_message("main")), ); } @@ -262,7 +262,7 @@ mod tests { assert_ipc_response( &window, path_is_absolute_request(), - Err(&crate::window::ipc_scope_domain_error_message( + Err(crate::window::ipc_scope_domain_error_message( "https://tauri.app/", )), ); @@ -286,7 +286,7 @@ mod tests { assert_ipc_response( &window, path_is_absolute_request(), - Err(&crate::window::ipc_scope_domain_error_message( + Err(crate::window::ipc_scope_domain_error_message( "https://blog.tauri.app/", )), ); @@ -299,7 +299,7 @@ mod tests { assert_ipc_response( &window, path_is_absolute_request(), - Err(&crate::window::ipc_scope_not_found_error_message( + Err(crate::window::ipc_scope_not_found_error_message( "test", "https://dev.tauri.app/", )), @@ -340,7 +340,7 @@ mod tests { assert_ipc_response( &window, plugin_test_request(), - Err(&format!("plugin {PLUGIN_NAME} not found")), + Err(format!("plugin {PLUGIN_NAME} not found")), ); } diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 7b0a20e4385d..30e94c3d170d 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -220,23 +220,30 @@ pub fn mock_app() -> App { /// } /// } /// ``` -pub fn assert_ipc_response( +pub fn assert_ipc_response( window: &Window, request: InvokeRequest, expected: Result, ) { - let rx = window.clone().on_message(request); - let response = rx.recv().unwrap(); + let (tx, rx) = std::sync::mpsc::channel(); + window.clone().on_message( + request, + Box::new(move |_window, _cmd, response, _callback, _error| { + assert_eq!( + match response { + InvokeResponse::Ok(b) => Ok(b.into_json()), + InvokeResponse::Err(e) => Err(e.0), + }, + expected + .map(|e| serde_json::to_value(e).unwrap()) + .map_err(|e| serde_json::to_value(e).unwrap()) + ); - assert_eq!( - match response { - InvokeResponse::Ok(b) => Ok(b.into_json()), - InvokeResponse::Err(e) => Err(e.0), - }, - expected - .map(|e| serde_json::to_value(e).unwrap()) - .map_err(|e| serde_json::to_value(e).unwrap()) + tx.send(()).unwrap(); + }), ); + + rx.recv().unwrap(); } #[cfg(test)] diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index bfb22f47c86e..e2a39315e34a 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -15,7 +15,8 @@ use crate::{ command::{CommandArg, CommandItem}, event::{Event, EventHandler}, ipc::{ - CallbackFn, Invoke, InvokeBody, InvokeError, InvokeMessage, InvokeResolver, InvokeResponse, + CallbackFn, Invoke, InvokeBody, InvokeError, InvokeMessage, InvokeResolver, + OwnedInvokeResponder, }, manager::WindowManager, runtime::{ @@ -54,16 +55,19 @@ use std::{ fmt, hash::{Hash, Hasher}, path::PathBuf, - sync::{ - mpsc::{sync_channel, Receiver}, - Arc, Mutex, - }, + sync::{Arc, Mutex}, }; -pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; +pub(crate) type WebResourceRequestHandler = dyn Fn(HttpRequest, &mut HttpResponse) + Send + Sync; pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send; -pub(crate) type UriSchemeProtocolHandler = - Box Result> + Send + Sync>; +pub(crate) type UriSchemeProtocolHandler = Box< + dyn Fn( + HttpRequest, + Box, + ) -> Result<(), Box> + + Send + + Sync, +>; #[derive(Clone, Serialize)] struct WindowCreatedEvent { @@ -291,7 +295,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// Ok(()) /// }); /// ``` - pub fn on_web_resource_request( + pub fn on_web_resource_request( mut self, f: F, ) -> Self { @@ -892,7 +896,7 @@ pub struct Window { /// The webview window created by the runtime. pub(crate) window: DetachedWindow, /// The manager to associate this webview window with. - manager: WindowManager, + pub(crate) manager: WindowManager, pub(crate) app_handle: AppHandle, js_event_listeners: Arc>>>, // The menu set for this window @@ -2067,7 +2071,7 @@ impl Window { } /// Handles this window receiving an [`InvokeRequest`]. - pub fn on_message(self, request: InvokeRequest) -> Receiver { + pub fn on_message(self, request: InvokeRequest, responder: Box>) { let manager = self.manager.clone(); let current_url = self.url(); let is_local = self.is_local_url(¤t_url); @@ -2090,75 +2094,20 @@ impl Window { } }; - let (tx, rx) = sync_channel(1); - let custom_responder = self.manager.invoke_responder(); let resolver = InvokeResolver::new( self.clone(), - Arc::new( + Arc::new(Mutex::new(Some(Box::new( #[allow(unused_variables)] move |window: Window, cmd, response, callback, error| { - if (cfg!(target_os = "macos") && window.url().scheme() == "https") - || !cfg!(ipc_custom_protocol) - { - use crate::ipc::{ - format_callback::{ - format as format_callback, format_result as format_callback_result, - }, - Channel, - }; - use serde_json::Value as JsonValue; - - // the channel data command is the only command that uses a custom protocol on Linux - if custom_responder.is_none() && cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND - { - fn responder_eval( - window: &Window, - js: crate::api::Result, - error: CallbackFn, - ) { - let eval_js = match js { - Ok(js) => js, - Err(e) => format_callback(error, &e.to_string()) - .expect("unable to serialize response error string to json"), - }; - - let _ = window.eval(&eval_js); - } - - match &response { - InvokeResponse::Ok(InvokeBody::Json(v)) => { - if matches!(v, JsonValue::Object(_) | JsonValue::Array(_)) { - let _ = Channel::from_ipc(window.clone(), callback).send(v); - } else { - responder_eval( - &window, - format_callback_result(Result::<_, ()>::Ok(v), callback, error), - error, - ) - } - } - InvokeResponse::Ok(InvokeBody::Raw(v)) => { - let _ = - Channel::from_ipc(window.clone(), callback).send(InvokeBody::Raw(v.clone())); - } - InvokeResponse::Err(e) => responder_eval( - &window, - format_callback_result(Result::<(), _>::Err(&e.0), callback, error), - error, - ), - } - } - } - if let Some(responder) = &custom_responder { - (responder)(window, cmd, &response, callback, error); + (responder)(&window, &cmd, &response, callback, error); } - let _ = tx.send(response); + responder(window, cmd, response, callback, error); }, - ), + )))), request.cmd.clone(), request.callback, request.error, @@ -2208,7 +2157,7 @@ impl Window { .unwrap_or(true)) { invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); - return rx; + return; } let command = invoke.message.command.clone(); @@ -2252,7 +2201,7 @@ impl Window { }, ) { resolver.reject(e.to_string()); - return rx; + return; } } } @@ -2269,8 +2218,6 @@ impl Window { } } } - - rx } /// Evaluates JavaScript on this window. diff --git a/examples/streaming/main.rs b/examples/streaming/main.rs index 087aa4566bbc..eb3ce7b06081 100644 --- a/examples/streaming/main.rs +++ b/examples/streaming/main.rs @@ -41,7 +41,7 @@ fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![video_uri]) - .register_uri_scheme_protocol("stream", move |_app, request| { + .register_uri_scheme_protocol("stream", move |_app, request, responder| { // get the file path let path = request.uri().strip_prefix("stream://localhost/").unwrap(); let path = percent_encoding::percent_decode(path.as_bytes()) @@ -50,7 +50,8 @@ fn main() { if path != "test_video.mp4" { // return error 404 if it's not our video - return ResponseBuilder::new().status(404).body(Vec::new()); + responder(ResponseBuilder::new().status(404).body(Vec::new())?); + return Ok(()); } let mut file = std::fs::File::open(&path)?; @@ -83,7 +84,8 @@ fn main() { .map(|r| (r.start, r.start + r.length - 1)) .collect::>() } else { - return not_satisfiable(); + responder(not_satisfiable()?); + return Ok(()); }; /// The Maximum bytes we send in one range @@ -97,7 +99,8 @@ fn main() { // this should be already taken care of by HttpRange::parse // but checking here again for extra assurance if start >= len || end >= len || end < start { - return not_satisfiable(); + responder(not_satisfiable()?); + return Ok(()); } // adjust end byte for MAX_LEN @@ -178,7 +181,9 @@ fn main() { resp.body(buf) }; - response + responder(response?); + + Ok(()) }) .run(tauri::generate_context!( "../../examples/streaming/tauri.conf.json" From c9389713b063506e7f28bf531978826e6908836a Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 18:03:19 -0300 Subject: [PATCH 02/27] sync channel --- core/tauri/src/test/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 30e94c3d170d..ff163eb2a3fc 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -225,7 +225,7 @@ pub fn assert_ipc_response( request: InvokeRequest, expected: Result, ) { - let (tx, rx) = std::sync::mpsc::channel(); + let (tx, rx) = std::sync::mpsc::sync_channel(1); window.clone().on_message( request, Box::new(move |_window, _cmd, response, _callback, _error| { From b6e5f8e5c2ca2d13518cdda34615fcdbaaf50a1b Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 18:06:29 -0300 Subject: [PATCH 03/27] lint --- core/tauri/src/ipc/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index ae9bd952c0a1..e46a64725e58 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -193,7 +193,7 @@ fn handle_ipc_message(message: String, manager: &WindowManager, l .unwrap_or_else(|| serde_json::from_str::(&message).map_err(Into::into)) { Ok(message) => { - let _ = window.on_message( + window.on_message( InvokeRequest { cmd: message.cmd, callback: message.callback, From e01901d1eb7a90c1a6893885cf5f7ab6ee268833 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 19:20:48 -0300 Subject: [PATCH 04/27] remove own http types --- .changes/http-drop-export.md | 5 + .changes/http-types-refactor.md | 7 + core/tauri-runtime-wry/Cargo.toml | 1 + core/tauri-runtime-wry/src/lib.rs | 47 +--- core/tauri-runtime/Cargo.toml | 1 - core/tauri-runtime/src/http/mod.rs | 20 -- core/tauri-runtime/src/http/request.rs | 132 ---------- core/tauri-runtime/src/http/response.rs | 309 ------------------------ core/tauri-runtime/src/lib.rs | 6 +- core/tauri-runtime/src/window.rs | 14 +- core/tauri/Cargo.toml | 1 + core/tauri/src/app.rs | 7 +- core/tauri/src/asset_protocol.rs | 7 +- core/tauri/src/ipc/protocol.rs | 64 ++--- core/tauri/src/lib.rs | 3 - core/tauri/src/manager.rs | 35 +-- core/tauri/src/window.rs | 20 +- examples/streaming/main.rs | 15 +- 18 files changed, 107 insertions(+), 587 deletions(-) create mode 100644 .changes/http-drop-export.md create mode 100644 .changes/http-types-refactor.md delete mode 100644 core/tauri-runtime/src/http/mod.rs delete mode 100644 core/tauri-runtime/src/http/request.rs delete mode 100644 core/tauri-runtime/src/http/response.rs diff --git a/.changes/http-drop-export.md b/.changes/http-drop-export.md new file mode 100644 index 000000000000..c4cf7ea12e46 --- /dev/null +++ b/.changes/http-drop-export.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:breaking +--- + +No longer re-export the `http` crate. diff --git a/.changes/http-types-refactor.md b/.changes/http-types-refactor.md new file mode 100644 index 000000000000..d44158cf1f16 --- /dev/null +++ b/.changes/http-types-refactor.md @@ -0,0 +1,7 @@ +--- +"tauri": patch:breaking +"tauri-runtime": patch:breaking +"tauri-runtime-wry": patch:breaking +--- + +`tauri-runtime` no longer implements its own HTTP types and relies on the `http` crate instead. diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 5a20ec132c74..8f47b6f35f4e 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -22,6 +22,7 @@ tauri-utils = { version = "2.0.0-alpha.7", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } rand = "0.8" raw-window-handle = "0.5" +http = "0.2" [target."cfg(windows)".dependencies] webview2-com = "0.25" diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 902f846dfc84..691f3ad17aed 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -13,7 +13,6 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle}; use tauri_runtime::{ - http::{header::CONTENT_TYPE, Request as HttpRequest, RequestParts, Response as HttpResponse}, monitor::Monitor, webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase}, window::{ @@ -61,7 +60,6 @@ use wry::{ UserAttentionType as WryUserAttentionType, }, }, - http::{Request as WryRequest, Response as WryResponse}, webview::{FileDropEvent as WryFileDropEvent, Url, WebContext, WebView, WebViewBuilder}, }; @@ -85,7 +83,6 @@ pub use wry::application::platform::macos::{ }; use std::{ - borrow::Cow, cell::RefCell, collections::{ hash_map::Entry::{Occupied, Vacant}, @@ -259,40 +256,6 @@ impl fmt::Debug for Context { } } -struct HttpRequestWrapper(HttpRequest); - -impl From>> for HttpRequestWrapper { - fn from(req: WryRequest>) -> Self { - let (parts, body) = req.into_parts(); - let parts = RequestParts { - uri: parts.uri.to_string(), - method: parts.method, - headers: parts.headers, - }; - Self(HttpRequest::new_internal(parts, body)) - } -} - -// response -struct HttpResponseWrapper(WryResponse>); -impl From for HttpResponseWrapper { - fn from(response: HttpResponse) -> Self { - let (parts, body) = response.into_parts(); - let mut res_builder = WryResponse::builder() - .status(parts.status) - .version(parts.version); - if let Some(mime) = parts.mimetype { - res_builder = res_builder.header(CONTENT_TYPE, mime); - } - for (name, val) in parts.headers.iter() { - res_builder = res_builder.header(name, val); - } - - let res = res_builder.body(body).unwrap(); - Self(res) - } -} - pub struct DeviceEventFilterWrapper(pub WryDeviceEventFilter); impl From for DeviceEventFilterWrapper { @@ -2702,14 +2665,10 @@ fn create_webview( } for (scheme, protocol) in uri_scheme_protocols { - webview_builder = - webview_builder.with_custom_protocol(scheme, move |wry_request: WryRequest>, api| { - protocol( - HttpRequestWrapper::from(wry_request).0, - Box::new(move |response| api.respond(HttpResponseWrapper::from(response).0)), - ) + webview_builder = webview_builder.with_custom_protocol(scheme, move |request, api| { + protocol(request, Box::new(move |response| api.respond(response))) .map_err(|_| wry::Error::InitScriptError) - }); + }); } for script in webview_attributes.initialization_scripts { diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 0185ca771cae..aa95edebac77 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -29,7 +29,6 @@ thiserror = "1.0" tauri-utils = { version = "2.0.0-alpha.7", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } http = "0.2.4" -http-range = "0.1.4" raw-window-handle = "0.5" rand = "0.8" url = { version = "2" } diff --git a/core/tauri-runtime/src/http/mod.rs b/core/tauri-runtime/src/http/mod.rs deleted file mode 100644 index 7ce36f4fadee..000000000000 --- a/core/tauri-runtime/src/http/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -// custom wry types -mod request; -mod response; - -pub use self::{ - request::{Request, RequestParts}, - response::{Builder as ResponseBuilder, Response, ResponseParts}, -}; - -pub use tauri_utils::mime_type::MimeType; - -// re-expose default http types -pub use http::{header, method, status, uri::InvalidUri, version, Uri}; - -// re-export httprange helper as it can be useful and we need it locally -pub use http_range::HttpRange; diff --git a/core/tauri-runtime/src/http/request.rs b/core/tauri-runtime/src/http/request.rs deleted file mode 100644 index 42729de06baf..000000000000 --- a/core/tauri-runtime/src/http/request.rs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::fmt; - -use super::{ - header::{HeaderMap, HeaderValue}, - method::Method, -}; - -/// Represents an HTTP request from the WebView. -/// -/// An HTTP request consists of a head and a potentially optional body. -/// -/// ## Platform-specific -/// -/// - **Linux:** Headers are not exposed. -pub struct Request { - head: RequestParts, - body: Vec, -} - -/// Component parts of an HTTP `Request` -/// -/// The HTTP request head consists of a method, uri, and a set of -/// header fields. -#[derive(Clone)] -pub struct RequestParts { - /// The request's method - pub method: Method, - - /// The request's URI - pub uri: String, - - /// The request's headers - pub headers: HeaderMap, -} - -impl Request { - /// Creates a new blank `Request` with the body - #[inline] - pub fn new(body: Vec) -> Request { - Request { - head: RequestParts::new(), - body, - } - } - - /// Creates a new `Request` with the given head and body. - /// - /// # Stability - /// - /// This API is used internally. It may have breaking changes in the future. - #[inline] - #[doc(hidden)] - pub fn new_internal(head: RequestParts, body: Vec) -> Request { - Request { head, body } - } - - /// Returns a reference to the associated HTTP method. - #[inline] - pub fn method(&self) -> &Method { - &self.head.method - } - - /// Returns a reference to the associated URI. - #[inline] - pub fn uri(&self) -> &str { - &self.head.uri - } - - /// Returns a reference to the associated header field map. - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - /// Returns a reference to the associated HTTP body. - #[inline] - pub fn body(&self) -> &Vec { - &self.body - } - - /// Consumes the request returning the head and body RequestParts. - /// - /// # Stability - /// - /// This API is used internally. It may have breaking changes in the future. - #[inline] - pub fn into_parts(self) -> (RequestParts, Vec) { - (self.head, self.body) - } -} - -impl Default for Request { - fn default() -> Request { - Request::new(Vec::new()) - } -} - -impl fmt::Debug for Request { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Request") - .field("method", self.method()) - .field("uri", &self.uri()) - .field("headers", self.headers()) - .field("body", self.body()) - .finish() - } -} - -impl RequestParts { - /// Creates a new default instance of `RequestParts` - fn new() -> RequestParts { - RequestParts { - method: Method::default(), - uri: "".into(), - headers: HeaderMap::default(), - } - } -} - -impl fmt::Debug for RequestParts { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Parts") - .field("method", &self.method) - .field("uri", &self.uri) - .field("headers", &self.headers) - .finish() - } -} diff --git a/core/tauri-runtime/src/http/response.rs b/core/tauri-runtime/src/http/response.rs deleted file mode 100644 index a004d4dc0d5f..000000000000 --- a/core/tauri-runtime/src/http/response.rs +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::{ - header::{HeaderMap, HeaderName, HeaderValue}, - status::StatusCode, - version::Version, -}; -use std::{borrow::Cow, fmt}; - -type Result = core::result::Result>; - -/// Represents an HTTP response -/// -/// An HTTP response consists of a head and a potentially body. -/// -/// ## Platform-specific -/// -/// - **Linux:** Headers and status code cannot be changed. -/// -/// # Examples -/// -/// ``` -/// # use tauri_runtime::http::*; -/// -/// let response = ResponseBuilder::new() -/// .status(202) -/// .mimetype("text/html") -/// .body("hello!".as_bytes().to_vec()) -/// .unwrap(); -/// ``` -/// -pub struct Response { - head: ResponseParts, - body: Cow<'static, [u8]>, -} - -/// Component parts of an HTTP `Response` -/// -/// The HTTP response head consists of a status, version, and a set of -/// header fields. -#[derive(Clone)] -pub struct ResponseParts { - /// The response's status. - pub status: StatusCode, - - /// The response's version. - pub version: Version, - - /// The response's headers. - pub headers: HeaderMap, - - /// The response's mimetype type. - pub mimetype: Option, -} - -/// An HTTP response builder -/// -/// This type can be used to construct an instance of `Response` through a -/// builder-like pattern. -#[derive(Debug)] -pub struct Builder { - inner: Result, -} - -impl Response { - /// Creates a new blank `Response` with the body - #[inline] - pub fn new(body: Cow<'static, [u8]>) -> Response { - Response { - head: ResponseParts::new(), - body, - } - } - - /// Consumes the response returning the head and body ResponseParts. - /// - /// # Stability - /// - /// This API is used internally. It may have breaking changes in the future. - #[inline] - #[doc(hidden)] - pub fn into_parts(self) -> (ResponseParts, Cow<'static, [u8]>) { - (self.head, self.body) - } - - /// Sets the status code. - #[inline] - pub fn set_status(&mut self, status: StatusCode) { - self.head.status = status; - } - - /// Returns the [`StatusCode`]. - #[inline] - pub fn status(&self) -> StatusCode { - self.head.status - } - - /// Sets the mimetype. - #[inline] - pub fn set_mimetype(&mut self, mimetype: Option) { - self.head.mimetype = mimetype; - } - - /// Returns a reference to the mime type. - #[inline] - pub fn mimetype(&self) -> Option<&String> { - self.head.mimetype.as_ref() - } - - /// Returns a reference to the associated version. - #[inline] - pub fn version(&self) -> Version { - self.head.version - } - - /// Returns a mutable reference to the associated header field map. - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head.headers - } - - /// Returns a reference to the associated header field map. - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - /// Returns a mutable reference to the associated HTTP body. - #[inline] - pub fn body_mut(&mut self) -> &mut Cow<'static, [u8]> { - &mut self.body - } - - /// Returns a reference to the associated HTTP body. - #[inline] - pub fn body(&self) -> &Cow<'static, [u8]> { - &self.body - } -} - -impl Default for Response { - #[inline] - fn default() -> Response { - Response::new(Default::default()) - } -} - -impl fmt::Debug for Response { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Response") - .field("status", &self.status()) - .field("version", &self.version()) - .field("headers", self.headers()) - .field("body", self.body()) - .finish() - } -} - -impl ResponseParts { - /// Creates a new default instance of `ResponseParts` - fn new() -> ResponseParts { - ResponseParts { - status: StatusCode::default(), - version: Version::default(), - headers: HeaderMap::default(), - mimetype: None, - } - } -} - -impl fmt::Debug for ResponseParts { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Parts") - .field("status", &self.status) - .field("version", &self.version) - .field("headers", &self.headers) - .finish() - } -} - -impl Builder { - /// Creates a new default instance of `Builder` to construct either a - /// `Head` or a `Response`. - /// - /// # Examples - /// - /// ``` - /// # use tauri_runtime::http::*; - /// - /// let response = ResponseBuilder::new() - /// .status(200) - /// .mimetype("text/html") - /// .body(Vec::new()) - /// .unwrap(); - /// ``` - #[inline] - pub fn new() -> Builder { - Builder { - inner: Ok(ResponseParts::new()), - } - } - - /// Set the HTTP mimetype for this response. - #[must_use] - pub fn mimetype(self, mimetype: &str) -> Self { - self.and_then(move |mut head| { - head.mimetype = Some(mimetype.to_string()); - Ok(head) - }) - } - - /// Set the HTTP status for this response. - #[must_use] - pub fn status(self, status: T) -> Self - where - StatusCode: TryFrom, - >::Error: Into, - { - self.and_then(move |mut head| { - head.status = TryFrom::try_from(status).map_err(Into::into)?; - Ok(head) - }) - } - - /// Set the HTTP version for this response. - /// - /// This function will configure the HTTP version of the `Response` that - /// will be returned from `Builder::build`. - /// - /// By default this is HTTP/1.1 - #[must_use] - pub fn version(self, version: Version) -> Self { - self.and_then(move |mut head| { - head.version = version; - Ok(head) - }) - } - - /// Appends a header to this response builder. - /// - /// This function will append the provided key/value as a header to the - /// internal `HeaderMap` being constructed. Essentially this is equivalent - /// to calling `HeaderMap::append`. - #[must_use] - pub fn header(self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - self.and_then(move |mut head| { - let name = >::try_from(key).map_err(Into::into)?; - let value = >::try_from(value).map_err(Into::into)?; - head.headers.append(name, value); - Ok(head) - }) - } - - /// "Consumes" this builder, using the provided `body` to return a - /// constructed `Response`. - /// - /// # Errors - /// - /// This function may return an error if any previously configured argument - /// failed to parse or get converted to the internal representation. For - /// example if an invalid `head` was specified via `header("Foo", - /// "Bar\r\n")` the error will be returned when this function is called - /// rather than when `header` was called. - /// - /// # Examples - /// - /// ``` - /// # use tauri_runtime::http::*; - /// - /// let response = ResponseBuilder::new() - /// .mimetype("text/html") - /// .body(Vec::new()) - /// .unwrap(); - /// ``` - pub fn body(self, body: impl Into>) -> Result { - self.inner.map(move |head| Response { - head, - body: body.into(), - }) - } - - // private - - fn and_then(self, func: F) -> Self - where - F: FnOnce(ResponseParts) -> Result, - { - Builder { - inner: self.inner.and_then(func), - } - } -} - -impl Default for Builder { - #[inline] - fn default() -> Builder { - Builder { - inner: Ok(ResponseParts::new()), - } - } -} diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index 25c743b8ae35..52158ccff121 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -19,7 +19,6 @@ use tauri_utils::Theme; use url::Url; use uuid::Uuid; -pub mod http; /// Types useful for interacting with a user's monitors. pub mod monitor; pub mod webview; @@ -32,11 +31,10 @@ use window::{ CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, }; -use crate::http::{ +use http::{ header::{InvalidHeaderName, InvalidHeaderValue}, method::InvalidMethod, status::InvalidStatusCode, - InvalidUri, }; /// Type of user attention requested on a window. @@ -101,8 +99,6 @@ pub enum Error { InvalidHeaderName(#[from] InvalidHeaderName), #[error("Invalid header value: {0}")] InvalidHeaderValue(#[from] InvalidHeaderValue), - #[error("Invalid uri: {0}")] - InvalidUri(#[from] InvalidUri), #[error("Invalid status code: {0}")] InvalidStatusCode(#[from] InvalidStatusCode), #[error("Invalid method: {0}")] diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index be55b1ae9dd6..eab21a826adc 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -5,16 +5,17 @@ //! A layer between raw [`Runtime`] webview windows and Tauri. use crate::{ - http::{Request as HttpRequest, Response as HttpResponse}, webview::{WebviewAttributes, WebviewIpcHandler}, Dispatch, Runtime, UserEvent, WindowBuilder, }; +use http::{Request as HttpRequest, Response as HttpResponse}; use serde::{Deserialize, Deserializer}; use tauri_utils::{config::WindowConfig, Theme}; use url::Url; use std::{ + borrow::Cow, collections::HashMap, hash::{Hash, Hasher}, marker::PhantomData, @@ -25,14 +26,15 @@ use std::{ use self::dpi::PhysicalPosition; type UriSchemeProtocol = dyn Fn( - HttpRequest, - Box, + HttpRequest>, + Box>) + Send + Sync>, ) -> Result<(), Box> + Send + Sync + 'static; -type WebResourceRequestHandler = dyn Fn(HttpRequest, &mut HttpResponse) + Send + Sync; +type WebResourceRequestHandler = + dyn Fn(HttpRequest>, &mut HttpResponse>) + Send + Sync; type NavigationHandler = dyn Fn(&Url) -> bool + Send; @@ -312,8 +314,8 @@ impl> PendingWindow { pub fn register_uri_scheme_protocol< N: Into, H: Fn( - HttpRequest, - Box, + HttpRequest>, + Box>) + Send + Sync>, ) -> Result<(), Box> + Send + Sync diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 537929c46db4..8e6e13b70bde 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -116,6 +116,7 @@ serde_json = "1.0" tauri = { path = ".", default-features = false, features = [ "wry" ] } tokio = { version = "1", features = [ "full" ] } cargo_toml = "0.15" +http-range = "0.1.4" [features] default = [ diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index c87b879f9a76..c2f96b2a56f1 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -11,7 +11,6 @@ use crate::{ manager::{Asset, CustomProtocol, WindowManager}, plugin::{Plugin, PluginStore}, runtime::{ - http::{Request as HttpRequest, Response as HttpResponse}, webview::WebviewAttributes, window::{PendingWindow, WindowEvent as RuntimeWindowEvent}, ExitRequestedEventAction, RunEvent as RuntimeRunEvent, @@ -33,6 +32,7 @@ use crate::menu::{Menu, MenuEvent}; use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId}; #[cfg(desktop)] use crate::window::WindowMenu; +use http::{Request as HttpRequest, Response as HttpResponse}; use raw_window_handle::HasRawDisplayHandle; use serde::Deserialize; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; @@ -49,6 +49,7 @@ use tauri_runtime::{ use tauri_utils::PackageInfo; use std::{ + borrow::Cow, collections::HashMap, fmt, sync::{mpsc::Sender, Arc, Weak}, @@ -1354,8 +1355,8 @@ impl Builder { N: Into, H: Fn( &AppHandle, - HttpRequest, - Box, + HttpRequest>, + Box>) + Send + Sync>, ) -> Result<(), Box> + Send + Sync diff --git a/core/tauri/src/asset_protocol.rs b/core/tauri/src/asset_protocol.rs index 73f31966eb23..5d5081e1aea3 100644 --- a/core/tauri/src/asset_protocol.rs +++ b/core/tauri/src/asset_protocol.rs @@ -4,12 +4,11 @@ use crate::path::SafePathBuf; use crate::scope::FsScope; +use http::{ + header::*, status::StatusCode, HttpRange, MimeType, Request, Response, ResponseBuilder, +}; use rand::RngCore; use std::io::SeekFrom; -use tauri_runtime::http::HttpRange; -use tauri_runtime::http::{ - header::*, status::StatusCode, MimeType, Request, Response, ResponseBuilder, -}; use tauri_utils::debug_eprintln; use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index e46a64725e58..4f9140f3e2a6 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -2,14 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use std::borrow::Cow; + use http::{ - header::{ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN}, - HeaderValue, Method, StatusCode, + header::{ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE}, + HeaderValue, Method, Request as HttpRequest, Response as HttpResponse, StatusCode, }; use crate::{ manager::WindowManager, - runtime::http::{Request as HttpRequest, Response as HttpResponse}, window::{InvokeRequest, UriSchemeProtocolHandler}, Runtime, }; @@ -31,7 +32,7 @@ pub fn get(manager: WindowManager, label: String) -> UriSchemePro let manager = manager.clone(); let label = label.clone(); - let respond = move |mut response: tauri_runtime::http::Response| { + let respond = move |mut response: http::Response>| { response .headers_mut() .insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); @@ -57,34 +58,43 @@ pub fn get(manager: WindowManager, label: String) -> UriSchemePro InvokeResponse::Err(e) => { let mut response = HttpResponse::new(serde_json::to_vec(&e.0).unwrap().into()); - response.set_status(StatusCode::BAD_REQUEST); + *response.status_mut() = StatusCode::BAD_REQUEST; (response, mime::TEXT_PLAIN) } }; - response.set_mimetype(Some(mime_type.essence_str().into())); + response.headers_mut().insert( + CONTENT_TYPE, + HeaderValue::from_str(mime_type.essence_str()).unwrap(), + ); respond(response); }), ); } Err(e) => { - let mut response = HttpResponse::new(e.as_bytes().to_vec().into()); - response.set_status(StatusCode::BAD_REQUEST); - response.set_mimetype(Some(mime::TEXT_PLAIN.essence_str().into())); - respond(response); + respond( + HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) + .body(e.as_bytes().to_vec().into()) + .unwrap(), + ); } } } else { - let mut response = HttpResponse::new( - "failed to acquire window reference" - .as_bytes() - .to_vec() - .into(), + respond( + HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) + .body( + "failed to acquire window reference" + .as_bytes() + .to_vec() + .into(), + ) + .unwrap(), ); - response.set_status(StatusCode::BAD_REQUEST); - response.set_mimetype(Some(mime::TEXT_PLAIN.essence_str().into())); - respond(response); } } @@ -104,8 +114,11 @@ pub fn get(manager: WindowManager, label: String) -> UriSchemePro .to_vec() .into(), ); - r.set_status(StatusCode::METHOD_NOT_ALLOWED); - r.set_mimetype(Some(mime::TEXT_PLAIN.essence_str().into())); + *r.status_mut() = StatusCode::METHOD_NOT_ALLOWED; + r.headers_mut().insert( + CONTENT_TYPE, + HeaderValue::from_str(mime::TEXT_PLAIN.essence_str()).unwrap(), + ); respond(r); } } @@ -266,19 +279,12 @@ fn handle_ipc_message(message: String, manager: &WindowManager, l fn parse_invoke_request( #[allow(unused_variables)] manager: &WindowManager, - request: HttpRequest, + request: HttpRequest>, ) -> std::result::Result { #[allow(unused_mut)] let (parts, mut body) = request.into_parts(); - let cmd = parts - .uri - .strip_prefix("ipc://localhost/") - .map(|c| c.to_string()) - // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows - // where `$P` is not `localhost/*` - // in this case the IPC call is considered invalid - .unwrap_or_else(|| "".to_string()); + let cmd = parts.uri.path(); let cmd = percent_encoding::percent_decode(cmd.as_bytes()) .decode_utf8_lossy() .to_string(); diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index a6135c181422..cddc1c59dab7 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -174,9 +174,6 @@ use std::{ sync::Arc, }; -// Export types likely to be used by the application. -pub use runtime::http; - #[cfg(feature = "wry")] #[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))] pub use tauri_runtime_wry::webview_version; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index f23ff41ef29f..bbbc95715eea 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -19,6 +19,7 @@ use serde::Serialize; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; use url::Url; +use http::{header::CONTENT_TYPE, Request as HttpRequest, Response as HttpResponse}; use tauri_macros::default_runtime; use tauri_utils::debug_eprintln; use tauri_utils::{ @@ -34,10 +35,6 @@ use crate::{ pattern::PatternJavascript, plugin::PluginStore, runtime::{ - http::{ - MimeType, Request as HttpRequest, Response as HttpResponse, - ResponseBuilder as HttpResponseBuilder, - }, webview::WindowBuilder, window::{ dpi::{PhysicalPosition, PhysicalSize}, @@ -312,8 +309,8 @@ pub struct CustomProtocol { pub protocol: Box< dyn Fn( &AppHandle, - HttpRequest, - Box, + HttpRequest>, + Box>) + Send + Sync>, ) -> Result<(), Box> + Send + Sync, @@ -686,7 +683,7 @@ impl WindowManager { process_ipc_message_fn: PROCESS_IPC_MESSAGE_FN, }; match template.render(asset.as_ref(), &Default::default()) { - Ok(asset) => HttpResponseBuilder::new() + Ok(asset) => HttpResponse::builder() .mimetype(mime::TEXT_HTML.as_ref()) .body(asset.into_string().as_bytes().to_vec()), Err(_) => HttpResponseBuilder::new() @@ -696,12 +693,12 @@ impl WindowManager { } } - None => HttpResponseBuilder::new() + None => HttpResponse::builder() .status(404) .mimetype(mime::TEXT_PLAIN.as_ref()) .body(Vec::new()), }, - _ => HttpResponseBuilder::new() + _ => HttpResponse::builder() .status(404) .mimetype(mime::TEXT_PLAIN.as_ref()) .body(Vec::new()), @@ -785,7 +782,7 @@ impl WindowManager { } else { asset }; - let mime_type = MimeType::parse(&final_data, &path); + let mime_type = tauri_utils::mime_type::MimeType::parse(&final_data, &path); Ok(Asset { bytes: final_data.to_vec(), mime_type, @@ -830,10 +827,16 @@ impl WindowManager { Box::new(move |request, responder| { // use the entire URI as we are going to proxy the request let path = if PROXY_DEV_SERVER { - request.uri() + request.uri().to_string() } else { // ignore query string and fragment - request.uri().split(&['?', '#'][..]).next().unwrap() + request + .uri() + .to_string() + .split(&['?', '#'][..]) + .next() + .unwrap() + .into() }; let path = path @@ -844,7 +847,7 @@ impl WindowManager { .unwrap_or_else(|| "".to_string()); let mut builder = - HttpResponseBuilder::new().header("Access-Control-Allow-Origin", &window_origin); + HttpResponse::builder().header("Access-Control-Allow-Origin", &window_origin); #[cfg(all(dev, mobile))] let mut response = { @@ -903,11 +906,11 @@ impl WindowManager { #[cfg(not(all(dev, mobile)))] let mut response = { let asset = manager.get_asset(path)?; - builder = builder.mimetype(&asset.mime_type); + builder = builder.header(CONTENT_TYPE, &asset.mime_type); if let Some(csp) = &asset.csp_header { builder = builder.header("Content-Security-Policy", csp); } - builder.body(asset.bytes)? + builder.body(asset.bytes.into())? }; if let Some(handler) = &web_resource_request_handler { handler(request, &mut response); @@ -1502,7 +1505,7 @@ struct ScaleFactorChanged { } #[cfg(feature = "isolation")] -fn request_to_path(request: &tauri_runtime::http::Request, base_url: &str) -> String { +fn request_to_path(request: &http::Request, base_url: &str) -> String { let mut path = request .uri() .split(&['?', '#'][..]) diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index e2a39315e34a..230a22d2a4f6 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -20,7 +20,6 @@ use crate::{ }, manager::WindowManager, runtime::{ - http::{Request as HttpRequest, Response as HttpResponse}, monitor::Monitor as RuntimeMonitor, webview::{WebviewAttributes, WindowBuilder as _}, window::{ @@ -44,6 +43,7 @@ use crate::{ CursorIcon, Icon, }; +use http::{Request as HttpRequest, Response as HttpResponse}; use serde::Serialize; #[cfg(windows)] use windows::Win32::Foundation::HWND; @@ -51,6 +51,7 @@ use windows::Win32::Foundation::HWND; use tauri_macros::default_runtime; use std::{ + borrow::Cow, collections::{HashMap, HashSet}, fmt, hash::{Hash, Hasher}, @@ -58,12 +59,13 @@ use std::{ sync::{Arc, Mutex}, }; -pub(crate) type WebResourceRequestHandler = dyn Fn(HttpRequest, &mut HttpResponse) + Send + Sync; +pub(crate) type WebResourceRequestHandler = + dyn Fn(HttpRequest>, &mut HttpResponse>) + Send + Sync; pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send; pub(crate) type UriSchemeProtocolHandler = Box< dyn Fn( - HttpRequest, - Box, + HttpRequest>, + Box>) + Send + Sync>, ) -> Result<(), Box> + Send + Sync, @@ -270,15 +272,15 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// ```rust,no_run /// use tauri::{ /// utils::config::{Csp, CspDirectiveSources, WindowUrl}, - /// http::header::HeaderValue, /// window::WindowBuilder, /// }; + /// use http::header::HeaderValue; /// use std::collections::HashMap; /// tauri::Builder::default() /// .setup(|app| { /// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into())) /// .on_web_resource_request(|request, response| { - /// if request.uri().starts_with("tauri://") { + /// if request.uri().scheme_str() == Some("tauri") { /// // if we have a CSP header, Tauri is loading an HTML file /// // for this example, let's dynamically change the CSP /// if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") { @@ -295,7 +297,9 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// Ok(()) /// }); /// ``` - pub fn on_web_resource_request( + pub fn on_web_resource_request< + F: Fn(HttpRequest>, &mut HttpResponse>) + Send + Sync + 'static, + >( mut self, f: F, ) -> Self { @@ -310,9 +314,9 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// ```rust,no_run /// use tauri::{ /// utils::config::{Csp, CspDirectiveSources, WindowUrl}, - /// http::header::HeaderValue, /// window::WindowBuilder, /// }; + /// use http::header::HeaderValue; /// use std::collections::HashMap; /// tauri::Builder::default() /// .setup(|app| { diff --git a/examples/streaming/main.rs b/examples/streaming/main.rs index eb3ce7b06081..78c4f4afd9a6 100644 --- a/examples/streaming/main.rs +++ b/examples/streaming/main.rs @@ -7,12 +7,13 @@ use std::sync::{Arc, Mutex}; fn main() { + use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode}; + use http_range::HttpRange; use std::{ io::{Read, Seek, SeekFrom, Write}, path::PathBuf, process::{Command, Stdio}, }; - use tauri::http::{header::*, status::StatusCode, HttpRange, ResponseBuilder}; let video_file = PathBuf::from("test_video.mp4"); let video_url = @@ -43,14 +44,14 @@ fn main() { .invoke_handler(tauri::generate_handler![video_uri]) .register_uri_scheme_protocol("stream", move |_app, request, responder| { // get the file path - let path = request.uri().strip_prefix("stream://localhost/").unwrap(); + let path = request.uri().path(); let path = percent_encoding::percent_decode(path.as_bytes()) .decode_utf8_lossy() .to_string(); if path != "test_video.mp4" { // return error 404 if it's not our video - responder(ResponseBuilder::new().status(404).body(Vec::new())?); + responder(ResponseBuilder::new().status(404).body(Vec::new().into())?); return Ok(()); } @@ -73,7 +74,7 @@ fn main() { ResponseBuilder::new() .status(StatusCode::RANGE_NOT_SATISFIABLE) .header(CONTENT_RANGE, format!("bytes */{len}")) - .body(vec![]) + .body(vec![].into()) }; // parse range header @@ -119,7 +120,7 @@ fn main() { resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}")); resp = resp.header(CONTENT_LENGTH, end + 1 - start); resp = resp.status(StatusCode::PARTIAL_CONTENT); - resp.body(buf) + resp.body(buf.into()) } else { let mut buf = Vec::new(); let ranges = ranges @@ -172,13 +173,13 @@ fn main() { // all ranges have been written, write the closing boundary buf.write_all(boundary_closer.as_bytes())?; - resp.body(buf) + resp.body(buf.into()) } } else { resp = resp.header(CONTENT_LENGTH, len); let mut buf = Vec::with_capacity(len as usize); file.read_to_end(&mut buf)?; - resp.body(buf) + resp.body(buf.into()) }; responder(response?); From c8787138909e2f837b93ac9ea5de57db7136011e Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 21:13:00 -0300 Subject: [PATCH 05/27] fix mut [skip ci] --- core/tauri/src/menu/icon.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index b398499e10ce..14851fcd35ea 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -207,7 +207,7 @@ impl IconMenuItem { /// - **Windows / Linux**: Unsupported. pub fn set_native_icon(&mut self, _icon: Option) -> crate::Result<()> { #[cfg(target_os = "macos")] - return run_main_thread!(self, |self_: Self| self_ + return run_main_thread!(self, |#[allow(unused_mut)] mut self_: Self| self_ .inner .set_native_icon(_icon.map(Into::into))); #[allow(unreachable_code)] From 9145da2a938dd64ab198badbce964648bfe9a7d8 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 21:13:48 -0300 Subject: [PATCH 06/27] remove console.log [skip ci] --- core/tauri/scripts/ipc-protocol.js | 1 - 1 file changed, 1 deletion(-) diff --git a/core/tauri/scripts/ipc-protocol.js b/core/tauri/scripts/ipc-protocol.js index b2af9c47897b..082a8ddd5466 100644 --- a/core/tauri/scripts/ipc-protocol.js +++ b/core/tauri/scripts/ipc-protocol.js @@ -32,7 +32,6 @@ ) && !(osName === 'macos' && location.protocol === 'https:') ) { - console.log('process') const { contentType, data From ac360c662d6bbb5cb5815c7e8808f6f93d09609f Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 21:30:04 -0300 Subject: [PATCH 07/27] fix asset protocol usage --- core/tauri/Cargo.toml | 3 +- core/tauri/src/asset_protocol.rs | 48 +++++++++++++++----------------- core/tauri/src/ipc/protocol.rs | 2 +- core/tauri/src/manager.rs | 39 ++++++++++---------------- 4 files changed, 41 insertions(+), 51 deletions(-) diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 8e6e13b70bde..390bd281eb2f 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -67,6 +67,7 @@ serialize-to-javascript = "=0.1.1" infer = { version = "0.15", optional = true } png = { version = "0.17", optional = true } ico = { version = "0.3.0", optional = true } +http-range = { version = "0.1.4", optional = true } [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies] muda = { version = "0.8", default-features = false } @@ -146,7 +147,7 @@ macos-private-api = [ "tauri-runtime-wry/macos-private-api" ] window-data-url = [ "data-url" ] -protocol-asset = [ ] +protocol-asset = [ "http-range" ] config-json5 = [ "tauri-macros/config-json5" ] config-toml = [ "tauri-macros/config-toml" ] icon-ico = [ "infer", "ico" ] diff --git a/core/tauri/src/asset_protocol.rs b/core/tauri/src/asset_protocol.rs index 5d5081e1aea3..7896369b6941 100644 --- a/core/tauri/src/asset_protocol.rs +++ b/core/tauri/src/asset_protocol.rs @@ -4,44 +4,41 @@ use crate::path::SafePathBuf; use crate::scope::FsScope; -use http::{ - header::*, status::StatusCode, HttpRange, MimeType, Request, Response, ResponseBuilder, -}; +use http::{header::*, status::StatusCode, Request, Response}; +use http_range::HttpRange; use rand::RngCore; -use std::io::SeekFrom; +use std::{borrow::Cow, io::SeekFrom}; use tauri_utils::debug_eprintln; +use tauri_utils::mime_type::MimeType; use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; -use url::Position; -use url::Url; pub fn asset_protocol_handler( - request: Request, + request: Request>, scope: FsScope, window_origin: String, -) -> Result> { - let parsed_path = Url::parse(request.uri())?; - let filtered_path = &parsed_path[..Position::AfterPath]; - let path = filtered_path - .strip_prefix("asset://localhost/") - // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows - // where `$P` is not `localhost/*` - .unwrap_or(""); - let path = percent_encoding::percent_decode(path.as_bytes()) +) -> Result>, Box> { + let path = percent_encoding::percent_decode(request.uri().path().as_bytes()) .decode_utf8_lossy() .to_string(); if let Err(e) = SafePathBuf::new(path.clone().into()) { debug_eprintln!("asset protocol path \"{}\" is not valid: {}", path, e); - return ResponseBuilder::new().status(403).body(Vec::new()); + return Response::builder() + .status(403) + .body(Vec::new().into()) + .map_err(Into::into); } if !scope.is_allowed(&path) { debug_eprintln!("asset protocol not configured to allow the path: {}", path); - return ResponseBuilder::new().status(403).body(Vec::new()); + return Response::builder() + .status(403) + .body(Vec::new().into()) + .map_err(Into::into); } - let mut resp = ResponseBuilder::new().header("Access-Control-Allow-Origin", &window_origin); + let mut resp = Response::builder().header("Access-Control-Allow-Origin", &window_origin); let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move { let mut file = File::open(&path).await?; @@ -83,10 +80,11 @@ pub fn asset_protocol_handler( resp = resp.header(ACCEPT_RANGES, "bytes"); let not_satisfiable = || { - ResponseBuilder::new() + Response::builder() .status(StatusCode::RANGE_NOT_SATISFIABLE) .header(CONTENT_RANGE, format!("bytes */{len}")) - .body(vec![]) + .body(vec![].into()) + .map_err(Into::into) }; // parse range header @@ -131,7 +129,7 @@ pub fn asset_protocol_handler( resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}")); resp = resp.header(CONTENT_LENGTH, end + 1 - start); resp = resp.status(StatusCode::PARTIAL_CONTENT); - resp.body(buf) + resp.body(buf.into()) } else { let ranges = ranges .iter() @@ -191,7 +189,7 @@ pub fn asset_protocol_handler( Ok::, anyhow::Error>(buf) })?; - resp.body(buf) + resp.body(buf.into()) } } else { // avoid reading the file if we already read it @@ -206,10 +204,10 @@ pub fn asset_protocol_handler( })? }; resp = resp.header(CONTENT_LENGTH, len); - resp.body(buf) + resp.body(buf.into()) }; - response + response.map_err(Into::into) } fn random_boundary() -> String { diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index 4f9140f3e2a6..1c64c025ab67 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -284,7 +284,7 @@ fn parse_invoke_request( #[allow(unused_mut)] let (parts, mut body) = request.into_parts(); - let cmd = parts.uri.path(); + let cmd = parts.uri.path().trim_start_matches('/'); let cmd = percent_encoding::percent_decode(cmd.as_bytes()) .decode_utf8_lossy() .to_string(); diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index bbbc95715eea..274e09c51bb8 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -669,12 +669,10 @@ impl WindowManager { } = &self.inner.pattern { let assets = assets.clone(); - let schema_ = schema.clone(); - let url_base = format!("{schema_}://localhost"); let aes_gcm_key = *crypto_keys.aes_gcm().raw(); pending.register_uri_scheme_protocol(schema, move |request, responder| { - let response = match request_to_path(&request, &url_base).as_str() { + let response = match request_to_path(&request).as_str() { "index.html" => match assets.get(&"index.html".into()) { Some(asset) => { let asset = String::from_utf8_lossy(asset.as_ref()); @@ -684,24 +682,24 @@ impl WindowManager { }; match template.render(asset.as_ref(), &Default::default()) { Ok(asset) => HttpResponse::builder() - .mimetype(mime::TEXT_HTML.as_ref()) - .body(asset.into_string().as_bytes().to_vec()), - Err(_) => HttpResponseBuilder::new() + .header(CONTENT_TYPE, mime::TEXT_HTML.as_ref()) + .body(asset.into_string().as_bytes().to_vec().into()), + Err(_) => HttpResponse::builder() .status(500) - .mimetype(mime::TEXT_PLAIN.as_ref()) - .body(Vec::new()), + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(Vec::new().into()), } } None => HttpResponse::builder() .status(404) - .mimetype(mime::TEXT_PLAIN.as_ref()) - .body(Vec::new()), + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(Vec::new().into()), }, _ => HttpResponse::builder() .status(404) - .mimetype(mime::TEXT_PLAIN.as_ref()) - .body(Vec::new()), + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(Vec::new().into()), }?; (responder)(response); @@ -1505,19 +1503,12 @@ struct ScaleFactorChanged { } #[cfg(feature = "isolation")] -fn request_to_path(request: &http::Request, base_url: &str) -> String { - let mut path = request +fn request_to_path(request: &http::Request>) -> String { + let path = request .uri() - .split(&['?', '#'][..]) - // ignore query string - .next() - .unwrap() - .trim_start_matches(base_url) - .to_string(); - - if path.ends_with('/') { - path.pop(); - } + .path() + .trim_start_matches('/') + .trim_end_matches('/'); let path = percent_encoding::percent_decode(path.as_bytes()) .decode_utf8_lossy() From 35912fbad57ae062e045ed14e5e6b53e8049967a Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 21:38:49 -0300 Subject: [PATCH 08/27] fix channel usage --- core/tauri/src/ipc/channel.rs | 2 +- core/tauri/src/ipc/protocol.rs | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/core/tauri/src/ipc/channel.rs b/core/tauri/src/ipc/channel.rs index e09a8d922b53..76a9101cebaf 100644 --- a/core/tauri/src/ipc/channel.rs +++ b/core/tauri/src/ipc/channel.rs @@ -78,7 +78,7 @@ impl Channel { .unwrap() .insert(data_id, body); window.eval(&format!( - "__TAURI_INVOKE__('{FETCH_CHANNEL_DATA_COMMAND}', null, {{ headers: {{ '{CHANNEL_ID_HEADER_NAME}': {data_id} }} }}).then(window['_' + {}]).catch(console.error)", + "__TAURI_INVOKE__('{FETCH_CHANNEL_DATA_COMMAND}', null, {{ headers: {{ '{CHANNEL_ID_HEADER_NAME}': '{data_id}' }} }}).then(window['_' + {}]).catch(console.error)", callback.0 )) }) diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index 1c64c025ab67..a82ed5acea63 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -243,7 +243,9 @@ fn handle_ipc_message(message: String, manager: &WindowManager, l match &response { InvokeResponse::Ok(InvokeBody::Json(v)) => { - if matches!(v, JsonValue::Object(_) | JsonValue::Array(_)) { + if !cfg!(target_os = "macos") + && matches!(v, JsonValue::Object(_) | JsonValue::Array(_)) + { let _ = Channel::from_ipc(window.clone(), callback).send(v); } else { responder_eval( @@ -254,8 +256,16 @@ fn handle_ipc_message(message: String, manager: &WindowManager, l } } InvokeResponse::Ok(InvokeBody::Raw(v)) => { - let _ = - Channel::from_ipc(window.clone(), callback).send(InvokeBody::Raw(v.clone())); + responder_eval( + &window, + format_callback_result(Result::<_, ()>::Ok(v), callback, error), + error, + ); + if cfg!(target_os = "macos") { + } else { + let _ = + Channel::from_ipc(window.clone(), callback).send(InvokeBody::Raw(v.clone())); + } } InvokeResponse::Err(e) => responder_eval( &window, From 0c13903a67bd6bdbddfcce83ebd81a467af2c0c4 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 5 Sep 2023 21:44:15 -0300 Subject: [PATCH 09/27] update wry [skip ci] --- core/tauri-runtime-wry/src/lib.rs | 9 +- examples/api/src-tauri/Cargo.lock | 131 +++--------------------------- 2 files changed, 15 insertions(+), 125 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 691f3ad17aed..cf55f97f2f19 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -2665,10 +2665,11 @@ fn create_webview( } for (scheme, protocol) in uri_scheme_protocols { - webview_builder = webview_builder.with_custom_protocol(scheme, move |request, api| { - protocol(request, Box::new(move |response| api.respond(response))) - .map_err(|_| wry::Error::InitScriptError) - }); + webview_builder = + webview_builder.with_asynchronous_custom_protocol(scheme, move |request, api| { + protocol(request, Box::new(move |response| api.respond(response))) + .map_err(|_| wry::Error::InitScriptError) + }); } for script in webview_attributes.initialization_scripts { diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 01f36e50fd94..731b38ce1569 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -17,41 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "aho-corasick" version = "1.0.3" @@ -561,16 +526,6 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "clap" version = "4.3.21" @@ -779,7 +734,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] @@ -820,15 +774,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - [[package]] name = "darling" version = "0.20.3" @@ -1394,16 +1339,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "ghash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" -dependencies = [ - "opaque-debug", - "polyval", -] - [[package]] name = "gimli" version = "0.27.3" @@ -1791,15 +1726,6 @@ dependencies = [ "cfb", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - [[package]] name = "instant" version = "0.1.12" @@ -2331,12 +2257,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - [[package]] name = "ordered-stream" version = "0.2.0" @@ -2583,18 +2503,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "polyval" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3279,12 +3187,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - [[package]] name = "swift-rs" version = "1.0.6" @@ -3399,7 +3301,7 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tauri" -version = "2.0.0-alpha.10" +version = "2.0.0-alpha.11" dependencies = [ "anyhow", "bytes", @@ -3412,6 +3314,7 @@ dependencies = [ "gtk", "heck", "http", + "http-range", "ico", "infer 0.15.0", "jni", @@ -3449,7 +3352,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.0-alpha.6" +version = "2.0.0-alpha.8" dependencies = [ "anyhow", "cargo_toml", @@ -3469,7 +3372,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.0-alpha.6" +version = "2.0.0-alpha.7" dependencies = [ "base64", "brotli", @@ -3493,7 +3396,7 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.0-alpha.6" +version = "2.0.0-alpha.7" dependencies = [ "heck", "proc-macro2", @@ -3549,11 +3452,10 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.13.0-alpha.6" +version = "1.0.0-alpha.0" dependencies = [ "gtk", "http", - "http-range", "jni", "rand 0.8.5", "raw-window-handle", @@ -3568,10 +3470,11 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.13.0-alpha.6" +version = "1.0.0-alpha.0" dependencies = [ "cocoa 0.24.1", "gtk", + "http", "jni", "percent-encoding", "rand 0.8.5", @@ -3587,13 +3490,11 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.0-alpha.6" +version = "2.0.0-alpha.7" dependencies = [ - "aes-gcm", "brotli", "ctor", "dunce", - "getrandom 0.2.10", "glob", "heck", "html5ever", @@ -3608,7 +3509,6 @@ dependencies = [ "serde", "serde_json", "serde_with", - "serialize-to-javascript", "thiserror", "url", "walkdir", @@ -3952,16 +3852,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "url" version = "2.4.0" @@ -4530,8 +4420,7 @@ dependencies = [ [[package]] name = "wry" version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6289018fa3cbc051c13f4ae1a102d80c3f35a527456c75567eb2cad6989020" +source = "git+https://github.com/tauri-apps/wry?branch=refactor/custom-protocol-response#d06020d90ad05df7f4df8a9c579b5ae42ea9b98b" dependencies = [ "base64", "block", From 0f936ba16cd08bed19a90daa9d797b201ac9f30e Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 08:10:55 -0300 Subject: [PATCH 10/27] add struct api instead of closure --- core/tauri/src/app.rs | 14 +++- core/tauri/src/ipc/protocol.rs | 2 +- core/tauri/src/manager.rs | 41 ++++++----- core/tauri/src/window.rs | 7 +- examples/api/src-tauri/Cargo.lock | 113 +++++++++++++++++++++++++++++- examples/streaming/main.rs | 20 +++--- 6 files changed, 160 insertions(+), 37 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index c2f96b2a56f1..4449ec0a67cb 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1356,7 +1356,7 @@ impl Builder { H: Fn( &AppHandle, HttpRequest>, - Box>) + Send + Sync>, + UriSchemeResponse, ) -> Result<(), Box> + Send + Sync @@ -1584,6 +1584,18 @@ impl Builder { } } +pub struct UriSchemeResponse( + pub(crate) Box>) + Send + Sync>, +); + +impl UriSchemeResponse { + /// Resolves the request with the given response. + pub fn respond>>(self, response: HttpResponse) { + let (parts, body) = response.into_parts(); + (self.0)(HttpResponse::from_parts(parts, body.into())) + } +} + #[cfg(target_os = "macos")] fn init_app_menu(menu: &Menu) -> crate::Result<()> { menu.inner().init_for_nsapp(); diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index a82ed5acea63..480f338d8094 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -36,7 +36,7 @@ pub fn get(manager: WindowManager, label: String) -> UriSchemePro response .headers_mut() .insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); - responder(response); + responder.respond(response); }; match *request.method() { diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 274e09c51bb8..e52a934e88fa 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -29,7 +29,10 @@ use tauri_utils::{ }; use crate::{ - app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload}, + app::{ + AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload, + UriSchemeResponse, + }, event::{assert_event_name_is_valid, Event, EventHandler, Listeners}, ipc::{Invoke, InvokeHandler, InvokeResponder}, pattern::PatternJavascript, @@ -310,7 +313,7 @@ pub struct CustomProtocol { dyn Fn( &AppHandle, HttpRequest>, - Box>) + Send + Sync>, + UriSchemeResponse, ) -> Result<(), Box> + Send + Sync, @@ -606,7 +609,7 @@ impl WindowManager { let protocol = protocol.clone(); let app_handle = Mutex::new(app_handle.clone()); pending.register_uri_scheme_protocol(uri_scheme.clone(), move |p, responder| { - (protocol.protocol)(&app_handle.lock().unwrap(), p, responder) + (protocol.protocol)(&app_handle.lock().unwrap(), p, UriSchemeResponse(responder)) }); } @@ -629,18 +632,18 @@ impl WindowManager { if !registered_scheme_protocols.contains(&"tauri".into()) { let web_resource_request_handler = pending.web_resource_request_handler.take(); - pending.register_uri_scheme_protocol( - "tauri", - self.prepare_uri_scheme_protocol(&window_origin, web_resource_request_handler), - ); + let protocol = self.prepare_uri_scheme_protocol(&window_origin, web_resource_request_handler); + pending.register_uri_scheme_protocol("tauri", move |request, response| { + protocol(request, UriSchemeResponse(response)) + }); registered_scheme_protocols.push("tauri".into()); } if !registered_scheme_protocols.contains(&"ipc".into()) { - pending.register_uri_scheme_protocol( - "ipc", - crate::ipc::protocol::get(self.clone(), pending.label.clone()), - ); + let protocol = crate::ipc::protocol::get(self.clone(), pending.label.clone()); + pending.register_uri_scheme_protocol("ipc", move |request, response| { + protocol(request, UriSchemeResponse(response)) + }); registered_scheme_protocols.push("ipc".into()); } @@ -683,26 +686,26 @@ impl WindowManager { match template.render(asset.as_ref(), &Default::default()) { Ok(asset) => HttpResponse::builder() .header(CONTENT_TYPE, mime::TEXT_HTML.as_ref()) - .body(asset.into_string().as_bytes().to_vec().into()), + .body(asset.into_string().as_bytes().to_vec()), Err(_) => HttpResponse::builder() .status(500) .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new().into()), + .body(Vec::new()), } } None => HttpResponse::builder() .status(404) .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new().into()), + .body(Vec::new()), }, _ => HttpResponse::builder() .status(404) .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new().into()), + .body(Vec::new()), }?; - (responder)(response); + responder.respond(response); Ok(()) }); @@ -892,7 +895,7 @@ impl WindowManager { } builder .status(response.status) - .body(response.body.to_vec())? + .body(response.body.to_vec().into())? } Err(e) => { debug_eprintln!("Failed to request {}: {}", url.as_str(), e); @@ -919,10 +922,10 @@ impl WindowManager { let response_csp = String::from_utf8_lossy(response_csp.as_bytes()); let html = String::from_utf8_lossy(response.body()); let body = html.replacen(tauri_utils::html::CSP_TOKEN, &response_csp, 1); - *response.body_mut() = body.as_bytes().to_vec().into(); + *response.body_mut() = body.as_bytes().to_vec(); } - responder(response); + responder.respond(response); Ok(()) }) diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 230a22d2a4f6..f7131305fd92 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -11,7 +11,7 @@ use url::Url; #[cfg(target_os = "macos")] use crate::TitleBarStyle; use crate::{ - app::AppHandle, + app::{AppHandle, UriSchemeResponse}, command::{CommandArg, CommandItem}, event::{Event, EventHandler}, ipc::{ @@ -63,10 +63,7 @@ pub(crate) type WebResourceRequestHandler = dyn Fn(HttpRequest>, &mut HttpResponse>) + Send + Sync; pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send; pub(crate) type UriSchemeProtocolHandler = Box< - dyn Fn( - HttpRequest>, - Box>) + Send + Sync>, - ) -> Result<(), Box> + dyn Fn(HttpRequest>, UriSchemeResponse) -> Result<(), Box> + Send + Sync, >; diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 731b38ce1569..39b074f83309 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -17,6 +17,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.0.3" @@ -526,6 +561,16 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.3.21" @@ -734,6 +779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -774,6 +820,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.20.3" @@ -1339,6 +1394,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.27.3" @@ -1726,6 +1791,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -2257,6 +2331,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2503,6 +2583,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3187,6 +3279,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "swift-rs" version = "1.0.6" @@ -3492,9 +3590,11 @@ dependencies = [ name = "tauri-utils" version = "2.0.0-alpha.7" dependencies = [ + "aes-gcm", "brotli", "ctor", "dunce", + "getrandom 0.2.10", "glob", "heck", "html5ever", @@ -3509,6 +3609,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "serialize-to-javascript", "thiserror", "url", "walkdir", @@ -3852,6 +3953,16 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.4.0" @@ -4420,7 +4531,7 @@ dependencies = [ [[package]] name = "wry" version = "0.31.0" -source = "git+https://github.com/tauri-apps/wry?branch=refactor/custom-protocol-response#d06020d90ad05df7f4df8a9c579b5ae42ea9b98b" +source = "git+https://github.com/tauri-apps/wry?branch=refactor/custom-protocol-response#52c9a16d847d8c1f33a42705f14644a23a4109e9" dependencies = [ "base64", "block", diff --git a/examples/streaming/main.rs b/examples/streaming/main.rs index 78c4f4afd9a6..566f2d8c5ea4 100644 --- a/examples/streaming/main.rs +++ b/examples/streaming/main.rs @@ -42,7 +42,7 @@ fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![video_uri]) - .register_uri_scheme_protocol("stream", move |_app, request, responder| { + .register_uri_scheme_protocol("stream", move |_app, request, response| { // get the file path let path = request.uri().path(); let path = percent_encoding::percent_decode(path.as_bytes()) @@ -51,7 +51,7 @@ fn main() { if path != "test_video.mp4" { // return error 404 if it's not our video - responder(ResponseBuilder::new().status(404).body(Vec::new().into())?); + response.respond(ResponseBuilder::new().status(404).body(Vec::new())?); return Ok(()); } @@ -69,12 +69,12 @@ fn main() { // if the webview sent a range header, we need to send a 206 in return // Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers. - let response = if let Some(range_header) = request.headers().get("range") { + let http_response = if let Some(range_header) = request.headers().get("range") { let not_satisfiable = || { ResponseBuilder::new() .status(StatusCode::RANGE_NOT_SATISFIABLE) .header(CONTENT_RANGE, format!("bytes */{len}")) - .body(vec![].into()) + .body(vec![]) }; // parse range header @@ -85,7 +85,7 @@ fn main() { .map(|r| (r.start, r.start + r.length - 1)) .collect::>() } else { - responder(not_satisfiable()?); + response.respond(not_satisfiable()?); return Ok(()); }; @@ -100,7 +100,7 @@ fn main() { // this should be already taken care of by HttpRange::parse // but checking here again for extra assurance if start >= len || end >= len || end < start { - responder(not_satisfiable()?); + response.respond(not_satisfiable()?); return Ok(()); } @@ -120,7 +120,7 @@ fn main() { resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}")); resp = resp.header(CONTENT_LENGTH, end + 1 - start); resp = resp.status(StatusCode::PARTIAL_CONTENT); - resp.body(buf.into()) + resp.body(buf) } else { let mut buf = Vec::new(); let ranges = ranges @@ -173,16 +173,16 @@ fn main() { // all ranges have been written, write the closing boundary buf.write_all(boundary_closer.as_bytes())?; - resp.body(buf.into()) + resp.body(buf) } } else { resp = resp.header(CONTENT_LENGTH, len); let mut buf = Vec::with_capacity(len as usize); file.read_to_end(&mut buf)?; - resp.body(buf.into()) + resp.body(buf) }; - responder(response?); + response.respond(http_response?); Ok(()) }) From bc004b0109c7e374aa0195efe786665b1c2ac8ca Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 09:45:38 -0300 Subject: [PATCH 11/27] custom protocol no longer return result --- core/tauri-runtime-wry/src/lib.rs | 5 +- core/tauri-runtime/src/window.rs | 10 +- core/tauri/src/app.rs | 9 +- core/tauri/src/ipc/protocol.rs | 2 - core/tauri/src/lib.rs | 3 +- core/tauri/src/manager.rs | 209 +++--------- .../{asset_protocol.rs => protocol/asset.rs} | 2 +- core/tauri/src/protocol/mod.rs | 7 + core/tauri/src/protocol/tauri.rs | 179 ++++++++++ core/tauri/src/window.rs | 7 +- examples/api/src-tauri/Cargo.lock | 2 +- examples/streaming/main.rs | 313 +++++++++--------- 12 files changed, 396 insertions(+), 352 deletions(-) rename core/tauri/src/{asset_protocol.rs => protocol/asset.rs} (99%) create mode 100644 core/tauri/src/protocol/mod.rs create mode 100644 core/tauri/src/protocol/tauri.rs diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index cf55f97f2f19..de27f03546e8 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -2665,10 +2665,9 @@ fn create_webview( } for (scheme, protocol) in uri_scheme_protocols { - webview_builder = - webview_builder.with_asynchronous_custom_protocol(scheme, move |request, api| { + webview_builder = webview_builder + .with_asynchronous_custom_protocol(scheme, move |request, api| { protocol(request, Box::new(move |response| api.respond(response))) - .map_err(|_| wry::Error::InitScriptError) }); } diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index eab21a826adc..bfcdb19c9b39 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -25,10 +25,7 @@ use std::{ use self::dpi::PhysicalPosition; -type UriSchemeProtocol = dyn Fn( - HttpRequest>, - Box>) + Send + Sync>, - ) -> Result<(), Box> +type UriSchemeProtocol = dyn Fn(HttpRequest>, Box>) + Send + Sync>) + Send + Sync + 'static; @@ -313,10 +310,7 @@ impl> PendingWindow { pub fn register_uri_scheme_protocol< N: Into, - H: Fn( - HttpRequest>, - Box>) + Send + Sync>, - ) -> Result<(), Box> + H: Fn(HttpRequest>, Box>) + Send + Sync>) + Send + Sync + 'static, diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 4449ec0a67cb..f3d31183e78c 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1353,14 +1353,7 @@ impl Builder { #[must_use] pub fn register_uri_scheme_protocol< N: Into, - H: Fn( - &AppHandle, - HttpRequest>, - UriSchemeResponse, - ) -> Result<(), Box> - + Send - + Sync - + 'static, + H: Fn(&AppHandle, HttpRequest>, UriSchemeResponse) + Send + Sync + 'static, >( mut self, uri_scheme: N, diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index 480f338d8094..043ea931a4d8 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -122,8 +122,6 @@ pub fn get(manager: WindowManager, label: String) -> UriSchemePro respond(r); } } - - Ok(()) }) } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index cddc1c59dab7..6ca1dcec0029 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -76,8 +76,6 @@ pub use tauri_macros::{command, generate_handler}; pub mod api; pub(crate) mod app; -#[cfg(feature = "protocol-asset")] -pub(crate) mod asset_protocol; pub mod async_runtime; pub mod command; mod error; @@ -86,6 +84,7 @@ pub mod ipc; mod manager; mod pattern; pub mod plugin; +pub(crate) mod protocol; mod vibrancy; pub mod window; use tauri_runtime as runtime; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index e52a934e88fa..c7e1b20ab594 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -19,7 +19,7 @@ use serde::Serialize; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; use url::Url; -use http::{header::CONTENT_TYPE, Request as HttpRequest, Response as HttpResponse}; +use http::Request as HttpRequest; use tauri_macros::default_runtime; use tauri_utils::debug_eprintln; use tauri_utils::{ @@ -49,7 +49,6 @@ use crate::{ config::{AppUrl, Config, WindowUrl}, PackageInfo, }, - window::{UriSchemeProtocolHandler, WebResourceRequestHandler}, Context, EventLoopMessage, Icon, Manager, Pattern, Runtime, Scopes, StateManager, Window, WindowEvent, }; @@ -81,7 +80,7 @@ pub(crate) const PROCESS_IPC_MESSAGE_FN: &str = // and we do not get a secure context without the custom protocol that proxies to the dev server // additionally, we need the custom protocol to inject the initialization scripts on Android // must also keep in sync with the `let mut response` assignment in prepare_uri_scheme_protocol -const PROXY_DEV_SERVER: bool = cfg!(all(dev, mobile)); +pub(crate) const PROXY_DEV_SERVER: bool = cfg!(all(dev, mobile)); #[cfg(feature = "isolation")] #[derive(Template)] @@ -309,15 +308,7 @@ pub struct Asset { pub struct CustomProtocol { /// Handler for protocol #[allow(clippy::type_complexity)] - pub protocol: Box< - dyn Fn( - &AppHandle, - HttpRequest>, - UriSchemeResponse, - ) -> Result<(), Box> - + Send - + Sync, - >, + pub protocol: Box, HttpRequest>, UriSchemeResponse) + Send + Sync>, } #[default_runtime(crate::Wry, wry)] @@ -632,7 +623,8 @@ impl WindowManager { if !registered_scheme_protocols.contains(&"tauri".into()) { let web_resource_request_handler = pending.web_resource_request_handler.take(); - let protocol = self.prepare_uri_scheme_protocol(&window_origin, web_resource_request_handler); + let protocol = + crate::protocol::tauri::get(self, &window_origin, web_resource_request_handler); pending.register_uri_scheme_protocol("tauri", move |request, response| { protocol(request, UriSchemeResponse(response)) }); @@ -651,15 +643,15 @@ impl WindowManager { if !registered_scheme_protocols.contains(&"asset".into()) { let asset_scope = self.state().get::().asset_protocol.clone(); pending.register_uri_scheme_protocol("asset", move |request, responder| { - let response = crate::asset_protocol::asset_protocol_handler( - request, - asset_scope.clone(), - window_origin.clone(), - )?; - - responder(response); - - Ok(()) + match crate::protocol::asset::get(request, asset_scope.clone(), window_origin.clone()) { + Ok(response) => responder(response), + Err(e) => responder( + http::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body(e.to_string().as_bytes().to_vec().into()) + .unwrap(), + ), + } }); } @@ -671,6 +663,8 @@ impl WindowManager { crypto_keys, } = &self.inner.pattern { + use http::header::CONTENT_TYPE; + let assets = assets.clone(); let aes_gcm_key = *crypto_keys.aes_gcm().raw(); @@ -684,30 +678,37 @@ impl WindowManager { process_ipc_message_fn: PROCESS_IPC_MESSAGE_FN, }; match template.render(asset.as_ref(), &Default::default()) { - Ok(asset) => HttpResponse::builder() + Ok(asset) => http::Response::builder() .header(CONTENT_TYPE, mime::TEXT_HTML.as_ref()) - .body(asset.into_string().as_bytes().to_vec()), - Err(_) => HttpResponse::builder() - .status(500) + .body(asset.into_string().as_bytes().to_vec().into()), + Err(_) => http::Response::builder() + .status(http::StatusCode::INTERNAL_SERVER_ERROR) .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new()), + .body(Vec::new().into()), } } - None => HttpResponse::builder() - .status(404) + None => http::Response::builder() + .status(http::StatusCode::NOT_FOUND) .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new()), + .body(Vec::new().into()), }, - _ => HttpResponse::builder() - .status(404) + _ => http::Response::builder() + .status(http::StatusCode::NOT_FOUND) .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new()), - }?; - - responder.respond(response); + .body(Vec::new().into()), + }; - Ok(()) + if let Ok(r) = response { + responder(r); + } else { + responder( + http::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body("failed to get response".as_bytes().to_vec().into()) + .unwrap(), + ); + } }); } @@ -797,140 +798,6 @@ impl WindowManager { } } - fn prepare_uri_scheme_protocol( - &self, - window_origin: &str, - web_resource_request_handler: Option>, - ) -> UriSchemeProtocolHandler { - #[cfg(all(dev, mobile))] - let url = { - let mut url = self.get_url().as_str().to_string(); - if url.ends_with('/') { - url.pop(); - } - url - }; - #[cfg(not(all(dev, mobile)))] - let manager = self.clone(); - let window_origin = window_origin.to_string(); - - #[cfg(all(dev, mobile))] - #[derive(Clone)] - struct CachedResponse { - status: http::StatusCode, - headers: http::HeaderMap, - body: bytes::Bytes, - } - - #[cfg(all(dev, mobile))] - let response_cache = Arc::new(Mutex::new(HashMap::new())); - - Box::new(move |request, responder| { - // use the entire URI as we are going to proxy the request - let path = if PROXY_DEV_SERVER { - request.uri().to_string() - } else { - // ignore query string and fragment - request - .uri() - .to_string() - .split(&['?', '#'][..]) - .next() - .unwrap() - .into() - }; - - let path = path - .strip_prefix("tauri://localhost") - .map(|p| p.to_string()) - // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows - // where `$P` is not `localhost/*` - .unwrap_or_else(|| "".to_string()); - - let mut builder = - HttpResponse::builder().header("Access-Control-Allow-Origin", &window_origin); - - #[cfg(all(dev, mobile))] - let mut response = { - let decoded_path = percent_encoding::percent_decode(path.as_bytes()) - .decode_utf8_lossy() - .to_string(); - let url = format!("{url}{decoded_path}"); - #[allow(unused_mut)] - let mut client_builder = reqwest::ClientBuilder::new(); - #[cfg(any(feature = "native-tls", feature = "rustls-tls"))] - { - client_builder = client_builder.danger_accept_invalid_certs(true); - } - let mut proxy_builder = client_builder - .build() - .unwrap() - .request(request.method().clone(), &url); - for (name, value) in request.headers() { - proxy_builder = proxy_builder.header(name, value); - } - match crate::async_runtime::block_on(proxy_builder.send()) { - Ok(r) => { - let mut response_cache_ = response_cache.lock().unwrap(); - let mut response = None; - if r.status() == http::StatusCode::NOT_MODIFIED { - response = response_cache_.get(&url); - } - let response = if let Some(r) = response { - r - } else { - let status = r.status(); - let headers = r.headers().clone(); - let body = crate::async_runtime::block_on(r.bytes())?; - let response = CachedResponse { - status, - headers, - body, - }; - response_cache_.insert(url.clone(), response); - response_cache_.get(&url).unwrap() - }; - for (name, value) in &response.headers { - builder = builder.header(name, value); - } - builder - .status(response.status) - .body(response.body.to_vec().into())? - } - Err(e) => { - debug_eprintln!("Failed to request {}: {}", url.as_str(), e); - return Err(Box::new(e)); - } - } - }; - - #[cfg(not(all(dev, mobile)))] - let mut response = { - let asset = manager.get_asset(path)?; - builder = builder.header(CONTENT_TYPE, &asset.mime_type); - if let Some(csp) = &asset.csp_header { - builder = builder.header("Content-Security-Policy", csp); - } - builder.body(asset.bytes.into())? - }; - if let Some(handler) = &web_resource_request_handler { - handler(request, &mut response); - } - // if it's an HTML file, we need to set the CSP meta tag on Linux - #[cfg(all(not(dev), target_os = "linux"))] - if let Some(response_csp) = response.headers().get("Content-Security-Policy") { - let response_csp = String::from_utf8_lossy(response_csp.as_bytes()); - let html = String::from_utf8_lossy(response.body()); - let body = html.replacen(tauri_utils::html::CSP_TOKEN, &response_csp, 1); - *response.body_mut() = body.as_bytes().to_vec(); - } - - responder.respond(response); - - Ok(()) - }) - } - fn initialization_script( &self, ipc_script: &str, diff --git a/core/tauri/src/asset_protocol.rs b/core/tauri/src/protocol/asset.rs similarity index 99% rename from core/tauri/src/asset_protocol.rs rename to core/tauri/src/protocol/asset.rs index 7896369b6941..d00be02cdfb2 100644 --- a/core/tauri/src/asset_protocol.rs +++ b/core/tauri/src/protocol/asset.rs @@ -13,7 +13,7 @@ use tauri_utils::mime_type::MimeType; use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; -pub fn asset_protocol_handler( +pub fn get( request: Request>, scope: FsScope, window_origin: String, diff --git a/core/tauri/src/protocol/mod.rs b/core/tauri/src/protocol/mod.rs new file mode 100644 index 000000000000..dc9abd8c799c --- /dev/null +++ b/core/tauri/src/protocol/mod.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg(feature = "protocol-asset")] +pub mod asset; +pub mod tauri; diff --git a/core/tauri/src/protocol/tauri.rs b/core/tauri/src/protocol/tauri.rs new file mode 100644 index 000000000000..e9b54619c822 --- /dev/null +++ b/core/tauri/src/protocol/tauri.rs @@ -0,0 +1,179 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::borrow::Cow; + +use http::{header::CONTENT_TYPE, Request, Response as HttpResponse, StatusCode}; + +use crate::{ + manager::{WindowManager, PROXY_DEV_SERVER}, + window::{UriSchemeProtocolHandler, WebResourceRequestHandler}, + Runtime, +}; + +#[cfg(all(dev, mobile))] +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +#[cfg(all(dev, mobile))] +#[derive(Clone)] +struct CachedResponse { + status: http::StatusCode, + headers: http::HeaderMap, + body: bytes::Bytes, +} + +pub fn get( + manager: &WindowManager, + window_origin: &str, + web_resource_request_handler: Option>, +) -> UriSchemeProtocolHandler { + #[cfg(all(dev, mobile))] + let url = { + let mut url = manager.get_url().as_str().to_string(); + if url.ends_with('/') { + url.pop(); + } + url + }; + + let manager = manager.clone(); + let window_origin = window_origin.to_string(); + + #[cfg(all(dev, mobile))] + let response_cache = Arc::new(Mutex::new(HashMap::new())); + + Box::new(move |request, responder| { + match get_response( + request, + &manager, + &window_origin, + web_resource_request_handler.as_ref(), + #[cfg(all(dev, mobile))] + (&url, &response_cache), + ) { + Ok(response) => responder.respond(response), + Err(e) => responder.respond( + HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) + .body(e.to_string().as_bytes().to_vec()) + .unwrap(), + ), + } + }) +} + +fn get_response( + request: Request>, + manager: &WindowManager, + window_origin: &str, + web_resource_request_handler: Option<&Box>, + #[cfg(all(dev, mobile))] (url, response_cache): ( + &str, + &Arc>>, + ), +) -> Result>, Box> { + // use the entire URI as we are going to proxy the request + let path = if PROXY_DEV_SERVER { + request.uri().to_string() + } else { + // ignore query string and fragment + request + .uri() + .to_string() + .split(&['?', '#'][..]) + .next() + .unwrap() + .into() + }; + + let path = path + .strip_prefix("tauri://localhost") + .map(|p| p.to_string()) + // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows + // where `$P` is not `localhost/*` + .unwrap_or_else(|| "".to_string()); + + let mut builder = HttpResponse::builder().header("Access-Control-Allow-Origin", window_origin); + + #[cfg(all(dev, mobile))] + let mut response = { + let decoded_path = percent_encoding::percent_decode(path.as_bytes()) + .decode_utf8_lossy() + .to_string(); + let url = format!("{url}{decoded_path}"); + #[allow(unused_mut)] + let mut client_builder = reqwest::ClientBuilder::new(); + #[cfg(any(feature = "native-tls", feature = "rustls-tls"))] + { + client_builder = client_builder.danger_accept_invalid_certs(true); + } + let mut proxy_builder = client_builder + .build() + .unwrap() + .request(request.method().clone(), &url); + for (name, value) in request.headers() { + proxy_builder = proxy_builder.header(name, value); + } + match crate::async_runtime::block_on(proxy_builder.send()) { + Ok(r) => { + let mut response_cache_ = response_cache.lock().unwrap(); + let mut response = None; + if r.status() == http::StatusCode::NOT_MODIFIED { + response = response_cache_.get(&url); + } + let response = if let Some(r) = response { + r + } else { + let status = r.status(); + let headers = r.headers().clone(); + let body = crate::async_runtime::block_on(r.bytes())?; + let response = CachedResponse { + status, + headers, + body, + }; + response_cache_.insert(url.clone(), response); + response_cache_.get(&url).unwrap() + }; + for (name, value) in &response.headers { + builder = builder.header(name, value); + } + builder + .status(response.status) + .body(response.body.to_vec().into())? + } + Err(e) => { + tauri_utils::debug_eprintln!("Failed to request {}: {}", url.as_str(), e); + return Err(Box::new(e)); + } + } + }; + + #[cfg(not(all(dev, mobile)))] + let mut response = { + let asset = manager.get_asset(path)?; + builder = builder.header(CONTENT_TYPE, &asset.mime_type); + if let Some(csp) = &asset.csp_header { + builder = builder.header("Content-Security-Policy", csp); + } + builder.body(asset.bytes.into())? + }; + if let Some(handler) = &web_resource_request_handler { + handler(request, &mut response); + } + // if it's an HTML file, we need to set the CSP meta tag on Linux + #[cfg(all(not(dev), target_os = "linux"))] + if let Some(response_csp) = response.headers().get("Content-Security-Policy") { + let response_csp = String::from_utf8_lossy(response_csp.as_bytes()); + let html = String::from_utf8_lossy(response.body()); + let body = html.replacen(tauri_utils::html::CSP_TOKEN, &response_csp, 1); + *response.body_mut() = body.as_bytes().to_vec(); + } + + Ok(response) +} diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index f7131305fd92..0f1caad7fafa 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -62,11 +62,8 @@ use std::{ pub(crate) type WebResourceRequestHandler = dyn Fn(HttpRequest>, &mut HttpResponse>) + Send + Sync; pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send; -pub(crate) type UriSchemeProtocolHandler = Box< - dyn Fn(HttpRequest>, UriSchemeResponse) -> Result<(), Box> - + Send - + Sync, ->; +pub(crate) type UriSchemeProtocolHandler = + Box>, UriSchemeResponse) + Send + Sync>; #[derive(Clone, Serialize)] struct WindowCreatedEvent { diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 39b074f83309..2d6b9ac3d3d6 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -4531,7 +4531,7 @@ dependencies = [ [[package]] name = "wry" version = "0.31.0" -source = "git+https://github.com/tauri-apps/wry?branch=refactor/custom-protocol-response#52c9a16d847d8c1f33a42705f14644a23a4109e9" +source = "git+https://github.com/tauri-apps/wry?branch=refactor/custom-protocol-response#96349651f29fd9d484be7e0a9248d9b85e93e839" dependencies = [ "base64", "block", diff --git a/examples/streaming/main.rs b/examples/streaming/main.rs index 566f2d8c5ea4..85e1f0ca48dd 100644 --- a/examples/streaming/main.rs +++ b/examples/streaming/main.rs @@ -4,17 +4,16 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode}; +use http_range::HttpRange; use std::sync::{Arc, Mutex}; +use std::{ + io::{Read, Seek, SeekFrom, Write}, + path::PathBuf, + process::{Command, Stdio}, +}; fn main() { - use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode}; - use http_range::HttpRange; - use std::{ - io::{Read, Seek, SeekFrom, Write}, - path::PathBuf, - process::{Command, Stdio}, - }; - let video_file = PathBuf::from("test_video.mp4"); let video_url = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; @@ -42,150 +41,19 @@ fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![video_uri]) - .register_uri_scheme_protocol("stream", move |_app, request, response| { - // get the file path - let path = request.uri().path(); - let path = percent_encoding::percent_decode(path.as_bytes()) - .decode_utf8_lossy() - .to_string(); - - if path != "test_video.mp4" { - // return error 404 if it's not our video - response.respond(ResponseBuilder::new().status(404).body(Vec::new())?); - return Ok(()); - } - - let mut file = std::fs::File::open(&path)?; - - // get file length - let len = { - let old_pos = file.stream_position()?; - let len = file.seek(SeekFrom::End(0))?; - file.seek(SeekFrom::Start(old_pos))?; - len - }; - - let mut resp = ResponseBuilder::new().header(CONTENT_TYPE, "video/mp4"); - - // if the webview sent a range header, we need to send a 206 in return - // Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers. - let http_response = if let Some(range_header) = request.headers().get("range") { - let not_satisfiable = || { + .register_uri_scheme_protocol( + "stream", + move |_app, request, response| match get_stream_response(request, &boundary_id) { + Ok(http_response) => response.respond(http_response), + Err(e) => response.respond( ResponseBuilder::new() - .status(StatusCode::RANGE_NOT_SATISFIABLE) - .header(CONTENT_RANGE, format!("bytes */{len}")) - .body(vec![]) - }; - - // parse range header - let ranges = if let Ok(ranges) = HttpRange::parse(range_header.to_str()?, len) { - ranges - .iter() - // map the output back to spec range , example: 0-499 - .map(|r| (r.start, r.start + r.length - 1)) - .collect::>() - } else { - response.respond(not_satisfiable()?); - return Ok(()); - }; - - /// The Maximum bytes we send in one range - const MAX_LEN: u64 = 1000 * 1024; - - if ranges.len() == 1 { - let &(start, mut end) = ranges.first().unwrap(); - - // check if a range is not satisfiable - // - // this should be already taken care of by HttpRange::parse - // but checking here again for extra assurance - if start >= len || end >= len || end < start { - response.respond(not_satisfiable()?); - return Ok(()); - } - - // adjust end byte for MAX_LEN - end = start + (end - start).min(len - start).min(MAX_LEN - 1); - - // calculate number of bytes needed to be read - let bytes_to_read = end + 1 - start; - - // allocate a buf with a suitable capacity - let mut buf = Vec::with_capacity(bytes_to_read as usize); - // seek the file to the starting byte - file.seek(SeekFrom::Start(start))?; - // read the needed bytes - file.take(bytes_to_read).read_to_end(&mut buf)?; - - resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}")); - resp = resp.header(CONTENT_LENGTH, end + 1 - start); - resp = resp.status(StatusCode::PARTIAL_CONTENT); - resp.body(buf) - } else { - let mut buf = Vec::new(); - let ranges = ranges - .iter() - .filter_map(|&(start, mut end)| { - // filter out unsatisfiable ranges - // - // this should be already taken care of by HttpRange::parse - // but checking here again for extra assurance - if start >= len || end >= len || end < start { - None - } else { - // adjust end byte for MAX_LEN - end = start + (end - start).min(len - start).min(MAX_LEN - 1); - Some((start, end)) - } - }) - .collect::>(); - - let mut id = boundary_id.lock().unwrap(); - *id += 1; - let boundary = format!("sadasq2e{id}"); - let boundary_sep = format!("\r\n--{boundary}\r\n"); - let boundary_closer = format!("\r\n--{boundary}\r\n"); - - resp = resp.header( - CONTENT_TYPE, - format!("multipart/byteranges; boundary={boundary}"), - ); - - for (end, start) in ranges { - // a new range is being written, write the range boundary - buf.write_all(boundary_sep.as_bytes())?; - - // write the needed headers `Content-Type` and `Content-Range` - buf.write_all(format!("{CONTENT_TYPE}: video/mp4\r\n").as_bytes())?; - buf.write_all(format!("{CONTENT_RANGE}: bytes {start}-{end}/{len}\r\n").as_bytes())?; - - // write the separator to indicate the start of the range body - buf.write_all("\r\n".as_bytes())?; - - // calculate number of bytes needed to be read - let bytes_to_read = end + 1 - start; - - let mut local_buf = vec![0_u8; bytes_to_read as usize]; - file.seek(SeekFrom::Start(start))?; - file.read_exact(&mut local_buf)?; - buf.extend_from_slice(&local_buf); - } - // all ranges have been written, write the closing boundary - buf.write_all(boundary_closer.as_bytes())?; - - resp.body(buf) - } - } else { - resp = resp.header(CONTENT_LENGTH, len); - let mut buf = Vec::with_capacity(len as usize); - file.read_to_end(&mut buf)?; - resp.body(buf) - }; - - response.respond(http_response?); - - Ok(()) - }) + .status(StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, "text/plain") + .body(e.to_string().as_bytes().to_vec()) + .unwrap(), + ), + }, + ) .run(tauri::generate_context!( "../../examples/streaming/tauri.conf.json" )) @@ -206,3 +74,146 @@ fn video_uri() -> (&'static str, std::path::PathBuf) { #[cfg(not(feature = "protocol-asset"))] ("stream", "test_video.mp4".into()) } + +fn get_stream_response( + request: http::Request>, + boundary_id: &Arc>, +) -> Result>, Box> { + // get the file path + let path = request.uri().path(); + let path = percent_encoding::percent_decode(path.as_bytes()) + .decode_utf8_lossy() + .to_string(); + + if path != "test_video.mp4" { + // return error 404 if it's not our video + return Ok(ResponseBuilder::new().status(404).body(Vec::new())?); + } + + let mut file = std::fs::File::open(&path)?; + + // get file length + let len = { + let old_pos = file.stream_position()?; + let len = file.seek(SeekFrom::End(0))?; + file.seek(SeekFrom::Start(old_pos))?; + len + }; + + let mut resp = ResponseBuilder::new().header(CONTENT_TYPE, "video/mp4"); + + // if the webview sent a range header, we need to send a 206 in return + // Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers. + let http_response = if let Some(range_header) = request.headers().get("range") { + let not_satisfiable = || { + ResponseBuilder::new() + .status(StatusCode::RANGE_NOT_SATISFIABLE) + .header(CONTENT_RANGE, format!("bytes */{len}")) + .body(vec![]) + }; + + // parse range header + let ranges = if let Ok(ranges) = HttpRange::parse(range_header.to_str()?, len) { + ranges + .iter() + // map the output back to spec range , example: 0-499 + .map(|r| (r.start, r.start + r.length - 1)) + .collect::>() + } else { + return Ok(not_satisfiable()?); + }; + + /// The Maximum bytes we send in one range + const MAX_LEN: u64 = 1000 * 1024; + + if ranges.len() == 1 { + let &(start, mut end) = ranges.first().unwrap(); + + // check if a range is not satisfiable + // + // this should be already taken care of by HttpRange::parse + // but checking here again for extra assurance + if start >= len || end >= len || end < start { + return Ok(not_satisfiable()?); + } + + // adjust end byte for MAX_LEN + end = start + (end - start).min(len - start).min(MAX_LEN - 1); + + // calculate number of bytes needed to be read + let bytes_to_read = end + 1 - start; + + // allocate a buf with a suitable capacity + let mut buf = Vec::with_capacity(bytes_to_read as usize); + // seek the file to the starting byte + file.seek(SeekFrom::Start(start))?; + // read the needed bytes + file.take(bytes_to_read).read_to_end(&mut buf)?; + + resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}")); + resp = resp.header(CONTENT_LENGTH, end + 1 - start); + resp = resp.status(StatusCode::PARTIAL_CONTENT); + resp.body(buf) + } else { + let mut buf = Vec::new(); + let ranges = ranges + .iter() + .filter_map(|&(start, mut end)| { + // filter out unsatisfiable ranges + // + // this should be already taken care of by HttpRange::parse + // but checking here again for extra assurance + if start >= len || end >= len || end < start { + None + } else { + // adjust end byte for MAX_LEN + end = start + (end - start).min(len - start).min(MAX_LEN - 1); + Some((start, end)) + } + }) + .collect::>(); + + let mut id = boundary_id.lock().unwrap(); + *id += 1; + let boundary = format!("sadasq2e{id}"); + let boundary_sep = format!("\r\n--{boundary}\r\n"); + let boundary_closer = format!("\r\n--{boundary}\r\n"); + + resp = resp.header( + CONTENT_TYPE, + format!("multipart/byteranges; boundary={boundary}"), + ); + + for (end, start) in ranges { + // a new range is being written, write the range boundary + buf.write_all(boundary_sep.as_bytes())?; + + // write the needed headers `Content-Type` and `Content-Range` + buf.write_all(format!("{CONTENT_TYPE}: video/mp4\r\n").as_bytes())?; + buf.write_all(format!("{CONTENT_RANGE}: bytes {start}-{end}/{len}\r\n").as_bytes())?; + + // write the separator to indicate the start of the range body + buf.write_all("\r\n".as_bytes())?; + + // calculate number of bytes needed to be read + let bytes_to_read = end + 1 - start; + + let mut local_buf = vec![0_u8; bytes_to_read as usize]; + file.seek(SeekFrom::Start(start))?; + file.read_exact(&mut local_buf)?; + buf.extend_from_slice(&local_buf); + } + // all ranges have been written, write the closing boundary + buf.write_all(boundary_closer.as_bytes())?; + + resp.body(buf) + } + } else { + resp = resp.header(CONTENT_LENGTH, len); + let mut buf = Vec::with_capacity(len as usize); + file.read_to_end(&mut buf)?; + resp.body(buf) + }; + + http_response.map_err(Into::into) +} From d934906b99725a11c3ca73d4770f01b23a4d8107 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 09:47:03 -0300 Subject: [PATCH 12/27] remove sync trait --- core/tauri-runtime/src/window.rs | 4 ++-- core/tauri/src/app.rs | 4 +--- core/tauri/src/ipc/mod.rs | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index bfcdb19c9b39..c5e4fb500575 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -25,7 +25,7 @@ use std::{ use self::dpi::PhysicalPosition; -type UriSchemeProtocol = dyn Fn(HttpRequest>, Box>) + Send + Sync>) +type UriSchemeProtocol = dyn Fn(HttpRequest>, Box>) + Send>) + Send + Sync + 'static; @@ -310,7 +310,7 @@ impl> PendingWindow { pub fn register_uri_scheme_protocol< N: Into, - H: Fn(HttpRequest>, Box>) + Send + Sync>) + H: Fn(HttpRequest>, Box>) + Send>) + Send + Sync + 'static, diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index f3d31183e78c..e61d6975631d 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1577,9 +1577,7 @@ impl Builder { } } -pub struct UriSchemeResponse( - pub(crate) Box>) + Send + Sync>, -); +pub struct UriSchemeResponse(pub(crate) Box>) + Send>); impl UriSchemeResponse { /// Resolves the request with the given response. diff --git a/core/tauri/src/ipc/mod.rs b/core/tauri/src/ipc/mod.rs index 81e25515de6b..2aef4a276089 100644 --- a/core/tauri/src/ipc/mod.rs +++ b/core/tauri/src/ipc/mod.rs @@ -34,7 +34,7 @@ pub type InvokeResponder = dyn Fn(&Window, &str, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static; /// Similar to [`InvokeResponder`] but taking owned arguments. pub type OwnedInvokeResponder = - dyn FnOnce(Window, String, InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static; + dyn FnOnce(Window, String, InvokeResponse, CallbackFn, CallbackFn) + Send + 'static; /// Possible values of an IPC payload. #[derive(Debug, Clone)] From 7359295325b3377b01520cdd0c7c30a51036df8d Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 09:48:30 -0300 Subject: [PATCH 13/27] lint --- core/tauri/src/app.rs | 3 ++- core/tauri/src/protocol/tauri.rs | 4 ++-- examples/api/src-tauri/Cargo.lock | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index e61d6975631d..6dc113c22798 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1577,7 +1577,8 @@ impl Builder { } } -pub struct UriSchemeResponse(pub(crate) Box>) + Send>); +pub(crate) type UriSchemeResponder = Box>) + Send>; +pub struct UriSchemeResponse(pub(crate) UriSchemeResponder); impl UriSchemeResponse { /// Resolves the request with the given response. diff --git a/core/tauri/src/protocol/tauri.rs b/core/tauri/src/protocol/tauri.rs index e9b54619c822..ed3cddcb79c8 100644 --- a/core/tauri/src/protocol/tauri.rs +++ b/core/tauri/src/protocol/tauri.rs @@ -51,7 +51,7 @@ pub fn get( request, &manager, &window_origin, - web_resource_request_handler.as_ref(), + web_resource_request_handler.as_deref(), #[cfg(all(dev, mobile))] (&url, &response_cache), ) { @@ -71,7 +71,7 @@ fn get_response( request: Request>, manager: &WindowManager, window_origin: &str, - web_resource_request_handler: Option<&Box>, + web_resource_request_handler: Option<&WebResourceRequestHandler>, #[cfg(all(dev, mobile))] (url, response_cache): ( &str, &Arc>>, diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 2d6b9ac3d3d6..e2a59fe25d8c 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -4531,7 +4531,7 @@ dependencies = [ [[package]] name = "wry" version = "0.31.0" -source = "git+https://github.com/tauri-apps/wry?branch=refactor/custom-protocol-response#96349651f29fd9d484be7e0a9248d9b85e93e839" +source = "git+https://github.com/tauri-apps/wry?branch=refactor/custom-protocol-response#281525bf2ed7f3ed0521f684e6967d3fd7ec5f62" dependencies = [ "base64", "block", From d446f59a6249619af3d9f0a5e7f0df1951b5c1f1 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 10:53:29 -0300 Subject: [PATCH 14/27] fix linux --- core/tauri/src/protocol/tauri.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/protocol/tauri.rs b/core/tauri/src/protocol/tauri.rs index ed3cddcb79c8..109d8a77debc 100644 --- a/core/tauri/src/protocol/tauri.rs +++ b/core/tauri/src/protocol/tauri.rs @@ -172,7 +172,7 @@ fn get_response( let response_csp = String::from_utf8_lossy(response_csp.as_bytes()); let html = String::from_utf8_lossy(response.body()); let body = html.replacen(tauri_utils::html::CSP_TOKEN, &response_csp, 1); - *response.body_mut() = body.as_bytes().to_vec(); + *response.body_mut() = body.as_bytes().to_vec().into(); } Ok(response) From 4e44125e95dc579f1e9e61e0692accceba96de8f Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 10:54:59 -0300 Subject: [PATCH 15/27] fix ipc error [skip ci] --- core/tauri/scripts/ipc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/scripts/ipc.js b/core/tauri/scripts/ipc.js index 433eba1e5db4..1101e123b2c8 100644 --- a/core/tauri/scripts/ipc.js +++ b/core/tauri/scripts/ipc.js @@ -34,7 +34,7 @@ */ function isIsolationMessage(event) { if (typeof event.data === 'object' && typeof event.data.payload === 'object') { - const keys = Object.keys(event.data.payload) + const keys = Object.keys(event.data.payload || {}) return ( keys.length > 0 && keys.every((key) => key === 'nonce' || key === 'payload') From 5fae60f016557f506bcab7d334381959a7229555 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 10:55:19 -0300 Subject: [PATCH 16/27] update wry --- core/tauri-runtime-wry/Cargo.toml | 2 +- examples/api/src-tauri/Cargo.lock | 2 +- examples/api/src-tauri/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 8f47b6f35f4e..1736f440cce9 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -16,7 +16,7 @@ rust-version = { workspace = true } features = [ "dox" ] [dependencies] -wry = { git = "https://github.com/tauri-apps/wry", branch = "refactor/custom-protocol-response", default-features = false, features = [ "file-drop", "protocol" ] } +wry = { git = "https://github.com/tauri-apps/wry", rev = "4bdf1c366de5708b7626ca63eb39e134869c5bd4", default-features = false, features = [ "file-drop", "protocol" ] } tauri-runtime = { version = "1.0.0-alpha.0", path = "../tauri-runtime" } tauri-utils = { version = "2.0.0-alpha.7", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index e2a59fe25d8c..45f6ca60d68d 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -4531,7 +4531,7 @@ dependencies = [ [[package]] name = "wry" version = "0.31.0" -source = "git+https://github.com/tauri-apps/wry?branch=refactor/custom-protocol-response#281525bf2ed7f3ed0521f684e6967d3fd7ec5f62" +source = "git+https://github.com/tauri-apps/wry?rev=4bdf1c366de5708b7626ca63eb39e134869c5bd4#4bdf1c366de5708b7626ca63eb39e134869c5bd4" dependencies = [ "base64", "block", diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 8af633098e91..9a06be515b09 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -63,8 +63,8 @@ pub fn run_app) + Send + 'static>( #[cfg(desktop)] app.manage(PopupMenu( tauri::menu::MenuBuilder::new(app) - .check("Tauri is awesome!") - .text("Do something") + .check("check", "Tauri is awesome!") + .text("text", "Do something") .copy() .build()?, )); From fd2f58c430e9d126cf6de0189860fd148c423ebf Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 13:10:20 -0300 Subject: [PATCH 17/27] wry 0.32 --- core/tauri-runtime-wry/Cargo.toml | 2 +- examples/api/src-tauri/Cargo.lock | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 1736f440cce9..1266214686d6 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -16,7 +16,7 @@ rust-version = { workspace = true } features = [ "dox" ] [dependencies] -wry = { git = "https://github.com/tauri-apps/wry", rev = "4bdf1c366de5708b7626ca63eb39e134869c5bd4", default-features = false, features = [ "file-drop", "protocol" ] } +wry = { version = "0.32", default-features = false, features = [ "tao", "file-drop", "protocol" ] } tauri-runtime = { version = "1.0.0-alpha.0", path = "../tauri-runtime" } tauri-utils = { version = "2.0.0-alpha.7", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 45f6ca60d68d..4e8e334fd5a5 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -4530,8 +4530,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.31.0" -source = "git+https://github.com/tauri-apps/wry?rev=4bdf1c366de5708b7626ca63eb39e134869c5bd4#4bdf1c366de5708b7626ca63eb39e134869c5bd4" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fc00d1511c9ff5b600a6c6bde254eb39b9fcc5c0369b71a8efd5ff807bf937" dependencies = [ "base64", "block", From e32d5210822ff7fbc268605d09d40f07abaa5f72 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 13:17:50 -0300 Subject: [PATCH 18/27] rename responder --- core/tauri-runtime-wry/src/lib.rs | 9 ++++++--- core/tauri/src/app.rs | 33 ++++++++++++++++++++++++++----- core/tauri/src/manager.rs | 14 ++++++++----- core/tauri/src/window.rs | 4 ++-- examples/streaming/main.rs | 13 ++++++------ 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index de27f03546e8..d1e36a5508da 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -2665,9 +2665,12 @@ fn create_webview( } for (scheme, protocol) in uri_scheme_protocols { - webview_builder = webview_builder - .with_asynchronous_custom_protocol(scheme, move |request, api| { - protocol(request, Box::new(move |response| api.respond(response))) + webview_builder = + webview_builder.with_asynchronous_custom_protocol(scheme, move |request, responder| { + protocol( + request, + Box::new(move |response| responder.respond(response)), + ) }); } diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 6dc113c22798..d4a692a8c11c 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1349,11 +1349,34 @@ impl Builder { /// # Arguments /// /// * `uri_scheme` The URI scheme to register, such as `example`. - /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`. + /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes a request and a responder you use to resolve the request. + /// + /// # Examples + /// ``` + /// tauri::Builder::default() + /// .register_uri_scheme_protocol("app-files", |_app, request, responder| { + /// let path = request.uri().path().trim_start_matches('/'); + /// if let Ok(data) = std::fs::read(path) { + /// responder.respond( + /// http::Response::builder() + /// .body(data) + /// .unwrap() + /// ); + /// } else { + /// responder.respond( + /// http::Response::builder() + /// .status(http::StatusCode::BAD_REQUEST) + /// .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) + /// .body("failed to read file".as_bytes().to_vec()) + /// .unwrap() + /// ); + /// } + /// }); + /// ``` #[must_use] pub fn register_uri_scheme_protocol< N: Into, - H: Fn(&AppHandle, HttpRequest>, UriSchemeResponse) + Send + Sync + 'static, + H: Fn(&AppHandle, HttpRequest>, UriSchemeResponder) + Send + Sync + 'static, >( mut self, uri_scheme: N, @@ -1577,10 +1600,10 @@ impl Builder { } } -pub(crate) type UriSchemeResponder = Box>) + Send>; -pub struct UriSchemeResponse(pub(crate) UriSchemeResponder); +pub(crate) type UriSchemeResponderFn = Box>) + Send>; +pub struct UriSchemeResponder(pub(crate) UriSchemeResponderFn); -impl UriSchemeResponse { +impl UriSchemeResponder { /// Resolves the request with the given response. pub fn respond>>(self, response: HttpResponse) { let (parts, body) = response.into_parts(); diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index c7e1b20ab594..e3cec3de9601 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -31,7 +31,7 @@ use tauri_utils::{ use crate::{ app::{ AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload, - UriSchemeResponse, + UriSchemeResponder, }, event::{assert_event_name_is_valid, Event, EventHandler, Listeners}, ipc::{Invoke, InvokeHandler, InvokeResponder}, @@ -308,7 +308,7 @@ pub struct Asset { pub struct CustomProtocol { /// Handler for protocol #[allow(clippy::type_complexity)] - pub protocol: Box, HttpRequest>, UriSchemeResponse) + Send + Sync>, + pub protocol: Box, HttpRequest>, UriSchemeResponder) + Send + Sync>, } #[default_runtime(crate::Wry, wry)] @@ -600,7 +600,11 @@ impl WindowManager { let protocol = protocol.clone(); let app_handle = Mutex::new(app_handle.clone()); pending.register_uri_scheme_protocol(uri_scheme.clone(), move |p, responder| { - (protocol.protocol)(&app_handle.lock().unwrap(), p, UriSchemeResponse(responder)) + (protocol.protocol)( + &app_handle.lock().unwrap(), + p, + UriSchemeResponder(responder), + ) }); } @@ -626,7 +630,7 @@ impl WindowManager { let protocol = crate::protocol::tauri::get(self, &window_origin, web_resource_request_handler); pending.register_uri_scheme_protocol("tauri", move |request, response| { - protocol(request, UriSchemeResponse(response)) + protocol(request, UriSchemeResponder(response)) }); registered_scheme_protocols.push("tauri".into()); } @@ -634,7 +638,7 @@ impl WindowManager { if !registered_scheme_protocols.contains(&"ipc".into()) { let protocol = crate::ipc::protocol::get(self.clone(), pending.label.clone()); pending.register_uri_scheme_protocol("ipc", move |request, response| { - protocol(request, UriSchemeResponse(response)) + protocol(request, UriSchemeResponder(response)) }); registered_scheme_protocols.push("ipc".into()); } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 0f1caad7fafa..351ce0d0f317 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -11,7 +11,7 @@ use url::Url; #[cfg(target_os = "macos")] use crate::TitleBarStyle; use crate::{ - app::{AppHandle, UriSchemeResponse}, + app::{AppHandle, UriSchemeResponder}, command::{CommandArg, CommandItem}, event::{Event, EventHandler}, ipc::{ @@ -63,7 +63,7 @@ pub(crate) type WebResourceRequestHandler = dyn Fn(HttpRequest>, &mut HttpResponse>) + Send + Sync; pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send; pub(crate) type UriSchemeProtocolHandler = - Box>, UriSchemeResponse) + Send + Sync>; + Box>, UriSchemeResponder) + Send + Sync>; #[derive(Clone, Serialize)] struct WindowCreatedEvent { diff --git a/examples/streaming/main.rs b/examples/streaming/main.rs index 85e1f0ca48dd..eb1880c1b5d5 100644 --- a/examples/streaming/main.rs +++ b/examples/streaming/main.rs @@ -41,19 +41,18 @@ fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![video_uri]) - .register_uri_scheme_protocol( - "stream", - move |_app, request, response| match get_stream_response(request, &boundary_id) { - Ok(http_response) => response.respond(http_response), - Err(e) => response.respond( + .register_uri_scheme_protocol("stream", move |_app, request, responder| { + match get_stream_response(request, &boundary_id) { + Ok(http_response) => responder.respond(http_response), + Err(e) => responder.respond( ResponseBuilder::new() .status(StatusCode::BAD_REQUEST) .header(CONTENT_TYPE, "text/plain") .body(e.to_string().as_bytes().to_vec()) .unwrap(), ), - }, - ) + } + }) .run(tauri::generate_context!( "../../examples/streaming/tauri.conf.json" )) From 630fb4530e32bdc5b171e7cd07af6e5e97d5bfa5 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 13:18:38 -0300 Subject: [PATCH 19/27] lint --- core/tauri/src/ipc/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/tauri/src/ipc/mod.rs b/core/tauri/src/ipc/mod.rs index 2aef4a276089..3d51726d277c 100644 --- a/core/tauri/src/ipc/mod.rs +++ b/core/tauri/src/ipc/mod.rs @@ -21,6 +21,7 @@ use crate::{ }; pub(crate) mod channel; +#[cfg(any(target_os = "macos", not(ipc_custom_protocol)))] pub(crate) mod format_callback; pub(crate) mod protocol; From 0a98f59ac4faa6545fba701665f6fffed1af2b79 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 15:19:45 -0300 Subject: [PATCH 20/27] add separate api --- .changes/custom-protocol-response-refactor.md | 2 +- ...gister_asynchronous_uri_scheme_protocol.md | 5 ++ core/tauri/src/app.rs | 72 +++++++++++++++---- examples/streaming/main.rs | 2 +- 4 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 .changes/register_asynchronous_uri_scheme_protocol.md diff --git a/.changes/custom-protocol-response-refactor.md b/.changes/custom-protocol-response-refactor.md index 2c7dcfed06f9..791a71ba18aa 100644 --- a/.changes/custom-protocol-response-refactor.md +++ b/.changes/custom-protocol-response-refactor.md @@ -2,4 +2,4 @@ "tauri": patch:breaking --- -Changed `Builder::register_uri_scheme_protocol` to take a second parameter to resolve the request instead of returning a response object. +Changed `Builder::register_uri_scheme_protocol` to return a `http::Response` instead of `Result`. To return an error response, manually create a response with status code >= 400. diff --git a/.changes/register_asynchronous_uri_scheme_protocol.md b/.changes/register_asynchronous_uri_scheme_protocol.md new file mode 100644 index 000000000000..68617316f66c --- /dev/null +++ b/.changes/register_asynchronous_uri_scheme_protocol.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:enhance +--- + +Added `Builder::register_asynchronous_uri_scheme_protocol` to allow resolving a custom URI scheme protocol request asynchronously to prevent blocking the main thread. diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index d4a692a8c11c..4a7bd8214f97 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1349,32 +1349,76 @@ impl Builder { /// # Arguments /// /// * `uri_scheme` The URI scheme to register, such as `example`. - /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes a request and a responder you use to resolve the request. + /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes a request and returns a response. /// /// # Examples /// ``` /// tauri::Builder::default() - /// .register_uri_scheme_protocol("app-files", |_app, request, responder| { + /// .register_uri_scheme_protocol("app-files", |_app, request| { /// let path = request.uri().path().trim_start_matches('/'); /// if let Ok(data) = std::fs::read(path) { - /// responder.respond( - /// http::Response::builder() - /// .body(data) - /// .unwrap() - /// ); + /// http::Response::builder() + /// .body(data) + /// .unwrap() /// } else { - /// responder.respond( - /// http::Response::builder() - /// .status(http::StatusCode::BAD_REQUEST) - /// .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) - /// .body("failed to read file".as_bytes().to_vec()) - /// .unwrap() - /// ); + /// http::Response::builder() + /// .status(http::StatusCode::BAD_REQUEST) + /// .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) + /// .body("failed to read file".as_bytes().to_vec()) + /// .unwrap() /// } /// }); /// ``` #[must_use] pub fn register_uri_scheme_protocol< + N: Into, + T: Into>, + H: Fn(&AppHandle, HttpRequest>) -> HttpResponse + Send + Sync + 'static, + >( + mut self, + uri_scheme: N, + protocol: H, + ) -> Self { + self.uri_scheme_protocols.insert( + uri_scheme.into(), + Arc::new(CustomProtocol { + protocol: Box::new(move |app, request, responder| { + responder.respond(protocol(app, request)) + }), + }), + ); + self + } + + /// Similar to [`Self::register_uri_scheme_protocol`] but with an asynchronous responder that allows you + /// to process the request in a separate thread and respond asynchronously. + /// + /// # Examples + /// ``` + /// tauri::Builder::default() + /// .register_asynchronous_uri_scheme_protocol("app-files", |_app, request, responder| { + /// let path = request.uri().path().trim_start_matches('/').to_string(); + /// std::thread::spawn(move || { + /// if let Ok(data) = std::fs::read(path) { + /// responder.respond( + /// http::Response::builder() + /// .body(data) + /// .unwrap() + /// ); + /// } else { + /// responder.respond( + /// http::Response::builder() + /// .status(http::StatusCode::BAD_REQUEST) + /// .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) + /// .body("failed to read file".as_bytes().to_vec()) + /// .unwrap() + /// ); + /// } + /// }); + /// }); + /// ``` + #[must_use] + pub fn register_asynchronous_uri_scheme_protocol< N: Into, H: Fn(&AppHandle, HttpRequest>, UriSchemeResponder) + Send + Sync + 'static, >( diff --git a/examples/streaming/main.rs b/examples/streaming/main.rs index eb1880c1b5d5..e86b235b75f6 100644 --- a/examples/streaming/main.rs +++ b/examples/streaming/main.rs @@ -41,7 +41,7 @@ fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![video_uri]) - .register_uri_scheme_protocol("stream", move |_app, request, responder| { + .register_asynchronous_uri_scheme_protocol("stream", move |_app, request, responder| { match get_stream_response(request, &boundary_id) { Ok(http_response) => responder.respond(http_response), Err(e) => responder.respond( From 8b56e7f984f46c8af0ae91dd91823bb7db106bec Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 15:22:05 -0300 Subject: [PATCH 21/27] add missing change file --- .changes/fix-channel-data-request.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/fix-channel-data-request.md diff --git a/.changes/fix-channel-data-request.md b/.changes/fix-channel-data-request.md new file mode 100644 index 000000000000..632c27f155ff --- /dev/null +++ b/.changes/fix-channel-data-request.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:bug +--- + +Fixes invalid header value type when requesting IPC body through a channel. From fc6a8cab6f2635e4f9df6d6daf57a9f2e85eeca2 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 15:23:40 -0300 Subject: [PATCH 22/27] re-export http --- .changes/http-drop-export.md | 5 ----- core/tauri/src/lib.rs | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 .changes/http-drop-export.md diff --git a/.changes/http-drop-export.md b/.changes/http-drop-export.md deleted file mode 100644 index c4cf7ea12e46..000000000000 --- a/.changes/http-drop-export.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch:breaking ---- - -No longer re-export the `http` crate. diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 6ca1dcec0029..eabcd02d1e22 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -106,6 +106,8 @@ mod state; pub mod tray; pub use tauri_utils as utils; +pub use http; + /// A Tauri [`Runtime`] wrapper around wry. #[cfg(feature = "wry")] #[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))] From c98eb8437985c372fff6ab49d636eeb0b8304d57 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 15:27:41 -0300 Subject: [PATCH 23/27] change asset protocol signature to match others --- core/tauri/src/manager.rs | 19 ++++++------------- core/tauri/src/protocol/asset.rs | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index e3cec3de9601..e7ff6d65235e 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -629,16 +629,16 @@ impl WindowManager { let web_resource_request_handler = pending.web_resource_request_handler.take(); let protocol = crate::protocol::tauri::get(self, &window_origin, web_resource_request_handler); - pending.register_uri_scheme_protocol("tauri", move |request, response| { - protocol(request, UriSchemeResponder(response)) + pending.register_uri_scheme_protocol("tauri", move |request, responder| { + protocol(request, UriSchemeResponder(responder)) }); registered_scheme_protocols.push("tauri".into()); } if !registered_scheme_protocols.contains(&"ipc".into()) { let protocol = crate::ipc::protocol::get(self.clone(), pending.label.clone()); - pending.register_uri_scheme_protocol("ipc", move |request, response| { - protocol(request, UriSchemeResponder(response)) + pending.register_uri_scheme_protocol("ipc", move |request, responder| { + protocol(request, UriSchemeResponder(responder)) }); registered_scheme_protocols.push("ipc".into()); } @@ -646,16 +646,9 @@ impl WindowManager { #[cfg(feature = "protocol-asset")] if !registered_scheme_protocols.contains(&"asset".into()) { let asset_scope = self.state().get::().asset_protocol.clone(); + let protocol = crate::protocol::asset::get(asset_scope.clone(), window_origin.clone()); pending.register_uri_scheme_protocol("asset", move |request, responder| { - match crate::protocol::asset::get(request, asset_scope.clone(), window_origin.clone()) { - Ok(response) => responder(response), - Err(e) => responder( - http::Response::builder() - .status(http::StatusCode::BAD_REQUEST) - .body(e.to_string().as_bytes().to_vec().into()) - .unwrap(), - ), - } + protocol(request, UriSchemeResponder(responder)) }); } diff --git a/core/tauri/src/protocol/asset.rs b/core/tauri/src/protocol/asset.rs index d00be02cdfb2..37ced2382571 100644 --- a/core/tauri/src/protocol/asset.rs +++ b/core/tauri/src/protocol/asset.rs @@ -4,6 +4,7 @@ use crate::path::SafePathBuf; use crate::scope::FsScope; +use crate::window::UriSchemeProtocolHandler; use http::{header::*, status::StatusCode, Request, Response}; use http_range::HttpRange; use rand::RngCore; @@ -13,10 +14,25 @@ use tauri_utils::mime_type::MimeType; use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; -pub fn get( +pub fn get(scope: FsScope, window_origin: String) -> UriSchemeProtocolHandler { + Box::new( + move |request, responder| match get_response(request, &scope, &window_origin) { + Ok(response) => responder.respond(response), + Err(e) => responder.respond( + http::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str()) + .body(e.to_string().as_bytes().to_vec()) + .unwrap(), + ), + }, + ) +} + +fn get_response( request: Request>, - scope: FsScope, - window_origin: String, + scope: &FsScope, + window_origin: &str, ) -> Result>, Box> { let path = percent_encoding::percent_decode(request.uri().path().as_bytes()) .decode_utf8_lossy() @@ -38,7 +54,7 @@ pub fn get( .map_err(Into::into); } - let mut resp = Response::builder().header("Access-Control-Allow-Origin", &window_origin); + let mut resp = Response::builder().header("Access-Control-Allow-Origin", window_origin); let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move { let mut file = File::open(&path).await?; From 6d9c53fea97084a18748a5b48a394402f2412501 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 15:31:58 -0300 Subject: [PATCH 24/27] move isolation protocol --- core/tauri/src/manager.rs | 68 +------------------------- core/tauri/src/protocol/isolation.rs | 72 ++++++++++++++++++++++++++++ core/tauri/src/protocol/mod.rs | 2 + 3 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 core/tauri/src/protocol/isolation.rs diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index e7ff6d65235e..57738953c6f9 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -660,52 +660,9 @@ impl WindowManager { crypto_keys, } = &self.inner.pattern { - use http::header::CONTENT_TYPE; - - let assets = assets.clone(); - let aes_gcm_key = *crypto_keys.aes_gcm().raw(); - + let protocol = crate::protocol::isolation::get(assets.clone(), *crypto_keys.aes_gcm().raw()); pending.register_uri_scheme_protocol(schema, move |request, responder| { - let response = match request_to_path(&request).as_str() { - "index.html" => match assets.get(&"index.html".into()) { - Some(asset) => { - let asset = String::from_utf8_lossy(asset.as_ref()); - let template = tauri_utils::pattern::isolation::IsolationJavascriptRuntime { - runtime_aes_gcm_key: &aes_gcm_key, - process_ipc_message_fn: PROCESS_IPC_MESSAGE_FN, - }; - match template.render(asset.as_ref(), &Default::default()) { - Ok(asset) => http::Response::builder() - .header(CONTENT_TYPE, mime::TEXT_HTML.as_ref()) - .body(asset.into_string().as_bytes().to_vec().into()), - Err(_) => http::Response::builder() - .status(http::StatusCode::INTERNAL_SERVER_ERROR) - .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new().into()), - } - } - - None => http::Response::builder() - .status(http::StatusCode::NOT_FOUND) - .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new().into()), - }, - _ => http::Response::builder() - .status(http::StatusCode::NOT_FOUND) - .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) - .body(Vec::new().into()), - }; - - if let Ok(r) = response { - responder(r); - } else { - responder( - http::Response::builder() - .status(http::StatusCode::BAD_REQUEST) - .body("failed to get response".as_bytes().to_vec().into()) - .unwrap(), - ); - } + protocol(request, UriSchemeResponder(responder)) }); } @@ -1369,27 +1326,6 @@ struct ScaleFactorChanged { size: PhysicalSize, } -#[cfg(feature = "isolation")] -fn request_to_path(request: &http::Request>) -> String { - let path = request - .uri() - .path() - .trim_start_matches('/') - .trim_end_matches('/'); - - let path = percent_encoding::percent_decode(path.as_bytes()) - .decode_utf8_lossy() - .to_string(); - - if path.is_empty() { - // if the url has no path, we should load `index.html` - "index.html".to_string() - } else { - // skip leading `/` - path.chars().skip(1).collect() - } -} - #[cfg(test)] mod tests { use super::replace_with_callback; diff --git a/core/tauri/src/protocol/isolation.rs b/core/tauri/src/protocol/isolation.rs new file mode 100644 index 000000000000..8d9559f77262 --- /dev/null +++ b/core/tauri/src/protocol/isolation.rs @@ -0,0 +1,72 @@ +use http::header::CONTENT_TYPE; +use serialize_to_javascript::Template; +use tauri_utils::assets::{Assets, EmbeddedAssets}; + +use std::sync::Arc; + +use crate::{manager::PROCESS_IPC_MESSAGE_FN, window::UriSchemeProtocolHandler}; + +pub fn get(assets: Arc, aes_gcm_key: [u8; 32]) -> UriSchemeProtocolHandler { + Box::new(move |request, responder| { + let response = match request_to_path(&request).as_str() { + "index.html" => match assets.get(&"index.html".into()) { + Some(asset) => { + let asset = String::from_utf8_lossy(asset.as_ref()); + let template = tauri_utils::pattern::isolation::IsolationJavascriptRuntime { + runtime_aes_gcm_key: &aes_gcm_key, + process_ipc_message_fn: PROCESS_IPC_MESSAGE_FN, + }; + match template.render(asset.as_ref(), &Default::default()) { + Ok(asset) => http::Response::builder() + .header(CONTENT_TYPE, mime::TEXT_HTML.as_ref()) + .body(asset.into_string().as_bytes().to_vec()), + Err(_) => http::Response::builder() + .status(http::StatusCode::INTERNAL_SERVER_ERROR) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(Vec::new()), + } + } + + None => http::Response::builder() + .status(http::StatusCode::NOT_FOUND) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(Vec::new()), + }, + _ => http::Response::builder() + .status(http::StatusCode::NOT_FOUND) + .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref()) + .body(Vec::new()), + }; + + if let Ok(r) = response { + responder.respond(r); + } else { + responder.respond( + http::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body("failed to get response".as_bytes().to_vec()) + .unwrap(), + ); + } + }) +} + +fn request_to_path(request: &http::Request>) -> String { + let path = request + .uri() + .path() + .trim_start_matches('/') + .trim_end_matches('/'); + + let path = percent_encoding::percent_decode(path.as_bytes()) + .decode_utf8_lossy() + .to_string(); + + if path.is_empty() { + // if the url has no path, we should load `index.html` + "index.html".to_string() + } else { + // skip leading `/` + path.chars().skip(1).collect() + } +} diff --git a/core/tauri/src/protocol/mod.rs b/core/tauri/src/protocol/mod.rs index dc9abd8c799c..3f75e4de4990 100644 --- a/core/tauri/src/protocol/mod.rs +++ b/core/tauri/src/protocol/mod.rs @@ -4,4 +4,6 @@ #[cfg(feature = "protocol-asset")] pub mod asset; +#[cfg(feature = "isolation")] +pub mod isolation; pub mod tauri; From 5ab7458ba3d424fe4bb9273b91ff220ef1dc01bb Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 15:32:16 -0300 Subject: [PATCH 25/27] revert lint change --- core/tauri/src/menu/icon.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index 14851fcd35ea..b398499e10ce 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -207,7 +207,7 @@ impl IconMenuItem { /// - **Windows / Linux**: Unsupported. pub fn set_native_icon(&mut self, _icon: Option) -> crate::Result<()> { #[cfg(target_os = "macos")] - return run_main_thread!(self, |#[allow(unused_mut)] mut self_: Self| self_ + return run_main_thread!(self, |self_: Self| self_ .inner .set_native_icon(_icon.map(Into::into))); #[allow(unreachable_code)] From 7c89f5d34cf9022eeea8c240ecf3d48ddf8fd6b4 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 15:33:33 -0300 Subject: [PATCH 26/27] add on_message change file --- .changes/window-on-message-refactor.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/window-on-message-refactor.md diff --git a/.changes/window-on-message-refactor.md b/.changes/window-on-message-refactor.md new file mode 100644 index 000000000000..335e8ff19228 --- /dev/null +++ b/.changes/window-on-message-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:breaking +--- + +Changed `Window::on_message` signature to take a responder closure instead of returning the response object in order to asynchronously process the request. From dbaf98da2c8ab5b91801379fbbd42230a1735fde Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 6 Sep 2023 15:35:13 -0300 Subject: [PATCH 27/27] license header --- core/tauri/src/protocol/isolation.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/tauri/src/protocol/isolation.rs b/core/tauri/src/protocol/isolation.rs index 8d9559f77262..ba3974031095 100644 --- a/core/tauri/src/protocol/isolation.rs +++ b/core/tauri/src/protocol/isolation.rs @@ -1,3 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + use http::header::CONTENT_TYPE; use serialize_to_javascript::Template; use tauri_utils::assets::{Assets, EmbeddedAssets};