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]]
name = "tauri-plugin-http"
version = "2.4.0"
version = "2.3.0"
dependencies = [
"data-url",
"http",

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

@ -26,7 +26,7 @@
* @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.
@ -106,20 +106,6 @@ export interface DangerousSettings {
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'
/**
@ -200,19 +186,6 @@ export async function fetch(
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', {
clientConfig: {
method: req.method,
@ -223,8 +196,7 @@ export async function fetch(
connectTimeout,
proxy,
danger
},
streamChannel
}
})
const abort = () => invoke('plugin:http|fetch_cancel', { rid })
@ -251,15 +223,30 @@ export async function fetch(
status,
statusText,
url,
headers: responseHeaders
headers: responseHeaders,
rid: responseRid
} = await invoke<FetchSendResponse>('plugin:http|fetch_send', {
rid
})
const res = new Response(readableStreamBody, {
status,
statusText
})
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
}
)
// url and headers are read only properties
// but seems like we can set them like this

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

@ -2,7 +2,6 @@
// 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};
@ -11,7 +10,7 @@ use serde::{Deserialize, Serialize};
use tauri::{
async_runtime::Mutex,
command,
ipc::{Channel, CommandScope, GlobalScope},
ipc::{CommandScope, GlobalScope},
Manager, ResourceId, ResourceTable, Runtime, State, Webview,
};
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"),);
// reqwest::Response is never read, but might be needed for future use.
#[allow(dead_code)]
struct ReqwestResponse(reqwest::Response);
impl tauri::Resource for ReqwestResponse {}
@ -129,12 +126,6 @@ pub struct BasicAuth {
password: String,
}
#[derive(Clone, Serialize)]
pub struct StreamMessage {
value: Option<Vec<u8>>,
done: bool,
}
#[inline]
fn proxy_creator(
url_or_config: UrlOrConfig,
@ -190,7 +181,6 @@ pub async fn fetch<R: Runtime>(
client_config: ClientConfig,
command_scope: CommandScope<Entry>,
global_scope: GlobalScope<Entry>,
stream_channel: Channel<StreamMessage>
) -> crate::Result<ResourceId> {
let ClientConfig {
method,
@ -324,24 +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(StreamMessage{
value: Some(chunk.to_vec()),
done: false,
})?;
}
stream_channel.send(StreamMessage { value: None, done: true })?;
// 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));
@ -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
#[cfg(not(feature = "unsafe-headers"))]
fn is_unsafe_header(header: &HeaderName) -> bool {

@ -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()
}

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

Loading…
Cancel
Save