Skip to content

Commit

Permalink
refactor: custom protocol (#2503)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
  • Loading branch information
lemarier and lucasfernog committed Aug 23, 2021
1 parent 994b532 commit 539e448
Show file tree
Hide file tree
Showing 32 changed files with 4,262 additions and 177 deletions.
6 changes: 6 additions & 0 deletions .changes/refactor-register-uri-scheme-protocol.md
@@ -0,0 +1,6 @@
---
"tauri": patch
"tauri-runtime": patch
---

**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.
7 changes: 7 additions & 0 deletions .changes/tauri-protocol.md
@@ -0,0 +1,7 @@
---
"tauri": minor
"tauri-runtime": minor
"tauri-runtime-wry": minor
---

Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -93,3 +93,4 @@ __handlers__/

# benches
gh-pages
test_video.mp4
91 changes: 80 additions & 11 deletions core/tauri-runtime-wry/src/lib.rs
Expand Up @@ -5,6 +5,10 @@
//! The [`wry`] Tauri [`Runtime`].

use tauri_runtime::{
http::{
Request as HttpRequest, RequestParts as HttpRequestParts, Response as HttpResponse,
ResponseParts as HttpResponseParts,
},
menu::{CustomMenuItem, Menu, MenuEntry, MenuHash, MenuItem, MenuUpdate, Submenu},
monitor::Monitor,
webview::{
Expand Down Expand Up @@ -54,6 +58,10 @@ use wry::{
monitor::MonitorHandle,
window::{Fullscreen, Icon as WindowIcon, UserAttentionType as WryUserAttentionType},
},
http::{
Request as WryHttpRequest, RequestParts as WryRequestParts, Response as WryHttpResponse,
ResponseParts as WryResponseParts,
},
webview::{
FileDropEvent as WryFileDropEvent, RpcRequest as WryRpcRequest, RpcResponse, WebContext,
WebView, WebViewBuilder,
Expand Down Expand Up @@ -95,9 +103,6 @@ mod system_tray;
#[cfg(feature = "system-tray")]
use system_tray::*;

mod mime_type;
use mime_type::MimeType;

type WebContextStore = Mutex<HashMap<Option<PathBuf>, WebContext>>;
// window
type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
Expand Down Expand Up @@ -152,6 +157,72 @@ struct EventLoopContext {
proxy: EventLoopProxy<Message>,
}

struct HttpRequestPartsWrapper(HttpRequestParts);

impl From<HttpRequestPartsWrapper> for HttpRequestParts {
fn from(parts: HttpRequestPartsWrapper) -> Self {
Self {
method: parts.0.method,
uri: parts.0.uri,
headers: parts.0.headers,
}
}
}

impl From<HttpRequestParts> for HttpRequestPartsWrapper {
fn from(request: HttpRequestParts) -> Self {
Self(HttpRequestParts {
method: request.method,
uri: request.uri,
headers: request.headers,
})
}
}

impl From<WryRequestParts> for HttpRequestPartsWrapper {
fn from(request: WryRequestParts) -> Self {
Self(HttpRequestParts {
method: request.method,
uri: request.uri,
headers: request.headers,
})
}
}

struct HttpRequestWrapper(HttpRequest);

impl From<&WryHttpRequest> for HttpRequestWrapper {
fn from(req: &WryHttpRequest) -> Self {
Self(HttpRequest {
body: req.body.clone(),
head: HttpRequestPartsWrapper::from(req.head.clone()).0,
})
}
}

// response
struct HttpResponsePartsWrapper(WryResponseParts);
impl From<HttpResponseParts> for HttpResponsePartsWrapper {
fn from(response: HttpResponseParts) -> Self {
Self(WryResponseParts {
mimetype: response.mimetype,
status: response.status,
version: response.version,
headers: response.headers,
})
}
}

struct HttpResponseWrapper(WryHttpResponse);
impl From<HttpResponse> for HttpResponseWrapper {
fn from(response: HttpResponse) -> Self {
Self(WryHttpResponse {
body: response.body,
head: HttpResponsePartsWrapper::from(response.head).0,
})
}
}

pub struct MenuItemAttributesWrapper<'a>(pub WryMenuItemAttributes<'a>);

impl<'a> From<&'a CustomMenuItem> for MenuItemAttributesWrapper<'a> {
Expand Down Expand Up @@ -2327,6 +2398,7 @@ fn create_webview(
#[allow(unused_mut)]
let PendingWindow {
webview_attributes,
uri_scheme_protocols,
mut window_builder,
rpc_handler,
file_drop_handler,
Expand Down Expand Up @@ -2375,13 +2447,10 @@ fn create_webview(
handler,
));
}
for (scheme, protocol) in webview_attributes.uri_scheme_protocols {
webview_builder = webview_builder.with_custom_protocol(scheme, move |url| {
protocol(url)
.map(|data| {
let mime_type = MimeType::parse(&data, url);
(data, mime_type)
})
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)
.map_err(|_| wry::Error::InitScriptError)
});
}
Expand Down Expand Up @@ -2409,7 +2478,7 @@ fn create_webview(
.build()
.map_err(|e| Error::CreateWebview(Box::new(e)))?
} else {
let mut context = WebContext::new(webview_attributes.data_directory.clone());
let mut context = WebContext::new(webview_attributes.data_directory);
webview_builder
.with_web_context(&mut context)
.build()
Expand Down
3 changes: 3 additions & 0 deletions core/tauri-runtime/Cargo.toml
Expand Up @@ -27,6 +27,9 @@ serde_json = "1.0"
thiserror = "1.0"
tauri-utils = { version = "1.0.0-beta.3", path = "../tauri-utils" }
uuid = { version = "0.8.2", features = [ "v4" ] }
http = "0.2.4"
http-range = "0.1.4"
infer = "0.4"

[target."cfg(windows)".dependencies]
winapi = "0.3"
Expand Down
Expand Up @@ -7,7 +7,7 @@ use std::fmt;
const MIMETYPE_PLAIN: &str = "text/plain";

/// [Web Compatible MimeTypes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#important_mime_types_for_web_developers)
pub(crate) enum MimeType {
pub enum MimeType {
Css,
Csv,
Html,
Expand All @@ -18,6 +18,7 @@ pub(crate) enum MimeType {
OctetStream,
Rtf,
Svg,
Mp4,
}

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

let mp4: String = MimeType::parse_from_uri("https://example.com/video.mp4").to_string();
assert_eq!(mp4, String::from("video/mp4"));

let custom_scheme = MimeType::parse_from_uri("wry://tauri.studio").to_string();
assert_eq!(custom_scheme, String::from("text/html"));
}
Expand Down
20 changes: 20 additions & 0 deletions core/tauri-runtime/src/http/mod.rs
@@ -0,0 +1,20 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

// custom wry types
mod mime_type;
mod request;
mod response;

pub use self::{
mime_type::MimeType,
request::{Request, RequestParts},
response::{Builder as ResponseBuilder, Response, ResponseParts},
};

// 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;
117 changes: 117 additions & 0 deletions core/tauri-runtime/src/http/request.rs
@@ -0,0 +1,117 @@
// Copyright 2019-2021 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 {
pub head: RequestParts,
pub body: Vec<u8>,
}

/// 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<HeaderValue>,
}

impl Request {
/// Creates a new blank `Request` with the body
#[inline]
pub fn new(body: Vec<u8>) -> Request {
Request {
head: RequestParts::new(),
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<HeaderValue> {
&self.head.headers
}

/// Returns a reference to the associated HTTP body.
#[inline]
pub fn body(&self) -> &Vec<u8> {
&self.body
}

/// Consumes the request returning the head and body RequestParts.
#[inline]
pub fn into_parts(self) -> (RequestParts, Vec<u8>) {
(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()
}
}

0 comments on commit 539e448

Please sign in to comment.