body on its own command

pull/428/head
Lucas Nogueira 2 years ago
parent 8778a55116
commit 4bc57234f5
No known key found for this signature in database
GPG Key ID: FFEA6C72E73482F1

@ -12,20 +12,33 @@
const options = { const options = {
method: method || "GET", method: method || "GET",
headers: {},
}; };
if ( let bodyType;
(httpBody.startsWith("{") && httpBody.endsWith("}")) ||
(httpBody.startsWith("[") && httpBody.endsWith("]")) if (method !== "GET") {
) {
options.body = JSON.parse(httpBody);
} else if (httpBody !== "") {
options.body = httpBody; options.body = httpBody;
if (
(httpBody.startsWith("{") && httpBody.endsWith("}")) ||
(httpBody.startsWith("[") && httpBody.endsWith("]"))
) {
options.headers["Content-Type"] = "application/json";
bodyType = "json";
} else if (httpBody !== "") {
bodyType = "text";
}
} }
tauriFetch("http://localhost:3003", options) const response = await tauriFetch("http://localhost:3003", options);
.then(onMessage) const body =
.catch(onMessage); bodyType === "json" ? await response.json() : await response.text();
onMessage({
url: response.url,
status: response.status,
body,
});
} }
/// http form /// http form
@ -46,9 +59,10 @@
: undefined, : undefined,
}); });
result = { result = {
url: response.url,
status: response.status, status: response.status,
headers: JSON.parse(JSON.stringify(response.headers)), headers: JSON.parse(JSON.stringify(response.headers)),
body: response.body, body: await response.text(),
}; };
} }
</script> </script>

@ -30,6 +30,51 @@ declare global {
} }
} }
async function readBody<T>(rid: number, kind: "blob" | "text"): Promise<T> {
return await window.__TAURI_INVOKE__("plugin:http|fetch_read_body", {
rid,
kind
});
}
class TauriResponse extends Response {
_rid: number = 0
blob(): Promise<Blob> {
return readBody<Uint8Array>(this._rid, "blob").then(bytes => new Blob([bytes], { type: this.headers.get("content-type") || "application/octet-stream" }))
}
json(): Promise<any> {
return readBody<string>(this._rid, "text").then(data => {
try {
return JSON.parse(data);
} catch (e) {
if (this.ok && data === "") {
return {};
} else if (this.ok) {
throw Error(
`Failed to parse response \`${data}\` as JSON: ${e}`
);
}
}
})
}
formData(): Promise<FormData> {
return this.json().then((json: Record<string, string | Blob>) => {
const form = new FormData()
for (const [key, value] of Object.entries(json)) {
form.append(key, value)
}
return form
})
}
text(): Promise<string> {
return readBody(this._rid, "text")
}
}
/** /**
* Options to configure the Rust client used to make fetch requests * Options to configure the Rust client used to make fetch requests
* *
@ -62,7 +107,7 @@ export interface ClientOptions {
export async function fetch( export async function fetch(
input: URL | Request | string, input: URL | Request | string,
init?: RequestInit & ClientOptions init?: RequestInit & ClientOptions
): Promise<Response> { ): Promise<TauriResponse> {
const maxRedirections = init?.maxRedirections; const maxRedirections = init?.maxRedirections;
const connectTimeout = init?.maxRedirections; const connectTimeout = init?.maxRedirections;
@ -96,22 +141,21 @@ export async function fetch(
status: number; status: number;
statusText: string; statusText: string;
headers: [[string, string]]; headers: [[string, string]];
data: number[];
url: string; url: string;
} }
const { status, statusText, url, headers, data } = const { status, statusText, url, headers } =
await window.__TAURI_INVOKE__<FetchSendResponse>("plugin:http|fetch_send", { await window.__TAURI_INVOKE__<FetchSendResponse>("plugin:http|fetch_send", {
rid, rid,
}); });
console.log(status, url, headers, data)
const res = new Response(Uint8Array.from(data), { const res = new TauriResponse(null, {
headers, headers,
status, status,
statusText, statusText,
}); });
res._rid = rid;
// url is read only but seems like we can do this
Object.defineProperty(res, "url", { value: url }); Object.defineProperty(res, "url", { value: url });
return res; return res;

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_HTTP__=function(e){"use strict";return e.fetch=async function(e,t){const n=null==t?void 0:t.maxRedirections,r=null==t?void 0:t.maxRedirections;t&&(delete t.maxRedirections,delete t.connectTimeout);const i=new Request(e,t),a=await i.arrayBuffer(),_=a.byteLength?Array.from(new Uint8Array(a)):null,o=await window.__TAURI_INVOKE__("plugin:http|fetch",{cmd:"fetch",method:i.method,url:i.url,headers:Array.from(i.headers.entries()),data:_,maxRedirections:n,connectTimeout:r});i.signal.addEventListener("abort",(()=>{window.__TAURI_INVOKE__("plugin:http|fetch_cancel",{rid:o})}));const{status:s,statusText:d,url:c,headers:u,data:l}=await window.__TAURI_INVOKE__("plugin:http|fetch_send",{rid:o});console.log(s,c,u,l);const h=new Response(Uint8Array.from(l),{headers:u,status:s,statusText:d});return Object.defineProperty(h,"url",{value:c}),h},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_HTTP__})} if("__TAURI__"in window){var __TAURI_HTTP__=function(t){"use strict";async function e(t,e){return await window.__TAURI_INVOKE__("plugin:http|fetch_read_body",{rid:t,kind:e})}class r extends Response{constructor(){super(...arguments),this._rid=0}blob(){return e(this._rid,"blob").then((t=>new Blob([t],{type:this.headers.get("content-type")||"application/octet-stream"})))}json(){return e(this._rid,"text").then((t=>{try{return JSON.parse(t)}catch(e){if(this.ok&&""===t)return{};if(this.ok)throw Error(`Failed to parse response \`${t}\` as JSON: ${e}`)}}))}formData(){return this.json().then((t=>{const e=new FormData;for(const[r,n]of Object.entries(t))e.append(r,n);return e}))}text(){return e(this._rid,"text")}}return t.fetch=async function(t,e){const n=null==e?void 0:e.maxRedirections,i=null==e?void 0:e.maxRedirections;e&&(delete e.maxRedirections,delete e.connectTimeout);const s=new Request(t,e),o=await s.arrayBuffer(),a=o.byteLength?Array.from(new Uint8Array(o)):null,_=await window.__TAURI_INVOKE__("plugin:http|fetch",{cmd:"fetch",method:s.method,url:s.url,headers:Array.from(s.headers.entries()),data:a,maxRedirections:n,connectTimeout:i});s.signal.addEventListener("abort",(()=>{window.__TAURI_INVOKE__("plugin:http|fetch_cancel",{rid:_})}));const{status:d,statusText:c,url:u,headers:h}=await window.__TAURI_INVOKE__("plugin:http|fetch_send",{rid:_}),l=new r(null,{headers:h,status:d,statusText:c});return l._rid=_,Object.defineProperty(l,"url",{value:u}),l},t}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_HTTP__})}

@ -2,16 +2,60 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use std::{collections::HashMap, time::Duration}; use std::{collections::HashMap, str::FromStr, time::Duration};
use http::{header, HeaderName, HeaderValue, Method, StatusCode}; use http::{header, HeaderName, HeaderValue, Method, StatusCode};
use reqwest::redirect::Policy; use reqwest::redirect::Policy;
use serde::{de::Deserializer, Deserialize, Serialize};
use tauri::{command, AppHandle, Runtime}; use tauri::{command, AppHandle, Runtime};
use crate::{Error, FetchRequest, FetchResponse, HttpExt, RequestId}; use crate::{Error, FetchRequest, HttpExt, RequestId};
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchResponse {
status: u16,
status_text: String,
headers: Vec<(String, String)>,
url: String,
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum ResponseBody {
Blob(Vec<u8>),
Text(String),
}
pub enum BodyKind {
Blob,
Text,
}
impl FromStr for BodyKind {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"blob" => Ok(Self::Blob),
"text" => Ok(Self::Text),
_ => Err("unknown body kind"),
}
}
}
impl<'de> Deserialize<'de> for BodyKind {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let kind = String::deserialize(deserializer)?;
kind.parse().map_err(serde::de::Error::custom)
}
}
#[command] #[command]
pub(crate) async fn fetch<R: Runtime>( pub async fn fetch<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
method: String, method: String,
url: url::Url, url: url::Url,
@ -109,10 +153,7 @@ pub(crate) async fn fetch<R: Runtime>(
} }
#[command] #[command]
pub(crate) async fn fetch_cancel<R: Runtime>( pub async fn fetch_cancel<R: Runtime>(app: AppHandle<R>, rid: RequestId) -> crate::Result<()> {
app: AppHandle<R>,
rid: RequestId,
) -> crate::Result<()> {
let mut request_table = app.http().requests.lock().await; let mut request_table = app.http().requests.lock().await;
let req = request_table let req = request_table
.get_mut(&rid) .get_mut(&rid)
@ -122,7 +163,7 @@ pub(crate) async fn fetch_cancel<R: Runtime>(
} }
#[command] #[command]
pub(crate) async fn fetch_send<R: Runtime>( pub async fn fetch_send<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
rid: RequestId, rid: RequestId,
) -> crate::Result<FetchResponse> { ) -> crate::Result<FetchResponse> {
@ -146,11 +187,30 @@ pub(crate) async fn fetch_send<R: Runtime>(
)); ));
} }
app.http().responses.lock().await.insert(rid, res);
Ok(FetchResponse { Ok(FetchResponse {
status: status.as_u16(), status: status.as_u16(),
status_text: status.canonical_reason().unwrap_or_default().to_string(), status_text: status.canonical_reason().unwrap_or_default().to_string(),
headers, headers,
url, url,
data: res.bytes().await?.to_vec(),
}) })
} }
// TODO: change return value to tauri::ipc::Response on next alpha
#[command]
pub(crate) async fn fetch_read_body<R: Runtime>(
app: AppHandle<R>,
rid: RequestId,
kind: BodyKind,
) -> crate::Result<ResponseBody> {
let mut response_table = app.http().responses.lock().await;
let res = response_table
.remove(&rid)
.ok_or(Error::InvalidRequestId(rid))?;
match kind {
BodyKind::Blob => Ok(ResponseBody::Blob(res.bytes().await?.to_vec())),
BodyKind::Text => Ok(ResponseBody::Text(res.text().await?)),
}
}

@ -10,7 +10,7 @@ use std::sync::atomic::AtomicU32;
use std::{collections::HashMap, future::Future, pin::Pin}; use std::{collections::HashMap, future::Future, pin::Pin};
pub use reqwest; pub use reqwest;
use serde::Serialize; use reqwest::Response;
use tauri::async_runtime::Mutex; use tauri::async_runtime::Mutex;
use tauri::{ use tauri::{
plugin::{Builder, TauriPlugin}, plugin::{Builder, TauriPlugin},
@ -30,6 +30,7 @@ type CancelableResponseResult = Result<Result<reqwest::Response>>;
type CancelableResponseFuture = type CancelableResponseFuture =
Pin<Box<dyn Future<Output = CancelableResponseResult> + Send + Sync>>; Pin<Box<dyn Future<Output = CancelableResponseResult> + Send + Sync>>;
type RequestTable = HashMap<RequestId, FetchRequest>; type RequestTable = HashMap<RequestId, FetchRequest>;
type ResponseTable = HashMap<RequestId, Response>;
struct FetchRequest(Mutex<CancelableResponseFuture>); struct FetchRequest(Mutex<CancelableResponseFuture>);
impl FetchRequest { impl FetchRequest {
@ -38,22 +39,13 @@ impl FetchRequest {
} }
} }
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct FetchResponse {
status: u16,
status_text: String,
headers: Vec<(String, String)>,
url: String,
data: Vec<u8>,
}
struct Http<R: Runtime> { struct Http<R: Runtime> {
#[allow(dead_code)] #[allow(dead_code)]
app: AppHandle<R>, app: AppHandle<R>,
scope: scope::Scope, scope: scope::Scope,
current_id: AtomicU32, current_id: AtomicU32,
requests: Mutex<RequestTable>, requests: Mutex<RequestTable>,
responses: Mutex<ResponseTable>,
} }
impl<R: Runtime> Http<R> { impl<R: Runtime> Http<R> {
@ -80,6 +72,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
commands::fetch, commands::fetch,
commands::fetch_cancel, commands::fetch_cancel,
commands::fetch_send, commands::fetch_send,
commands::fetch_read_body,
]) ])
.setup(|app, api| { .setup(|app, api| {
let default_scope = HttpAllowlistScope::default(); let default_scope = HttpAllowlistScope::default();
@ -87,6 +80,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
app: app.clone(), app: app.clone(),
current_id: 0.into(), current_id: 0.into(),
requests: Default::default(), requests: Default::default(),
responses: Default::default(),
scope: scope::Scope::new( scope: scope::Scope::new(
api.config() api.config()
.as_ref() .as_ref()

Loading…
Cancel
Save