refactor(http): migrate to tauri's new resource table (#834)

* refactor(http): migrate to tauri's new resource table

* fmt

* change file
pull/874/head
Amr Bashir 1 year ago committed by GitHub
parent ea8eadce85
commit ae0cb92438
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
"http": "patch"
---
**Breaking change** Removed `Error::InvalidRequestId` variant and added `Error::Tauri` variant.

@ -113,6 +113,8 @@ export async function fetch(
delete init.proxy;
}
const signal = init?.signal;
const req = new Request(input, init);
const buffer = await req.arrayBuffer();
const reqData = buffer.byteLength ? Array.from(new Uint8Array(buffer)) : null;
@ -129,7 +131,7 @@ export async function fetch(
},
});
req.signal.addEventListener("abort", () => {
signal?.addEventListener("abort", () => {
invoke("plugin:http|fetch_cancel", {
rid,
});
@ -140,17 +142,21 @@ export async function fetch(
statusText: string;
headers: [[string, string]];
url: string;
rid: number;
}
const { status, statusText, url, headers } = await invoke<FetchSendResponse>(
"plugin:http|fetch_send",
{
rid,
},
);
const {
status,
statusText,
url,
headers,
rid: responseRid,
} = await invoke<FetchSendResponse>("plugin:http|fetch_send", {
rid,
});
const body = await invoke<number[]>("plugin:http|fetch_read_body", {
rid,
rid: responseRid,
});
const res = new Response(new Uint8Array(body), {

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";async function t(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}return"function"==typeof SuppressedError&&SuppressedError,e.fetch=async function(e,r){const n=r?.maxRedirections,i=r?.connectTimeout,a=r?.proxy;r&&(delete r.maxRedirections,delete r.connectTimeout,delete r.proxy);const o=new Request(e,r),s=await o.arrayBuffer(),u=s.byteLength?Array.from(new Uint8Array(s)):null,c=await t("plugin:http|fetch",{clientConfig:{method:o.method,url:o.url,headers:Array.from(o.headers.entries()),data:u,maxRedirections:n,connectTimeout:i,proxy:a}});o.signal.addEventListener("abort",(()=>{t("plugin:http|fetch_cancel",{rid:c})}));const{status:_,statusText:d,url:p,headers:f}=await t("plugin:http|fetch_send",{rid:c}),l=await t("plugin:http|fetch_read_body",{rid:c}),h=new Response(new Uint8Array(l),{headers:f,status:_,statusText:d});return Object.defineProperty(h,"url",{value:p}),h},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";async function t(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}return"function"==typeof SuppressedError&&SuppressedError,e.fetch=async function(e,r){const n=r?.maxRedirections,i=r?.connectTimeout,a=r?.proxy;r&&(delete r.maxRedirections,delete r.connectTimeout,delete r.proxy);const o=r?.signal,s=new Request(e,r),u=await s.arrayBuffer(),c=u.byteLength?Array.from(new Uint8Array(u)):null,d=await t("plugin:http|fetch",{clientConfig:{method:s.method,url:s.url,headers:Array.from(s.headers.entries()),data:c,maxRedirections:n,connectTimeout:i,proxy:a}});o?.addEventListener("abort",(()=>{t("plugin:http|fetch_cancel",{rid:d})}));const{status:_,statusText:p,url:f,headers:l,rid:h}=await t("plugin:http|fetch_send",{rid:d}),y=await t("plugin:http|fetch_read_body",{rid:h}),T=new Response(new Uint8Array(y),{headers:l,status:_,statusText:p});return Object.defineProperty(T,"url",{value:f}),T},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})}

@ -2,14 +2,30 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{collections::HashMap, time::Duration};
use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc, time::Duration};
use http::{header, HeaderName, HeaderValue, Method, StatusCode};
use reqwest::{redirect::Policy, NoProxy};
use serde::{Deserialize, Serialize};
use tauri::{command, AppHandle, Runtime};
use tauri::{async_runtime::Mutex, command, AppHandle, Manager, ResourceId, Runtime};
use crate::{Error, FetchRequest, HttpExt, RequestId};
use crate::{Error, HttpExt, Result};
struct ReqwestResponse(reqwest::Response);
type CancelableResponseResult = Result<Result<reqwest::Response>>;
type CancelableResponseFuture =
Pin<Box<dyn Future<Output = CancelableResponseResult> + Send + Sync>>;
struct FetchRequest(Mutex<CancelableResponseFuture>);
impl FetchRequest {
fn new(f: CancelableResponseFuture) -> Self {
Self(Mutex::new(f))
}
}
impl tauri::Resource for FetchRequest {}
impl tauri::Resource for ReqwestResponse {}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
@ -18,6 +34,7 @@ pub struct FetchResponse {
status_text: String,
headers: Vec<(String, String)>,
url: String,
rid: ResourceId,
}
#[derive(Deserialize)]
@ -114,7 +131,7 @@ fn attach_proxy(
pub async fn fetch<R: Runtime>(
app: AppHandle<R>,
client_config: ClientConfig,
) -> crate::Result<RequestId> {
) -> crate::Result<ResourceId> {
let ClientConfig {
method,
url,
@ -183,11 +200,9 @@ pub async fn fetch<R: Runtime>(
request = request.body(data);
}
let http_state = app.http();
let rid = http_state.next_id();
let fut = async move { Ok(request.send().await.map_err(Into::into)) };
let mut request_table = http_state.requests.lock().await;
request_table.insert(rid, FetchRequest::new(Box::pin(fut)));
let mut resources_table = app.resources_table();
let rid = resources_table.add(FetchRequest::new(Box::pin(fut)));
Ok(rid)
} else {
@ -206,11 +221,9 @@ pub async fn fetch<R: Runtime>(
.header(header::CONTENT_TYPE, data_url.mime_type().to_string())
.body(reqwest::Body::from(body))?;
let http_state = app.http();
let rid = http_state.next_id();
let fut = async move { Ok(Ok(reqwest::Response::from(response))) };
let mut request_table = http_state.requests.lock().await;
request_table.insert(rid, FetchRequest::new(Box::pin(fut)));
let mut resources_table = app.resources_table();
let rid = resources_table.add(FetchRequest::new(Box::pin(fut)));
Ok(rid)
}
_ => Err(Error::SchemeNotSupport(scheme.to_string())),
@ -218,24 +231,25 @@ pub async fn fetch<R: Runtime>(
}
#[command]
pub async fn fetch_cancel<R: Runtime>(app: AppHandle<R>, rid: RequestId) -> crate::Result<()> {
let mut request_table = app.http().requests.lock().await;
let req = request_table
.get_mut(&rid)
.ok_or(Error::InvalidRequestId(rid))?;
*req = FetchRequest::new(Box::pin(async { Err(Error::RequestCanceled) }));
pub async fn fetch_cancel<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<()> {
let req = {
let resources_table = app.resources_table();
resources_table.get::<FetchRequest>(rid)?
};
let mut req = req.0.lock().await;
*req = Box::pin(async { Err(Error::RequestCanceled) });
Ok(())
}
#[command]
pub async fn fetch_send<R: Runtime>(
app: AppHandle<R>,
rid: RequestId,
rid: ResourceId,
) -> crate::Result<FetchResponse> {
let mut request_table = app.http().requests.lock().await;
let req = request_table
.remove(&rid)
.ok_or(Error::InvalidRequestId(rid))?;
let req = {
let mut resources_table = app.resources_table();
resources_table.take::<FetchRequest>(rid)?
};
let res = match req.0.lock().await.as_mut().await {
Ok(Ok(res)) => res,
@ -252,25 +266,27 @@ pub async fn fetch_send<R: Runtime>(
));
}
app.http().responses.lock().await.insert(rid, res);
let mut resources_table = app.resources_table();
let rid = resources_table.add(ReqwestResponse(res));
Ok(FetchResponse {
status: status.as_u16(),
status_text: status.canonical_reason().unwrap_or_default().to_string(),
headers,
url,
rid,
})
}
#[command]
pub(crate) async fn fetch_read_body<R: Runtime>(
app: AppHandle<R>,
rid: RequestId,
rid: ResourceId,
) -> crate::Result<tauri::ipc::Response> {
let mut response_table = app.http().responses.lock().await;
let res = response_table
.remove(&rid)
.ok_or(Error::InvalidRequestId(rid))?;
let res = {
let mut resources_table = app.resources_table();
resources_table.take::<ReqwestResponse>(rid)?
};
let res = Arc::into_inner(res).unwrap().0;
Ok(tauri::ipc::Response::new(res.bytes().await?.to_vec()))
}

@ -5,8 +5,6 @@
use serde::{Serialize, Serializer};
use url::Url;
use crate::RequestId;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
@ -39,8 +37,8 @@ pub enum Error {
DataUrlError,
#[error("failed to decode data url into bytes")]
DataUrlDecodeError,
#[error("invalid request id: {0}")]
InvalidRequestId(RequestId),
#[error(transparent)]
Tauri(#[from] tauri::Error),
#[error(transparent)]
Utf8(#[from] std::string::FromUtf8Error),
}

@ -6,12 +6,7 @@
//!
//! Access the HTTP client written in Rust.
use std::sync::atomic::AtomicU32;
use std::{collections::HashMap, future::Future, pin::Pin};
pub use reqwest;
use reqwest::Response;
use tauri::async_runtime::Mutex;
use tauri::{
plugin::{Builder, TauriPlugin},
AppHandle, Manager, Runtime,
@ -25,34 +20,10 @@ mod config;
mod error;
mod scope;
type RequestId = u32;
type CancelableResponseResult = Result<Result<reqwest::Response>>;
type CancelableResponseFuture =
Pin<Box<dyn Future<Output = CancelableResponseResult> + Send + Sync>>;
type RequestTable = HashMap<RequestId, FetchRequest>;
type ResponseTable = HashMap<RequestId, Response>;
struct FetchRequest(Mutex<CancelableResponseFuture>);
impl FetchRequest {
fn new(f: CancelableResponseFuture) -> Self {
Self(Mutex::new(f))
}
}
struct Http<R: Runtime> {
#[allow(dead_code)]
app: AppHandle<R>,
scope: scope::Scope,
current_id: AtomicU32,
requests: Mutex<RequestTable>,
responses: Mutex<ResponseTable>,
}
impl<R: Runtime> Http<R> {
fn next_id(&self) -> RequestId {
self.current_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
}
trait HttpExt<R: Runtime> {
@ -78,9 +49,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
let default_scope = HttpAllowlistScope::default();
app.manage(Http {
app: app.clone(),
current_id: 0.into(),
requests: Default::default(),
responses: Default::default(),
scope: scope::Scope::new(
api.config()
.as_ref()

Loading…
Cancel
Save