diff --git a/examples/api/src/views/Http.svelte b/examples/api/src/views/Http.svelte
index 5a1d3032..998392ed 100644
--- a/examples/api/src/views/Http.svelte
+++ b/examples/api/src/views/Http.svelte
@@ -1,5 +1,5 @@
diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml
index f1fb6082..95f74bfe 100644
--- a/plugins/http/Cargo.toml
+++ b/plugins/http/Cargo.toml
@@ -13,14 +13,28 @@ tauri = { workspace = true }
thiserror = { workspace = true }
tauri-plugin-fs = { path = "../fs", version = "2.0.0-alpha.0" }
glob = "0.3"
-rand = "0.8"
-bytes = { version = "1", features = [ "serde" ] }
-serde_repr = "0.1"
http = "0.2"
-reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ] }
+reqwest = { version = "0.11", default-features = false }
+url = "2.4"
+data-url = "0.3"
[features]
-multipart = [ "reqwest/multipart" ]
-native-tls = [ "reqwest/native-tls" ]
-native-tls-vendored = [ "reqwest/native-tls-vendored" ]
-rustls-tls = [ "reqwest/rustls-tls" ]
+multipart = ["reqwest/multipart"]
+json = ["reqwest/json"]
+stream = ["reqwest/stream"]
+native-tls = ["reqwest/native-tls"]
+native-tls-vendored = ["reqwest/native-tls-vendored"]
+rustls-tls = ["reqwest/rustls-tls"]
+default-tls = ["reqwest/default-tls"]
+native-tls-alpn = ["reqwest/native-tls-alpn"]
+rustls-tls-manual-roots = ["reqwest/rustls-tls-manual-roots"]
+rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"]
+rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"]
+blocking = ["reqwest/blocking"]
+cookies = ["reqwest/cookies"]
+gzip = ["reqwest/gzip"]
+brotli = ["reqwest/brotli"]
+deflate = ["reqwest/deflate"]
+trust-dns = ["reqwest/trust-dns"]
+socks = ["reqwest/socks"]
+http3 = ["reqwest/http3"]
diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts
index 60378402..dac17145 100644
--- a/plugins/http/guest-js/index.ts
+++ b/plugins/http/guest-js/index.ts
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
/**
- * Access the HTTP client written in Rust.
+ * Make HTTP requests with the Rust backend.
*
* ## Security
*
@@ -31,518 +31,61 @@ declare global {
}
/**
- * @since 2.0.0
- */
-interface Duration {
- secs: number;
- nanos: number;
-}
-
-/**
- * @since 2.0.0
- */
-interface ClientOptions {
- /**
- * Defines the maximum number of redirects the client should follow.
- * If set to 0, no redirects will be followed.
- */
- maxRedirections?: number;
- connectTimeout?: number | Duration;
-}
-
-/**
- * @since 2.0.0
- */
-enum ResponseType {
- JSON = 1,
- Text = 2,
- Binary = 3,
-}
-
-/**
- * @since 2.0.0
- */
-interface FilePart {
- file: string | T;
- mime?: string;
- fileName?: string;
-}
-
-type Part = string | Uint8Array | FilePart;
-
-/**
- * The body object to be used on POST and PUT requests.
- *
- * @since 2.0.0
- */
-class Body {
- type: string;
- payload: unknown;
-
- /** @ignore */
- private constructor(type: string, payload: unknown) {
- this.type = type;
- this.payload = payload;
- }
-
- /**
- * Creates a new form data body. The form data is an object where each key is the entry name,
- * and the value is either a string or a file object.
- *
- * By default it sets the `application/x-www-form-urlencoded` Content-Type header,
- * but you can set it to `multipart/form-data` if the Cargo feature `multipart` is enabled.
- *
- * Note that a file path must be allowed in the `fs` scope.
- * @example
- * ```typescript
- * import { Body } from "@tauri-apps/plugin-http"
- * const body = Body.form({
- * key: 'value',
- * image: {
- * file: '/path/to/file', // either a path or an array buffer of the file contents
- * mime: 'image/jpeg', // optional
- * fileName: 'image.jpg' // optional
- * }
- * });
- *
- * // alternatively, use a FormData:
- * const form = new FormData();
- * form.append('key', 'value');
- * form.append('image', file, 'image.png');
- * const formBody = Body.form(form);
- * ```
- *
- * @param data The body data.
- *
- * @returns The body object ready to be used on the POST and PUT requests.
- *
- * @since 2.0.0
- */
- static form(data: Record | FormData): Body {
- const form: Record> = {};
-
- const append = (
- key: string,
- v: string | Uint8Array | FilePart | File
- ): void => {
- if (v !== null) {
- let r;
- if (typeof v === "string") {
- r = v;
- } else if (v instanceof Uint8Array || Array.isArray(v)) {
- r = Array.from(v);
- } else if (v instanceof File) {
- r = { file: v.name, mime: v.type, fileName: v.name };
- } else if (typeof v.file === "string") {
- r = { file: v.file, mime: v.mime, fileName: v.fileName };
- } else {
- r = { file: Array.from(v.file), mime: v.mime, fileName: v.fileName };
- }
- form[String(key)] = r;
- }
- };
-
- if (data instanceof FormData) {
- for (const [key, value] of data) {
- append(key, value);
- }
- } else {
- for (const [key, value] of Object.entries(data)) {
- append(key, value);
- }
- }
- return new Body("Form", form);
- }
-
- /**
- * Creates a new JSON body.
- * @example
- * ```typescript
- * import { Body } from "@tauri-apps/plugin-http"
- * Body.json({
- * registered: true,
- * name: 'tauri'
- * });
- * ```
- *
- * @param data The body JSON object.
- *
- * @returns The body object ready to be used on the POST and PUT requests.
- *
- * @since 2.0.0
- */
- static json(data: Record): Body {
- return new Body("Json", data);
- }
-
- /**
- * Creates a new UTF-8 string body.
- * @example
- * ```typescript
- * import { Body } from "@tauri-apps/plugin-http"
- * Body.text('The body content as a string');
- * ```
- *
- * @param value The body string.
- *
- * @returns The body object ready to be used on the POST and PUT requests.
- *
- * @since 2.0.0
- */
- static text(value: string): Body {
- return new Body("Text", value);
- }
-
- /**
- * Creates a new byte array body.
- * @example
- * ```typescript
- * import { Body } from "@tauri-apps/plugin-http"
- * Body.bytes(new Uint8Array([1, 2, 3]));
- * ```
- *
- * @param bytes The body byte array.
- *
- * @returns The body object ready to be used on the POST and PUT requests.
- *
- * @since 2.0.0
- */
- static bytes(
- bytes: Iterable | ArrayLike | ArrayBuffer
- ): Body {
- // stringifying Uint8Array doesn't return an array of numbers, so we create one here
- return new Body(
- "Bytes",
- Array.from(bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes)
- );
- }
-}
-
-/** The request HTTP verb. */
-type HttpVerb =
- | "GET"
- | "POST"
- | "PUT"
- | "DELETE"
- | "PATCH"
- | "HEAD"
- | "OPTIONS"
- | "CONNECT"
- | "TRACE";
-
-/**
- * Options object sent to the backend.
+ * Fetch a resource from the network. It returns a `Promise` that resolves to the
+ * `Response` to that `Request`, whether it is successful or not.
*
- * @since 2.0.0
- */
-interface HttpOptions {
- method: HttpVerb;
- url: string;
- headers?: Record;
- query?: Record;
- body?: Body;
- timeout?: number | Duration;
- responseType?: ResponseType;
-}
-
-/** Request options. */
-type RequestOptions = Omit;
-/** Options for the `fetch` API. */
-type FetchOptions = Omit;
-
-/** @ignore */
-interface IResponse {
- url: string;
- status: number;
- headers: Record;
- rawHeaders: Record;
- data: T;
-}
-
-/**
- * Response object.
- *
- * @since 2.0.0
- * */
-class Response {
- /** The request URL. */
- url: string;
- /** The response status code. */
- status: number;
- /** A boolean indicating whether the response was successful (status in the range 200–299) or not. */
- ok: boolean;
- /** The response headers. */
- headers: Record;
- /** The response raw headers. */
- rawHeaders: Record;
- /** The response data. */
- data: T;
-
- /** @ignore */
- constructor(response: IResponse) {
- this.url = response.url;
- this.status = response.status;
- this.ok = this.status >= 200 && this.status < 300;
- this.headers = response.headers;
- this.rawHeaders = response.rawHeaders;
- this.data = response.data;
- }
-}
-
-/**
- * @since 2.0.0
+ * @example
+ * ```typescript
+ * const response = await fetch("http://my.json.host/data.json");
+ * console.log(response.status); // e.g. 200
+ * console.log(response.statusText); // e.g. "OK"
+ * const jsonData = await response.json();
+ * ```
*/
-class Client {
- id: number;
- /** @ignore */
- constructor(id: number) {
- this.id = id;
- }
-
- /**
- * Drops the client instance.
- * @example
- * ```typescript
- * import { getClient } from '@tauri-apps/plugin-http';
- * const client = await getClient();
- * await client.drop();
- * ```
- */
- async drop(): Promise {
- return window.__TAURI_INVOKE__("plugin:http|drop_client", {
- client: this.id,
- });
- }
-
- /**
- * Makes an HTTP request.
- * @example
- * ```typescript
- * import { getClient } from '@tauri-apps/plugin-http';
- * const client = await getClient();
- * const response = await client.request({
- * method: 'GET',
- * url: 'http://localhost:3003/users',
- * });
- * ```
- */
- async request(options: HttpOptions): Promise> {
- const jsonResponse =
- !options.responseType || options.responseType === ResponseType.JSON;
- if (jsonResponse) {
- options.responseType = ResponseType.Text;
- }
- return window
- .__TAURI_INVOKE__>("plugin:http|request", {
- clientId: this.id,
- options,
- })
- .then((res) => {
- const response = new Response(res);
- if (jsonResponse) {
- /* eslint-disable */
- try {
- response.data = JSON.parse(response.data as string);
- } catch (e) {
- if (response.ok && (response.data as unknown as string) === "") {
- response.data = {} as T;
- } else if (response.ok) {
- throw Error(
- `Failed to parse response \`${response.data}\` as JSON: ${e};
- try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.`
- );
- }
- }
- /* eslint-enable */
- return response;
- }
- return response;
- });
- }
-
- /**
- * Makes a GET request.
- * @example
- * ```typescript
- * import { getClient, ResponseType } from '@tauri-apps/plugin-http';
- * const client = await getClient();
- * const response = await client.get('http://localhost:3003/users', {
- * timeout: 30,
- * // the expected response type
- * responseType: ResponseType.JSON
- * });
- * ```
- */
- async get(url: string, options?: RequestOptions): Promise> {
- return this.request({
- method: "GET",
- url,
- ...options,
- });
- }
+async function fetch(
+ input: URL | Request | string,
+ init?: RequestInit
+): Promise {
+ const req = new Request(input, init);
+ const buffer = await req.arrayBuffer();
+ const reqData = buffer.byteLength ? Array.from(new Uint8Array(buffer)) : null;
+
+ const rid = await window.__TAURI_INVOKE__("plugin:http|fetch", {
+ cmd: "fetch",
+ method: req.method,
+ url: req.url,
+ headers: Array.from(req.headers.entries()),
+ data: reqData,
+ });
- /**
- * Makes a POST request.
- * @example
- * ```typescript
- * import { getClient, Body, ResponseType } from '@tauri-apps/plugin-http';
- * const client = await getClient();
- * const response = await client.post('http://localhost:3003/users', {
- * body: Body.json({
- * name: 'tauri',
- * password: 'awesome'
- * }),
- * // in this case the server returns a simple string
- * responseType: ResponseType.Text,
- * });
- * ```
- */
- async post(
- url: string,
- body?: Body,
- options?: RequestOptions
- ): Promise> {
- return this.request({
- method: "POST",
- url,
- body,
- ...options,
+ req.signal.addEventListener("abort", () => {
+ window.__TAURI_INVOKE__("plugin:http|fetch_cancel", {
+ rid,
});
- }
+ });
- /**
- * Makes a PUT request.
- * @example
- * ```typescript
- * import { getClient, Body } from '@tauri-apps/plugin-http';
- * const client = await getClient();
- * const response = await client.put('http://localhost:3003/users/1', {
- * body: Body.form({
- * file: {
- * file: '/home/tauri/avatar.png',
- * mime: 'image/png',
- * fileName: 'avatar.png'
- * }
- * })
- * });
- * ```
- */
- async put(
- url: string,
- body?: Body,
- options?: RequestOptions
- ): Promise> {
- return this.request({
- method: "PUT",
- url,
- body,
- ...options,
- });
+ interface FetchSendResponse {
+ status: number;
+ statusText: string;
+ headers: [[string, string]];
+ data: number[];
+ url: string;
}
- /**
- * Makes a PATCH request.
- * @example
- * ```typescript
- * import { getClient, Body } from '@tauri-apps/plugin-http';
- * const client = await getClient();
- * const response = await client.patch('http://localhost:3003/users/1', {
- * body: Body.json({ email: 'contact@tauri.app' })
- * });
- * ```
- */
- async patch(url: string, options?: RequestOptions): Promise> {
- return this.request({
- method: "PATCH",
- url,
- ...options,
+ const { status, statusText, url, headers, data } =
+ await window.__TAURI_INVOKE__("plugin:http|fetch_send", {
+ rid,
});
- }
- /**
- * Makes a DELETE request.
- * @example
- * ```typescript
- * import { getClient } from '@tauri-apps/plugin-http';
- * const client = await getClient();
- * const response = await client.delete('http://localhost:3003/users/1');
- * ```
- */
- async delete(url: string, options?: RequestOptions): Promise> {
- return this.request({
- method: "DELETE",
- url,
- ...options,
- });
- }
-}
-
-/**
- * Creates a new client using the specified options.
- * @example
- * ```typescript
- * import { getClient } from '@tauri-apps/plugin-http';
- * const client = await getClient();
- * ```
- *
- * @param options Client configuration.
- *
- * @returns A promise resolving to the client instance.
- *
- * @since 2.0.0
- */
-async function getClient(options?: ClientOptions): Promise {
- return window
- .__TAURI_INVOKE__("plugin:http|create_client", {
- options,
- })
- .then((id) => new Client(id));
-}
+ const res = new Response(Uint8Array.from(data), {
+ headers,
+ status,
+ statusText,
+ });
-/** @internal */
-let defaultClient: Client | null = null;
+ Object.defineProperty(res, "url", { value: url });
-/**
- * Perform an HTTP request using the default client.
- * @example
- * ```typescript
- * import { fetch } from '@tauri-apps/plugin-http';
- * const response = await fetch('http://localhost:3003/users/2', {
- * method: 'GET',
- * timeout: 30,
- * });
- * ```
- */
-async function fetch(
- url: string,
- options?: FetchOptions
-): Promise> {
- if (defaultClient === null) {
- defaultClient = await getClient();
- }
- return defaultClient.request({
- url,
- method: options?.method ?? "GET",
- ...options,
- });
+ return res;
}
-export type {
- Duration,
- ClientOptions,
- Part,
- HttpVerb,
- HttpOptions,
- RequestOptions,
- FetchOptions,
-};
-
-export {
- getClient,
- fetch,
- Body,
- Client,
- Response,
- ResponseType,
- type FilePart,
-};
+export { fetch };
diff --git a/plugins/http/src/api-iife.js b/plugins/http/src/api-iife.js
index e99aa35b..722209c1 100644
--- a/plugins/http/src/api-iife.js
+++ b/plugins/http/src/api-iife.js
@@ -1 +1 @@
-if("__TAURI__"in window){var __TAURI_HTTP__=function(e){"use strict";var t;e.ResponseType=void 0,(t=e.ResponseType||(e.ResponseType={}))[t.JSON=1]="JSON",t[t.Text=2]="Text",t[t.Binary=3]="Binary";class r{constructor(e,t){this.type=e,this.payload=t}static form(e){const t={},s=(e,r)=>{if(null!==r){let s;s="string"==typeof r?r:r instanceof Uint8Array||Array.isArray(r)?Array.from(r):r instanceof File?{file:r.name,mime:r.type,fileName:r.name}:"string"==typeof r.file?{file:r.file,mime:r.mime,fileName:r.fileName}:{file:Array.from(r.file),mime:r.mime,fileName:r.fileName},t[String(e)]=s}};if(e instanceof FormData)for(const[t,r]of e)s(t,r);else for(const[t,r]of Object.entries(e))s(t,r);return new r("Form",t)}static json(e){return new r("Json",e)}static text(e){return new r("Text",e)}static bytes(e){return new r("Bytes",Array.from(e instanceof ArrayBuffer?new Uint8Array(e):e))}}class s{constructor(e){this.url=e.url,this.status=e.status,this.ok=this.status>=200&&this.status<300,this.headers=e.headers,this.rawHeaders=e.rawHeaders,this.data=e.data}}class n{constructor(e){this.id=e}async drop(){return window.__TAURI_INVOKE__("plugin:http|drop_client",{client:this.id})}async request(t){const r=!t.responseType||t.responseType===e.ResponseType.JSON;return r&&(t.responseType=e.ResponseType.Text),window.__TAURI_INVOKE__("plugin:http|request",{clientId:this.id,options:t}).then((e=>{const t=new s(e);if(r){try{t.data=JSON.parse(t.data)}catch(e){if(t.ok&&""===t.data)t.data={};else if(t.ok)throw Error(`Failed to parse response \`${t.data}\` as JSON: ${e};\n try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.`)}return t}return t}))}async get(e,t){return this.request({method:"GET",url:e,...t})}async post(e,t,r){return this.request({method:"POST",url:e,body:t,...r})}async put(e,t,r){return this.request({method:"PUT",url:e,body:t,...r})}async patch(e,t){return this.request({method:"PATCH",url:e,...t})}async delete(e,t){return this.request({method:"DELETE",url:e,...t})}}async function i(e){return window.__TAURI_INVOKE__("plugin:http|create_client",{options:e}).then((e=>new n(e)))}let o=null;return e.Body=r,e.Client=n,e.Response=s,e.fetch=async function(e,t){var r;return null===o&&(o=await i()),o.request({url:e,method:null!==(r=null==t?void 0:t.method)&&void 0!==r?r:"GET",...t})},e.getClient=i,e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_HTTP__})}
+if("__TAURI__"in window){var __TAURI_HTTP__=function(t){"use strict";return t.fetch=async function(t,e){const r=new Request(t,e),_=await r.arrayBuffer(),n=_.byteLength?Array.from(new Uint8Array(_)):null,a=await window.__TAURI_INVOKE__("plugin:http|fetch",{cmd:"fetch",method:r.method,url:r.url,headers:Array.from(r.headers.entries()),data:n});r.signal.addEventListener("abort",(()=>{window.__TAURI_INVOKE__("plugin:http|fetch_cancel",{rid:a})}));const{status:i,statusText:s,url:d,headers:u,data:o}=await window.__TAURI_INVOKE__("plugin:http|fetch_send",{rid:a}),c=new Response(Uint8Array.from(o),{headers:u,status:i,statusText:s});return Object.defineProperty(c,"url",{value:d}),c},t}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_HTTP__})}
diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs
new file mode 100644
index 00000000..d3b93c41
--- /dev/null
+++ b/plugins/http/src/commands.rs
@@ -0,0 +1,140 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::collections::HashMap;
+
+use http::{header, HeaderName, HeaderValue, Method, StatusCode};
+use tauri::{command, AppHandle, Runtime};
+
+use crate::{Error, FetchRequest, FetchResponse, HttpExt, RequestId};
+
+#[command]
+pub(crate) async fn fetch(
+ app: AppHandle,
+ method: String,
+ url: String,
+ headers: Vec<(String, String)>,
+ data: Option>,
+) -> crate::Result {
+ let url = url::Url::parse(&url)?;
+ let scheme = url.scheme();
+ let method = Method::from_bytes(method.as_bytes())?;
+ let headers: HashMap = HashMap::from_iter(headers);
+
+ match scheme {
+ "http" | "https" => {
+ if app.http().scope.is_allowed(&url) {
+ let mut request = reqwest::Client::new().request(method.clone(), url);
+
+ for (key, value) in &headers {
+ let name = HeaderName::from_bytes(key.as_bytes())?;
+ let v = HeaderValue::from_bytes(value.as_bytes())?;
+ if !matches!(name, header::HOST | header::CONTENT_LENGTH) {
+ request = request.header(name, v);
+ }
+ }
+
+ // POST and PUT requests should always have a 0 length content-length,
+ // if there is no body. https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
+ if data.is_none() && matches!(method, Method::POST | Method::PUT) {
+ request = request.header(header::CONTENT_LENGTH, HeaderValue::from(0));
+ }
+
+ if headers.contains_key(header::RANGE.as_str()) {
+ // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch step 18
+ // If httpRequest’s header list contains `Range`, then append (`Accept-Encoding`, `identity`)
+ request = request.header(
+ header::ACCEPT_ENCODING,
+ HeaderValue::from_static("identity"),
+ );
+ }
+
+ if !headers.contains_key(header::USER_AGENT.as_str()) {
+ request = request.header(header::USER_AGENT, HeaderValue::from_static("tauri"));
+ }
+
+ if let Some(data) = data {
+ request = request.body(data);
+ }
+
+ let http_state = app.http();
+ let rid = http_state.next_id();
+ let fut = async move { Ok(request.send().await.map_err(Into::into)) };
+ let mut request_table = http_state.requests.lock().await;
+ request_table.insert(rid, FetchRequest::new(Box::pin(fut)));
+
+ Ok(rid)
+ } else {
+ Err(Error::UrlNotAllowed(url))
+ }
+ }
+ "data" => {
+ let data_url =
+ data_url::DataUrl::process(url.as_str()).map_err(|_| Error::DataUrlError)?;
+ let (body, _) = data_url
+ .decode_to_vec()
+ .map_err(|_| Error::DataUrlDecodeError)?;
+
+ let response = http::Response::builder()
+ .status(StatusCode::OK)
+ .header(header::CONTENT_TYPE, data_url.mime_type().to_string())
+ .body(reqwest::Body::from(body))?;
+
+ let http_state = app.http();
+ let rid = http_state.next_id();
+ let fut = async move { Ok(Ok(reqwest::Response::from(response))) };
+ let mut request_table = http_state.requests.lock().await;
+ request_table.insert(rid, FetchRequest::new(Box::pin(fut)));
+ Ok(rid)
+ }
+ _ => Err(Error::SchemeNotSupport(scheme.to_string())),
+ }
+}
+
+#[command]
+pub(crate) async fn fetch_cancel(
+ app: AppHandle,
+ rid: RequestId,
+) -> crate::Result<()> {
+ let mut request_table = app.http().requests.lock().await;
+ let req = request_table
+ .get_mut(&rid)
+ .ok_or(Error::InvalidRequestId(rid))?;
+ *req = FetchRequest::new(Box::pin(async { Err(Error::RequestCanceled) }));
+ Ok(())
+}
+
+#[command]
+pub(crate) async fn fetch_send(
+ app: AppHandle,
+ rid: RequestId,
+) -> crate::Result {
+ let mut request_table = app.http().requests.lock().await;
+ let req = request_table
+ .remove(&rid)
+ .ok_or(Error::InvalidRequestId(rid))?;
+
+ let res = match req.0.lock().await.as_mut().await {
+ Ok(Ok(res)) => res,
+ Ok(Err(e)) | Err(e) => return Err(e),
+ };
+
+ let status = res.status();
+ let url = res.url().to_string();
+ let mut headers = Vec::new();
+ for (key, val) in res.headers().iter() {
+ headers.push((
+ key.as_str().into(),
+ String::from_utf8(val.as_bytes().to_vec())?,
+ ));
+ }
+
+ Ok(FetchResponse {
+ status: status.as_u16(),
+ status_text: status.canonical_reason().unwrap_or_default().to_string(),
+ headers,
+ url,
+ data: res.bytes().await?.to_vec(),
+ })
+}
diff --git a/plugins/http/src/commands/client.rs b/plugins/http/src/commands/client.rs
deleted file mode 100644
index 07614a53..00000000
--- a/plugins/http/src/commands/client.rs
+++ /dev/null
@@ -1,341 +0,0 @@
-// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-License-Identifier: MIT
-
-use std::{collections::HashMap, path::PathBuf, time::Duration};
-
-use reqwest::{header, Method, Url};
-use serde::{Deserialize, Deserializer, Serialize};
-use serde_json::Value;
-use serde_repr::{Deserialize_repr, Serialize_repr};
-
-#[derive(Deserialize)]
-#[serde(untagged)]
-enum SerdeDuration {
- Seconds(u64),
- Duration(Duration),
-}
-
-fn deserialize_duration<'de, D: Deserializer<'de>>(
- deserializer: D,
-) -> std::result::Result