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 = {
method: method || "GET",
headers: {},
};
if (
(httpBody.startsWith("{") && httpBody.endsWith("}")) ||
(httpBody.startsWith("[") && httpBody.endsWith("]"))
) {
options.body = JSON.parse(httpBody);
} else if (httpBody !== "") {
let bodyType;
if (method !== "GET") {
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)
.then(onMessage)
.catch(onMessage);
const response = await tauriFetch("http://localhost:3003", options);
const body =
bodyType === "json" ? await response.json() : await response.text();
onMessage({
url: response.url,
status: response.status,
body,
});
}
/// http form
@ -46,9 +59,10 @@
: undefined,
});
result = {
url: response.url,
status: response.status,
headers: JSON.parse(JSON.stringify(response.headers)),
body: response.body,
body: await response.text(),
};
}
</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
*
@ -62,7 +107,7 @@ export interface ClientOptions {
export async function fetch(
input: URL | Request | string,
init?: RequestInit & ClientOptions
): Promise<Response> {
): Promise<TauriResponse> {
const maxRedirections = init?.maxRedirections;
const connectTimeout = init?.maxRedirections;
@ -96,22 +141,21 @@ export async function fetch(
status: number;
statusText: string;
headers: [[string, string]];
data: number[];
url: string;
}
const { status, statusText, url, headers, data } =
const { status, statusText, url, headers } =
await window.__TAURI_INVOKE__<FetchSendResponse>("plugin:http|fetch_send", {
rid,
});
console.log(status, url, headers, data)
const res = new Response(Uint8Array.from(data), {
const res = new TauriResponse(null, {
headers,
status,
statusText,
});
res._rid = rid;
// url is read only but seems like we can do this
Object.defineProperty(res, "url", { value: url });
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: MIT
use std::{collections::HashMap, time::Duration};
use std::{collections::HashMap, str::FromStr, time::Duration};
use http::{header, HeaderName, HeaderValue, Method, StatusCode};
use reqwest::redirect::Policy;
use serde::{de::Deserializer, Deserialize, Serialize};
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]
pub(crate) async fn fetch<R: Runtime>(
pub async fn fetch<R: Runtime>(
app: AppHandle<R>,
method: String,
url: url::Url,
@ -109,10 +153,7 @@ pub(crate) async fn fetch<R: Runtime>(
}
#[command]
pub(crate) async fn fetch_cancel<R: Runtime>(
app: AppHandle<R>,
rid: RequestId,
) -> crate::Result<()> {
pub async fn fetch_cancel<R: Runtime>(app: AppHandle<R>, rid: RequestId) -> crate::Result<()> {
let mut request_table = app.http().requests.lock().await;
let req = request_table
.get_mut(&rid)
@ -122,7 +163,7 @@ pub(crate) async fn fetch_cancel<R: Runtime>(
}
#[command]
pub(crate) async fn fetch_send<R: Runtime>(
pub async fn fetch_send<R: Runtime>(
app: AppHandle<R>,
rid: RequestId,
) -> 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 {
status: status.as_u16(),
status_text: status.canonical_reason().unwrap_or_default().to_string(),
headers,
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};
pub use reqwest;
use serde::Serialize;
use reqwest::Response;
use tauri::async_runtime::Mutex;
use tauri::{
plugin::{Builder, TauriPlugin},
@ -30,6 +30,7 @@ type CancelableResponseResult = Result<Result<reqwest::Response>>;
type CancelableResponseFuture =
Pin<Box<dyn Future<Output = CancelableResponseResult> + Send + Sync>>;
type RequestTable = HashMap<RequestId, FetchRequest>;
type ResponseTable = HashMap<RequestId, Response>;
struct FetchRequest(Mutex<CancelableResponseFuture>);
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> {
#[allow(dead_code)]
app: AppHandle<R>,
scope: scope::Scope,
current_id: AtomicU32,
requests: Mutex<RequestTable>,
responses: Mutex<ResponseTable>,
}
impl<R: Runtime> Http<R> {
@ -80,6 +72,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
commands::fetch,
commands::fetch_cancel,
commands::fetch_send,
commands::fetch_read_body,
])
.setup(|app, api| {
let default_scope = HttpAllowlistScope::default();
@ -87,6 +80,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
app: app.clone(),
current_id: 0.into(),
requests: Default::default(),
responses: Default::default(),
scope: scope::Scope::new(
api.config()
.as_ref()

Loading…
Cancel
Save