Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 29 additions & 22 deletions plugins/http/guest-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* @module
*/

import { invoke } from '@tauri-apps/api/core'
import { Channel, invoke } from '@tauri-apps/api/core'

/**
* Configuration of a proxy that a Client should pass requests to.
Expand Down Expand Up @@ -186,6 +186,27 @@ export async function fetch(
throw new Error(ERROR_REQUEST_CANCELLED)
}

const streamChannel = new Channel<Uint8Array>()

const readableStreamBody = new ReadableStream({
start: (controller) => {
streamChannel.onmessage = (res: Uint8Array) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be an ArrayBuffer iirc, and sometimes in the case where IPC fails to use the borwser fetch, it fallsback to Json serialization so this would be Array<number>

So we need to do some checks here, similar as what was done before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right. I'll update that.

// close early if aborted
if (signal?.aborted) {
controller.error(ERROR_REQUEST_CANCELLED)
return
}

if (!res.length) {
controller.close()
return
}

controller.enqueue(res)
}
}
})

const rid = await invoke<number>('plugin:http|fetch', {
clientConfig: {
method: req.method,
Expand All @@ -196,7 +217,8 @@ export async function fetch(
connectTimeout,
proxy,
danger
}
},
streamChannel
})

const abort = () => invoke('plugin:http|fetch_cancel', { rid })
Expand All @@ -223,30 +245,15 @@ export async function fetch(
status,
statusText,
url,
headers: responseHeaders,
rid: responseRid
headers: responseHeaders
} = await invoke<FetchSendResponse>('plugin:http|fetch_send', {
rid
})

const body = await invoke<ArrayBuffer | number[]>(
'plugin:http|fetch_read_body',
{
rid: responseRid
}
)

const res = new Response(
body instanceof ArrayBuffer && body.byteLength !== 0
? body
: body instanceof Array && body.length > 0
? new Uint8Array(body)
: null,
{
status,
statusText
}
)
const res = new Response(readableStreamBody, {
status,
statusText
})

// url and headers are read only properties
// but seems like we can set them like this
Expand Down
36 changes: 21 additions & 15 deletions plugins/http/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT


use std::{future::Future, pin::Pin, str::FromStr, sync::Arc, time::Duration};

use http::{header, HeaderMap, HeaderName, HeaderValue, Method, StatusCode};
Expand All @@ -10,7 +11,7 @@ use serde::{Deserialize, Serialize};
use tauri::{
async_runtime::Mutex,
command,
ipc::{CommandScope, GlobalScope},
ipc::{Channel, CommandScope, GlobalScope},
Manager, ResourceId, ResourceTable, Runtime, State, Webview,
};
use tokio::sync::oneshot::{channel, Receiver, Sender};
Expand All @@ -22,6 +23,8 @@ use crate::{

const HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);

// reqwest::Response is never read, but might be needed for future use.
#[allow(dead_code)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove, no need to keep it around anymore

struct ReqwestResponse(reqwest::Response);
impl tauri::Resource for ReqwestResponse {}

Expand Down Expand Up @@ -181,6 +184,7 @@ pub async fn fetch<R: Runtime>(
client_config: ClientConfig,
command_scope: CommandScope<Entry>,
global_scope: GlobalScope<Entry>,
stream_channel: Channel<tauri::ipc::InvokeResponseBody>
) -> crate::Result<ResourceId> {
let ClientConfig {
method,
Expand Down Expand Up @@ -314,7 +318,22 @@ pub async fn fetch<R: Runtime>(
#[cfg(feature = "tracing")]
tracing::trace!("{:?}", request);

let fut = async move { request.send().await.map_err(Into::into) };
let fut = async move {
let mut res = request.send().await?;

// send response through IPC channel
while let Some(chunk) = res.chunk().await? {
stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(chunk.to_vec()))?;
}

// send empty vector when done
stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(Vec::new()))?;

// return that response
Ok(res)
};


let mut resources_table = webview.resources_table();
let rid = resources_table.add_request(Box::pin(fut));

Expand Down Expand Up @@ -410,19 +429,6 @@ pub async fn fetch_send<R: Runtime>(
})
}

#[tauri::command]
pub(crate) async fn fetch_read_body<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
) -> crate::Result<tauri::ipc::Response> {
let res = {
let mut resources_table = webview.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()))
}

// forbidden headers per fetch spec https://fetch.spec.whatwg.org/#terminology-headers
#[cfg(not(feature = "unsafe-headers"))]
fn is_unsafe_header(header: &HeaderName) -> bool {
Expand Down
3 changes: 1 addition & 2 deletions plugins/http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.invoke_handler(tauri::generate_handler![
commands::fetch,
commands::fetch_cancel,
commands::fetch_send,
commands::fetch_read_body,
commands::fetch_send
])
.build()
}
Loading