Skip to content

feat(http): send fetch request bodies as raw IPC payloads - #3732

Open
followdarko wants to merge 1 commit into
tauri-apps:v2from
followdarko:feat/http-raw-request-body
Open

followdarko wants to merge 1 commit into
tauri-apps:v2from
followdarko:feat/http-raw-request-body

Conversation

@followdarko

Copy link
Copy Markdown
Contributor

Closes #3731.

The problem

fetch sends the request body inside the JSON arguments of plugin:http|fetch as Array.from(new Uint8Array(buffer)): one number per byte, built and stringified on the webview's main thread, then parsed back by serde_json. The window stalls for as long as that takes, which grows with the body.

The change

  • The guest code sends a non-empty body as the raw IPC payload, invoke(cmd, new Uint8Array(buffer), { headers }), and the rest of the request options as percent-encoded JSON in a client-config IPC header. plugin:fs|write_file passes file contents and options the same way.
  • fetch takes tauri::ipc::Request and reads the options from that header and the body from InvokeBody::Raw, or from a JSON number array, which is how a raw payload arrives on Android and under the postMessage fallback.
  • Without the header it reads { clientConfig } from the JSON arguments as before. The guest code still uses that form for a request without a body, and it keeps older guest code working against this crate.

Android and the isolation pattern get no speed-up, since a raw payload travels as a number array there, but they take the same code path.

Verification

A small app on macOS 15.7 (arm64, WKWebView, release build) sends the same requests through the previous api-iife.js and through this one, both against this crate, to a local server that answers with the body's length and hash. "Stall" is the longest gap between event-loop turns while the request is in flight. Median of 10 requests per row:

body stall, number array stall, raw payload whole request, number array whole request, raw payload
16 KiB 3 ms 1 ms 8 ms 5 ms
64 KiB 10 ms 1 ms 19 ms 6 ms
900 KiB 110 ms 2 ms 155 ms 11 ms
4 MiB 881 ms 8 ms 1010 ms 26 ms
16 MiB 4131 ms 48 ms 4777 ms 131 ms

performance.now() in WKWebView has 1 ms resolution, so the small rows are at its floor.

The same app checks that binary and text bodies arrive byte for byte, that a byte-string header value keeps its bytes and the content type is sent, that a POST without a body and a GET still go through, and that the previous guest code still works against this crate.

Unit tests cover reading the options and the body: a raw payload, a number array, an out-of-range number, the JSON arguments with and without data, and a raw payload without the header. The existing api-e2e http specs (JSON, binary, multipart and byte-string header bodies) all go through the new path.

cargo test -p tauri-plugin-http, cargo clippy -p tauri-plugin-http --all-targets -- -D warnings, cargo fmt --check, pnpm build, eslint and prettier --check pass on v2.

The guest code encoded a request body as a JSON array with one number
per byte, built and stringified on the webview's main thread. A
non-empty body is now the raw IPC payload and the other request options
ride in a `client-config` IPC header, as `plugin:fs|write_file` does.

The `fetch` command still accepts `{ clientConfig }` JSON arguments,
used for requests without a body and by older guest code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[http] fetch encodes request bodies as a JSON number array on the main thread

1 participant