diff --git a/.changes/tauri-http-plugin-errror-invalid-rid.md b/.changes/tauri-http-plugin-errror-invalid-rid.md new file mode 100644 index 00000000..1ea21cea --- /dev/null +++ b/.changes/tauri-http-plugin-errror-invalid-rid.md @@ -0,0 +1,5 @@ +--- +"http": "patch" +--- + +**Breaking change** Removed `Error::InvalidRequestId` variant and added `Error::Tauri` variant. diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 17dd5703..240a89e8 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -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( - "plugin:http|fetch_send", - { - rid, - }, - ); + const { + status, + statusText, + url, + headers, + rid: responseRid, + } = await invoke("plugin:http|fetch_send", { + rid, + }); const body = await invoke("plugin:http|fetch_read_body", { - rid, + rid: responseRid, }); const res = new Response(new Uint8Array(body), { diff --git a/plugins/http/src/api-iife.js b/plugins/http/src/api-iife.js index 54f9cd6c..d86fb195 100644 --- a/plugins/http/src/api-iife.js +++ b/plugins/http/src/api-iife.js @@ -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__})} diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index d277dd26..d6bd9cf7 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -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>; +type CancelableResponseFuture = + Pin + Send + Sync>>; + +struct FetchRequest(Mutex); +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( app: AppHandle, client_config: ClientConfig, -) -> crate::Result { +) -> crate::Result { let ClientConfig { method, url, @@ -183,11 +200,9 @@ pub async fn fetch( 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( .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( } #[command] -pub async fn fetch_cancel(app: AppHandle, 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(app: AppHandle, rid: ResourceId) -> crate::Result<()> { + let req = { + let resources_table = app.resources_table(); + resources_table.get::(rid)? + }; + let mut req = req.0.lock().await; + *req = Box::pin(async { Err(Error::RequestCanceled) }); Ok(()) } #[command] pub async fn fetch_send( app: AppHandle, - rid: RequestId, + rid: ResourceId, ) -> crate::Result { - 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::(rid)? + }; let res = match req.0.lock().await.as_mut().await { Ok(Ok(res)) => res, @@ -252,25 +266,27 @@ pub async fn fetch_send( )); } - 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( app: AppHandle, - rid: RequestId, + rid: ResourceId, ) -> crate::Result { - 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::(rid)? + }; + let res = Arc::into_inner(res).unwrap().0; Ok(tauri::ipc::Response::new(res.bytes().await?.to_vec())) } diff --git a/plugins/http/src/error.rs b/plugins/http/src/error.rs index 457b3382..78ff08a2 100644 --- a/plugins/http/src/error.rs +++ b/plugins/http/src/error.rs @@ -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), } diff --git a/plugins/http/src/lib.rs b/plugins/http/src/lib.rs index 7bf9217b..b7a7f428 100644 --- a/plugins/http/src/lib.rs +++ b/plugins/http/src/lib.rs @@ -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>; -type CancelableResponseFuture = - Pin + Send + Sync>>; -type RequestTable = HashMap; -type ResponseTable = HashMap; - -struct FetchRequest(Mutex); -impl FetchRequest { - fn new(f: CancelableResponseFuture) -> Self { - Self(Mutex::new(f)) - } -} - struct Http { #[allow(dead_code)] app: AppHandle, scope: scope::Scope, - current_id: AtomicU32, - requests: Mutex, - responses: Mutex, -} - -impl Http { - fn next_id(&self) -> RequestId { - self.current_id - .fetch_add(1, std::sync::atomic::Ordering::Relaxed) - } } trait HttpExt { @@ -78,9 +49,6 @@ pub fn init() -> TauriPlugin> { 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()