Skip to content

Commit 539e448

Browse files
refactor: custom protocol (#2503)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
1 parent 994b532 commit 539e448

32 files changed

Lines changed: 4262 additions & 177 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri": patch
3+
"tauri-runtime": patch
4+
---
5+
6+
**Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttibutes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure.

.changes/tauri-protocol.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": minor
3+
"tauri-runtime": minor
4+
"tauri-runtime-wry": minor
5+
---
6+
7+
Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,4 @@ __handlers__/
9393

9494
# benches
9595
gh-pages
96+
test_video.mp4

core/tauri-runtime-wry/src/lib.rs

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
//! The [`wry`] Tauri [`Runtime`].
66
77
use tauri_runtime::{
8+
http::{
9+
Request as HttpRequest, RequestParts as HttpRequestParts, Response as HttpResponse,
10+
ResponseParts as HttpResponseParts,
11+
},
812
menu::{CustomMenuItem, Menu, MenuEntry, MenuHash, MenuItem, MenuUpdate, Submenu},
913
monitor::Monitor,
1014
webview::{
@@ -54,6 +58,10 @@ use wry::{
5458
monitor::MonitorHandle,
5559
window::{Fullscreen, Icon as WindowIcon, UserAttentionType as WryUserAttentionType},
5660
},
61+
http::{
62+
Request as WryHttpRequest, RequestParts as WryRequestParts, Response as WryHttpResponse,
63+
ResponseParts as WryResponseParts,
64+
},
5765
webview::{
5866
FileDropEvent as WryFileDropEvent, RpcRequest as WryRpcRequest, RpcResponse, WebContext,
5967
WebView, WebViewBuilder,
@@ -95,9 +103,6 @@ mod system_tray;
95103
#[cfg(feature = "system-tray")]
96104
use system_tray::*;
97105

98-
mod mime_type;
99-
use mime_type::MimeType;
100-
101106
type WebContextStore = Mutex<HashMap<Option<PathBuf>, WebContext>>;
102107
// window
103108
type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
@@ -152,6 +157,72 @@ struct EventLoopContext {
152157
proxy: EventLoopProxy<Message>,
153158
}
154159

160+
struct HttpRequestPartsWrapper(HttpRequestParts);
161+
162+
impl From<HttpRequestPartsWrapper> for HttpRequestParts {
163+
fn from(parts: HttpRequestPartsWrapper) -> Self {
164+
Self {
165+
method: parts.0.method,
166+
uri: parts.0.uri,
167+
headers: parts.0.headers,
168+
}
169+
}
170+
}
171+
172+
impl From<HttpRequestParts> for HttpRequestPartsWrapper {
173+
fn from(request: HttpRequestParts) -> Self {
174+
Self(HttpRequestParts {
175+
method: request.method,
176+
uri: request.uri,
177+
headers: request.headers,
178+
})
179+
}
180+
}
181+
182+
impl From<WryRequestParts> for HttpRequestPartsWrapper {
183+
fn from(request: WryRequestParts) -> Self {
184+
Self(HttpRequestParts {
185+
method: request.method,
186+
uri: request.uri,
187+
headers: request.headers,
188+
})
189+
}
190+
}
191+
192+
struct HttpRequestWrapper(HttpRequest);
193+
194+
impl From<&WryHttpRequest> for HttpRequestWrapper {
195+
fn from(req: &WryHttpRequest) -> Self {
196+
Self(HttpRequest {
197+
body: req.body.clone(),
198+
head: HttpRequestPartsWrapper::from(req.head.clone()).0,
199+
})
200+
}
201+
}
202+
203+
// response
204+
struct HttpResponsePartsWrapper(WryResponseParts);
205+
impl From<HttpResponseParts> for HttpResponsePartsWrapper {
206+
fn from(response: HttpResponseParts) -> Self {
207+
Self(WryResponseParts {
208+
mimetype: response.mimetype,
209+
status: response.status,
210+
version: response.version,
211+
headers: response.headers,
212+
})
213+
}
214+
}
215+
216+
struct HttpResponseWrapper(WryHttpResponse);
217+
impl From<HttpResponse> for HttpResponseWrapper {
218+
fn from(response: HttpResponse) -> Self {
219+
Self(WryHttpResponse {
220+
body: response.body,
221+
head: HttpResponsePartsWrapper::from(response.head).0,
222+
})
223+
}
224+
}
225+
155226
pub struct MenuItemAttributesWrapper<'a>(pub WryMenuItemAttributes<'a>);
156227

157228
impl<'a> From<&'a CustomMenuItem> for MenuItemAttributesWrapper<'a> {
@@ -2327,6 +2398,7 @@ fn create_webview(
23272398
#[allow(unused_mut)]
23282399
let PendingWindow {
23292400
webview_attributes,
2401+
uri_scheme_protocols,
23302402
mut window_builder,
23312403
rpc_handler,
23322404
file_drop_handler,
@@ -2375,13 +2447,10 @@ fn create_webview(
23752447
handler,
23762448
));
23772449
}
2378-
for (scheme, protocol) in webview_attributes.uri_scheme_protocols {
2379-
webview_builder = webview_builder.with_custom_protocol(scheme, move |url| {
2380-
protocol(url)
2381-
.map(|data| {
2382-
let mime_type = MimeType::parse(&data, url);
2383-
(data, mime_type)
2384-
})
2450+
for (scheme, protocol) in uri_scheme_protocols {
2451+
webview_builder = webview_builder.with_custom_protocol(scheme, move |wry_request| {
2452+
protocol(&HttpRequestWrapper::from(wry_request).0)
2453+
.map(|tauri_response| HttpResponseWrapper::from(tauri_response).0)
23852454
.map_err(|_| wry::Error::InitScriptError)
23862455
});
23872456
}
@@ -2409,7 +2478,7 @@ fn create_webview(
24092478
.build()
24102479
.map_err(|e| Error::CreateWebview(Box::new(e)))?
24112480
} else {
2412-
let mut context = WebContext::new(webview_attributes.data_directory.clone());
2481+
let mut context = WebContext::new(webview_attributes.data_directory);
24132482
webview_builder
24142483
.with_web_context(&mut context)
24152484
.build()

core/tauri-runtime/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ serde_json = "1.0"
2727
thiserror = "1.0"
2828
tauri-utils = { version = "1.0.0-beta.3", path = "../tauri-utils" }
2929
uuid = { version = "0.8.2", features = [ "v4" ] }
30+
http = "0.2.4"
31+
http-range = "0.1.4"
32+
infer = "0.4"
3033

3134
[target."cfg(windows)".dependencies]
3235
winapi = "0.3"
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::fmt;
77
const MIMETYPE_PLAIN: &str = "text/plain";
88

99
/// [Web Compatible MimeTypes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#important_mime_types_for_web_developers)
10-
pub(crate) enum MimeType {
10+
pub enum MimeType {
1111
Css,
1212
Csv,
1313
Html,
@@ -18,6 +18,7 @@ pub(crate) enum MimeType {
1818
OctetStream,
1919
Rtf,
2020
Svg,
21+
Mp4,
2122
}
2223

2324
impl std::fmt::Display for MimeType {
@@ -33,6 +34,7 @@ impl std::fmt::Display for MimeType {
3334
MimeType::OctetStream => "application/octet-stream",
3435
MimeType::Rtf => "application/rtf",
3536
MimeType::Svg => "image/svg+xml",
37+
MimeType::Mp4 => "video/mp4",
3638
};
3739
write!(f, "{}", mime)
3840
}
@@ -53,6 +55,7 @@ impl MimeType {
5355
Some("jsonld") => Self::Jsonld,
5456
Some("rtf") => Self::Rtf,
5557
Some("svg") => Self::Svg,
58+
Some("mp4") => Self::Mp4,
5659
// Assume HTML when a TLD is found for eg. `wry:://tauri.studio` | `wry://hello.com`
5760
Some(_) => Self::Html,
5861
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
@@ -118,6 +121,9 @@ mod tests {
118121
let svg: String = MimeType::parse_from_uri("https://example.com/picture.svg").to_string();
119122
assert_eq!(svg, String::from("image/svg+xml"));
120123

124+
let mp4: String = MimeType::parse_from_uri("https://example.com/video.mp4").to_string();
125+
assert_eq!(mp4, String::from("video/mp4"));
126+
121127
let custom_scheme = MimeType::parse_from_uri("wry://tauri.studio").to_string();
122128
assert_eq!(custom_scheme, String::from("text/html"));
123129
}

core/tauri-runtime/src/http/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
// custom wry types
6+
mod mime_type;
7+
mod request;
8+
mod response;
9+
10+
pub use self::{
11+
mime_type::MimeType,
12+
request::{Request, RequestParts},
13+
response::{Builder as ResponseBuilder, Response, ResponseParts},
14+
};
15+
16+
// re-expose default http types
17+
pub use http::{header, method, status, uri::InvalidUri, version, Uri};
18+
19+
// re-export httprange helper as it can be useful and we need it locally
20+
pub use http_range::HttpRange;
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::fmt;
6+
7+
use super::{
8+
header::{HeaderMap, HeaderValue},
9+
method::Method,
10+
};
11+
12+
/// Represents an HTTP request from the WebView.
13+
///
14+
/// An HTTP request consists of a head and a potentially optional body.
15+
///
16+
/// ## Platform-specific
17+
///
18+
/// - **Linux:** Headers are not exposed.
19+
pub struct Request {
20+
pub head: RequestParts,
21+
pub body: Vec<u8>,
22+
}
23+
24+
/// Component parts of an HTTP `Request`
25+
///
26+
/// The HTTP request head consists of a method, uri, and a set of
27+
/// header fields.
28+
#[derive(Clone)]
29+
pub struct RequestParts {
30+
/// The request's method
31+
pub method: Method,
32+
33+
/// The request's URI
34+
pub uri: String,
35+
36+
/// The request's headers
37+
pub headers: HeaderMap<HeaderValue>,
38+
}
39+
40+
impl Request {
41+
/// Creates a new blank `Request` with the body
42+
#[inline]
43+
pub fn new(body: Vec<u8>) -> Request {
44+
Request {
45+
head: RequestParts::new(),
46+
body,
47+
}
48+
}
49+
50+
/// Returns a reference to the associated HTTP method.
51+
#[inline]
52+
pub fn method(&self) -> &Method {
53+
&self.head.method
54+
}
55+
56+
/// Returns a reference to the associated URI.
57+
#[inline]
58+
pub fn uri(&self) -> &str {
59+
&self.head.uri
60+
}
61+
62+
/// Returns a reference to the associated header field map.
63+
#[inline]
64+
pub fn headers(&self) -> &HeaderMap<HeaderValue> {
65+
&self.head.headers
66+
}
67+
68+
/// Returns a reference to the associated HTTP body.
69+
#[inline]
70+
pub fn body(&self) -> &Vec<u8> {
71+
&self.body
72+
}
73+
74+
/// Consumes the request returning the head and body RequestParts.
75+
#[inline]
76+
pub fn into_parts(self) -> (RequestParts, Vec<u8>) {
77+
(self.head, self.body)
78+
}
79+
}
80+
81+
impl Default for Request {
82+
fn default() -> Request {
83+
Request::new(Vec::new())
84+
}
85+
}
86+
87+
impl fmt::Debug for Request {
88+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89+
f.debug_struct("Request")
90+
.field("method", self.method())
91+
.field("uri", &self.uri())
92+
.field("headers", self.headers())
93+
.field("body", self.body())
94+
.finish()
95+
}
96+
}
97+
98+
impl RequestParts {
99+
/// Creates a new default instance of `RequestParts`
100+
fn new() -> RequestParts {
101+
RequestParts {
102+
method: Method::default(),
103+
uri: "".into(),
104+
headers: HeaderMap::default(),
105+
}
106+
}
107+
}
108+
109+
impl fmt::Debug for RequestParts {
110+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111+
f.debug_struct("Parts")
112+
.field("method", &self.method)
113+
.field("uri", &self.uri)
114+
.field("headers", &self.headers)
115+
.finish()
116+
}
117+
}

0 commit comments

Comments
 (0)