Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
643039c
feat: add stream support
adrieljss Mar 2, 2025
5edea81
feat: add stream support
adrieljss Mar 2, 2025
bdbb3d7
Revert "feat: add stream support"
adrieljss Mar 2, 2025
39639ea
feat: add stream support
adrieljss Mar 2, 2025
44ee279
Discard changes to pnpm-lock.yaml
FabianLars Mar 2, 2025
fd7bd41
Discard changes to plugins/http/package.json
FabianLars Mar 2, 2025
ca3651b
fix(stream): change IPC packet
adrieljss Mar 3, 2025
0a0a428
fix: update stream message guest-js
adrieljss Mar 3, 2025
ba122e6
fix: return early when aborted
adrieljss Mar 3, 2025
0a2645f
fix: use InvokeResponseBody as packet
adrieljss Mar 3, 2025
e747212
fix: remove serde_bytes
adrieljss Mar 3, 2025
9b472c2
fix: remove reqwest response
adrieljss Mar 3, 2025
57eb09b
fix: content conversion bug
adrieljss Mar 3, 2025
acafdd8
fix: remove ReqwestResponses along with its implementations
adrieljss Mar 3, 2025
d75de7b
formatting and update changelog
adrieljss Mar 3, 2025
194fdee
build api-iife.js
adrieljss Mar 3, 2025
86ad81f
fix(http): fixes blocking body fetch function
adrieljss Mar 11, 2025
6a9dde5
fix: remove close after error and inject rid to invoke
adrieljss Mar 11, 2025
daadfd9
build and format
adrieljss Mar 11, 2025
7050193
fix: change permission command
adrieljss Mar 11, 2025
1ec0de8
update permissions
adrieljss Mar 11, 2025
6cf36fb
fix: revert command back to fetch_read_body to comply with permissions
adrieljss Mar 11, 2025
33242bf
fix: revert read_body name permission command
adrieljss Mar 11, 2025
99bd751
feat: add back ReqwestResponse implementation
adrieljss Mar 12, 2025
bfb468d
fix: use responseRid in invoke and build js
adrieljss Mar 12, 2025
73bb96b
cleanup previous permissions
adrieljss Mar 12, 2025
ea012c1
Merge branch 'v2' into v2
adrieljss Mar 13, 2025
bb66d44
fix: add fetch_read_body to lib.rs
adrieljss Mar 14, 2025
17311a8
run linting and build
adrieljss Mar 14, 2025
bb74676
build api-iife.js
adrieljss Mar 14, 2025
344a355
Update http-stream-support.md
amrbashir Mar 15, 2025
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
6 changes: 6 additions & 0 deletions .changes/http-stream-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"http": minor
"http-js": minor
---

Fix HTTP stream not streaming the body.
2 changes: 1 addition & 1 deletion plugins/http/api-iife.js

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

72 changes: 39 additions & 33 deletions plugins/http/guest-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export interface DangerousSettings {
acceptInvalidHostnames?: boolean
}

const ERROR_REQUEST_CANCELLED = 'Request canceled'
const ERROR_REQUEST_CANCELLED = 'Request cancelled'

/**
* Fetch a resource from the network. It returns a `Promise` that resolves to the
Expand Down Expand Up @@ -186,35 +186,6 @@ export async function fetch(
throw new Error(ERROR_REQUEST_CANCELLED)
}

const streamChannel = new Channel<ArrayBuffer | number[]>()

const readableStreamBody = new ReadableStream({
start: (controller) => {
streamChannel.onmessage = (res: ArrayBuffer | number[]) => {
// close early if aborted
if (signal?.aborted) {
controller.error(ERROR_REQUEST_CANCELLED)
controller.close()
return
}

// close when the signal to close (an empty chunk)
// is sent from the IPC.
if (
res instanceof ArrayBuffer ? res.byteLength == 0 : res.length == 0
) {
controller.close()
return
}

// the content conversion (like .text(), .json(), etc.) in Response
// must have Uint8Array as its content, else it will
// have untraceable error that's hard to debug.
controller.enqueue(new Uint8Array(res))
}
}
})

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

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

const readableStreamBody = new ReadableStream({
start: (controller) => {
const streamChannel = new Channel<ArrayBuffer | number[]>()
streamChannel.onmessage = (res: ArrayBuffer | number[]) => {
// close early if aborted
if (signal?.aborted) {
controller.error(ERROR_REQUEST_CANCELLED)
return
}

// close when the signal to close (an empty chunk)
// is sent from the IPC.
if (
res instanceof ArrayBuffer ? res.byteLength == 0 : res.length == 0
) {
controller.close()
return
}

// the content conversion (like .text(), .json(), etc.) in Response
// must have Uint8Array as its content, else it will
// have untraceable error that's hard to debug.
controller.enqueue(new Uint8Array(res))
}

// run a non-blocking body stream fetch
invoke('plugin:http|fetch_read_body', {
rid: responseRid,
streamChannel
}).catch((e) => {
controller.error(e)
})
}
})

const res = new Response(readableStreamBody, {
status,
statusText
Expand Down
48 changes: 32 additions & 16 deletions plugins/http/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ use crate::{

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

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

type CancelableResponseResult = Result<reqwest::Response>;
type CancelableResponseFuture =
Pin<Box<dyn Future<Output = CancelableResponseResult> + Send + Sync>>;
Expand Down Expand Up @@ -178,7 +181,6 @@ 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 @@ -312,20 +314,7 @@ pub async fn fetch<R: Runtime>(
#[cfg(feature = "tracing")]
tracing::trace!("{:?}", request);

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 fut = async move { request.send().await.map_err(Into::into) };

let mut resources_table = webview.resources_table();
let rid = resources_table.add_request(Box::pin(fut));
Expand Down Expand Up @@ -370,7 +359,7 @@ pub fn fetch_cancel<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::
Ok(())
}

#[tauri::command]
#[command]
pub async fn fetch_send<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
Expand Down Expand Up @@ -410,6 +399,9 @@ pub async fn fetch_send<R: Runtime>(
));
}

let mut resources_table = webview.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(),
Expand All @@ -419,6 +411,30 @@ pub async fn fetch_send<R: Runtime>(
})
}

#[command]
pub async fn fetch_read_body<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
stream_channel: Channel<tauri::ipc::InvokeResponseBody>,
) -> crate::Result<()> {
let res = {
let mut resources_table = webview.resources_table();
resources_table.take::<ReqwestResponse>(rid)?
};

let mut res = Arc::into_inner(res).unwrap().0;

// 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()))?;

Ok(())
}

// 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: 2 additions & 1 deletion plugins/http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.invoke_handler(tauri::generate_handler![
commands::fetch,
commands::fetch_cancel,
commands::fetch_send
commands::fetch_send,
commands::fetch_read_body
])
.build()
}
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

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

Loading