feat: add stream support

pull/2479/head
adrieljss 3 months ago
parent 643039c17e
commit 5edea81680
No known key found for this signature in database
GPG Key ID: 849F13CBD0B4AD05

2
Cargo.lock generated

@ -6659,7 +6659,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.4.0" version = "2.3.0"
dependencies = [ dependencies = [
"data-url", "data-url",
"http", "http",

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.4.0" version = "2.3.0"
description = "Access an HTTP client written in Rust." description = "Access an HTTP client written in Rust."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }

@ -26,7 +26,7 @@
* @module * @module
*/ */
import { Channel, invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
/** /**
* Configuration of a proxy that a Client should pass requests to. * Configuration of a proxy that a Client should pass requests to.
@ -106,20 +106,6 @@ export interface DangerousSettings {
acceptInvalidHostnames?: boolean acceptInvalidHostnames?: boolean
} }
/**
* Stream Packet for IPC
*/
export interface StreamMessage {
/**
* The chunk - an array of bytes sent from Rust.
*/
value?: ArrayBuffer | number[]
/**
* Is the stream done.
*/
done: boolean
}
const ERROR_REQUEST_CANCELLED = 'Request canceled' const ERROR_REQUEST_CANCELLED = 'Request canceled'
/** /**
@ -200,19 +186,6 @@ export async function fetch(
throw new Error(ERROR_REQUEST_CANCELLED) throw new Error(ERROR_REQUEST_CANCELLED)
} }
const streamChannel = new Channel<StreamMessage>()
const readableStreamBody = new ReadableStream({
start: (controller) => {
streamChannel.onmessage = (res: StreamMessage) => {
// close early if aborted
if (signal?.aborted) controller.error(ERROR_REQUEST_CANCELLED)
if (res.done) controller.close()
controller.enqueue(res.value)
}
}
})
const rid = await invoke<number>('plugin:http|fetch', { const rid = await invoke<number>('plugin:http|fetch', {
clientConfig: { clientConfig: {
method: req.method, method: req.method,
@ -223,8 +196,7 @@ export async function fetch(
connectTimeout, connectTimeout,
proxy, proxy,
danger danger
}, }
streamChannel
}) })
const abort = () => invoke('plugin:http|fetch_cancel', { rid }) const abort = () => invoke('plugin:http|fetch_cancel', { rid })
@ -251,15 +223,30 @@ export async function fetch(
status, status,
statusText, statusText,
url, url,
headers: responseHeaders headers: responseHeaders,
rid: responseRid
} = await invoke<FetchSendResponse>('plugin:http|fetch_send', { } = await invoke<FetchSendResponse>('plugin:http|fetch_send', {
rid rid
}) })
const res = new Response(readableStreamBody, { const body = await invoke<ArrayBuffer | number[]>(
status, 'plugin:http|fetch_read_body',
statusText {
}) 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
}
)
// url and headers are read only properties // url and headers are read only properties
// but seems like we can set them like this // but seems like we can set them like this

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-http", "name": "@tauri-apps/plugin-http",
"version": "2.4.0", "version": "2.3.0",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@ -24,7 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.0.0", "@tauri-apps/api": "^2.0.0"
"@tauri-apps/plugin-http": "link:"
} }
} }

@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use std::{future::Future, pin::Pin, str::FromStr, sync::Arc, time::Duration}; use std::{future::Future, pin::Pin, str::FromStr, sync::Arc, time::Duration};
use http::{header, HeaderMap, HeaderName, HeaderValue, Method, StatusCode}; use http::{header, HeaderMap, HeaderName, HeaderValue, Method, StatusCode};
@ -11,7 +10,7 @@ use serde::{Deserialize, Serialize};
use tauri::{ use tauri::{
async_runtime::Mutex, async_runtime::Mutex,
command, command,
ipc::{Channel, CommandScope, GlobalScope}, ipc::{CommandScope, GlobalScope},
Manager, ResourceId, ResourceTable, Runtime, State, Webview, Manager, ResourceId, ResourceTable, Runtime, State, Webview,
}; };
use tokio::sync::oneshot::{channel, Receiver, Sender}; use tokio::sync::oneshot::{channel, Receiver, Sender};
@ -23,8 +22,6 @@ use crate::{
const HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); 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)]
struct ReqwestResponse(reqwest::Response); struct ReqwestResponse(reqwest::Response);
impl tauri::Resource for ReqwestResponse {} impl tauri::Resource for ReqwestResponse {}
@ -129,12 +126,6 @@ pub struct BasicAuth {
password: String, password: String,
} }
#[derive(Clone, Serialize)]
pub struct StreamMessage {
value: Option<Vec<u8>>,
done: bool,
}
#[inline] #[inline]
fn proxy_creator( fn proxy_creator(
url_or_config: UrlOrConfig, url_or_config: UrlOrConfig,
@ -190,7 +181,6 @@ pub async fn fetch<R: Runtime>(
client_config: ClientConfig, client_config: ClientConfig,
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
global_scope: GlobalScope<Entry>, global_scope: GlobalScope<Entry>,
stream_channel: Channel<StreamMessage>
) -> crate::Result<ResourceId> { ) -> crate::Result<ResourceId> {
let ClientConfig { let ClientConfig {
method, method,
@ -324,24 +314,7 @@ pub async fn fetch<R: Runtime>(
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
tracing::trace!("{:?}", request); tracing::trace!("{:?}", request);
let fut = async move { let fut = async move { request.send().await.map_err(Into::into) };
let mut res = request.send().await?;
// send response through IPC channel
while let Some(chunk) = res.chunk().await? {
stream_channel.send(StreamMessage{
value: Some(chunk.to_vec()),
done: false,
})?;
}
stream_channel.send(StreamMessage { value: None, done: true })?;
// return that response
Ok(res)
};
let mut resources_table = webview.resources_table(); let mut resources_table = webview.resources_table();
let rid = resources_table.add_request(Box::pin(fut)); let rid = resources_table.add_request(Box::pin(fut));
@ -437,6 +410,19 @@ 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 // forbidden headers per fetch spec https://fetch.spec.whatwg.org/#terminology-headers
#[cfg(not(feature = "unsafe-headers"))] #[cfg(not(feature = "unsafe-headers"))]
fn is_unsafe_header(header: &HeaderName) -> bool { fn is_unsafe_header(header: &HeaderName) -> bool {

@ -36,7 +36,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
commands::fetch, commands::fetch,
commands::fetch_cancel, commands::fetch_cancel,
commands::fetch_send commands::fetch_send,
commands::fetch_read_body,
]) ])
.build() .build()
} }

@ -229,9 +229,6 @@ importers:
'@tauri-apps/api': '@tauri-apps/api':
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.3.0 version: 2.3.0
'@tauri-apps/plugin-http':
specifier: 'link:'
version: 'link:'
plugins/log: plugins/log:
dependencies: dependencies:
@ -2286,9 +2283,9 @@ snapshots:
- encoding - encoding
- mocha - mocha
'@covector/assemble@0.12.0(mocha@10.8.2)': '@covector/assemble@0.12.0':
dependencies: dependencies:
'@covector/command': 0.8.0(mocha@10.8.2) '@covector/command': 0.8.0
'@covector/files': 0.8.0 '@covector/files': 0.8.0
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
js-yaml: 4.1.0 js-yaml: 4.1.0
@ -2299,10 +2296,9 @@ snapshots:
unified: 9.2.2 unified: 9.2.2
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- mocha
- supports-color - supports-color
'@covector/changelog@0.12.0(mocha@10.8.2)': '@covector/changelog@0.12.0':
dependencies: dependencies:
'@covector/files': 0.8.0 '@covector/files': 0.8.0
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
@ -2312,16 +2308,14 @@ snapshots:
unified: 9.2.2 unified: 9.2.2
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- mocha
- supports-color - supports-color
'@covector/command@0.8.0(mocha@10.8.2)': '@covector/command@0.8.0':
dependencies: dependencies:
'@effection/process': 2.1.4(mocha@10.8.2) '@effection/process': 2.1.4
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- mocha
'@covector/files@0.8.0': '@covector/files@0.8.0':
dependencies: dependencies:
@ -2368,8 +2362,10 @@ snapshots:
dependencies: dependencies:
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
mocha: 10.8.2 mocha: 10.8.2
transitivePeerDependencies:
- encoding
'@effection/process@2.1.4(mocha@10.8.2)': '@effection/process@2.1.4':
dependencies: dependencies:
cross-spawn: 7.0.6 cross-spawn: 7.0.6
ctrlc-windows: 2.2.0 ctrlc-windows: 2.2.0
@ -2377,7 +2373,6 @@ snapshots:
shellwords: 0.1.1 shellwords: 0.1.1
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- mocha
'@effection/stream@2.0.6': '@effection/stream@2.0.6':
dependencies: dependencies:
@ -3167,9 +3162,9 @@ snapshots:
dependencies: dependencies:
'@clack/prompts': 0.7.0 '@clack/prompts': 0.7.0
'@covector/apply': 0.10.0(mocha@10.8.2) '@covector/apply': 0.10.0(mocha@10.8.2)
'@covector/assemble': 0.12.0(mocha@10.8.2) '@covector/assemble': 0.12.0
'@covector/changelog': 0.12.0(mocha@10.8.2) '@covector/changelog': 0.12.0
'@covector/command': 0.8.0(mocha@10.8.2) '@covector/command': 0.8.0
'@covector/files': 0.8.0 '@covector/files': 0.8.0
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
globby: 11.1.0 globby: 11.1.0

Loading…
Cancel
Save