[desktop] Use custom Tauri command for HTTP requests instead of http-client plugin

Replaced the http-client plugin, as it does not support keep-alive connections.
Each request opened a new TCP connection to the service, which was inefficient.

The new custom `send_tauri_http_request` command, exposed to the UI, uses an application-wide `reqwest::Client`, which supports idle (persistent) connections.
This commit is contained in:
Alexandr Stelnykovych
2025-04-24 14:00:15 +03:00
parent a42f0a6084
commit 5053ef1a23
7 changed files with 125 additions and 139 deletions

View File

@@ -1227,12 +1227,6 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
[[package]]
name = "data-url"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "dataurl"
version = "0.1.2"
@@ -2026,10 +2020,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -2039,11 +2031,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
"wasm-bindgen",
]
[[package]]
@@ -2381,7 +2371,6 @@ dependencies = [
"tokio",
"tokio-rustls 0.26.2",
"tower-service",
"webpki-roots",
]
[[package]]
@@ -3988,7 +3977,6 @@ dependencies = [
"tauri-build",
"tauri-plugin-clipboard-manager",
"tauri-plugin-dialog",
"tauri-plugin-http",
"tauri-plugin-log",
"tauri-plugin-notification",
"tauri-plugin-os",
@@ -4158,60 +4146,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "quinn"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012"
dependencies = [
"bytes",
"cfg_aliases 0.2.1",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls 0.23.25",
"socket2 0.5.9",
"thiserror 2.0.12",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
dependencies = [
"bytes",
"getrandom 0.3.2",
"rand 0.9.0",
"ring",
"rustc-hash",
"rustls 0.23.25",
"rustls-pki-types",
"slab",
"thiserror 2.0.12",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5"
dependencies = [
"cfg_aliases 0.2.1",
"libc",
"once_cell",
"socket2 0.5.9",
"tracing",
"windows-sys 0.59.0",
]
[[package]]
name = "quote"
version = "1.0.40"
@@ -4465,10 +4399,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.25",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
@@ -4476,7 +4407,6 @@ dependencies = [
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-rustls 0.26.2",
"tokio-util",
"tower",
"tower-service",
@@ -4485,7 +4415,6 @@ dependencies = [
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"webpki-roots",
"windows-registry",
]
@@ -4601,12 +4530,6 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
@@ -4676,7 +4599,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki 0.103.1",
"subtle",
@@ -4697,9 +4619,6 @@ name = "rustls-pki-types"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
dependencies = [
"web-time",
]
[[package]]
name = "rustls-webpki"
@@ -5561,28 +5480,6 @@ dependencies = [
"uuid",
]
[[package]]
name = "tauri-plugin-http"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "696ef548befeee6c6c17b80ef73e7c41205b6c2204e87ef78ccc231212389a5c"
dependencies = [
"data-url",
"http",
"regex",
"reqwest",
"schemars",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"tauri-plugin-fs",
"thiserror 2.0.12",
"tokio",
"url",
"urlpattern",
]
[[package]]
name = "tauri-plugin-log"
version = "2.3.1"
@@ -6682,16 +6579,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webkit2gtk"
version = "2.0.1"

View File

@@ -25,7 +25,6 @@ tauri-plugin-single-instance = "2.2.1"
tauri-plugin-notification = "2.2.1"
tauri-plugin-log = "2.2.1"
tauri-plugin-window-state = "2.2.1"
tauri-plugin-http = "2"
tauri-plugin-websocket = "2"
clap_lex = "0.7.2"
@@ -49,7 +48,7 @@ http = "1.0.0"
url = "2.5.0"
thiserror = "1.0"
log = "0.4.21"
reqwest = { version = "0.12" }
reqwest = { version = "0.12", features = ["cookies", "json"] }
rfd = { version = "*", default-features = false, features = [ "tokio", "gtk3", "common-controls-v6" ] }
open = "5.1.3"

View File

@@ -33,20 +33,7 @@
"window-state:allow-save-window-state",
"window-state:allow-restore-state",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
{
"identifier": "http:default",
"allow": [
{
"url": "http://127.0.0.1:817/**"
},
{
"url": "http://localhost:817/**"
}
]
},
"websocket:default",
"http:default",
"clipboard-manager:allow-write-text",
"websocket:default"
]
}

View File

@@ -0,0 +1 @@
pub mod tauri_http;

View File

@@ -0,0 +1,75 @@
use tauri::State;
use reqwest::{Client, Method};
use serde::{Deserialize, Serialize};
/// Creates and configures a shared HTTP client for application-wide use.
///
/// Returns a reqwest Client configured with:
/// - Connection pooling
/// - Persistent cookie store
///
/// Client can be accessed from UI through the exposed Tauri command `send_tauri_http_request(...)`
/// Such requests execute directly from the Tauri app binary, not from the WebView process
pub fn create_http_client() -> Client {
Client::builder()
// Maximum idle connections per host
.pool_max_idle_per_host(10)
// Enable cookie support
.cookie_store(true)
.user_agent("Portmaster UI")
.build()
.expect("failed to build HTTP client")
}
#[derive(Deserialize)]
pub struct HttpRequestOptions {
method: String,
headers: Vec<(String, String)>,
body: Option<Vec<u8>>,
}
#[derive(Serialize)]
pub struct HttpResponse {
status: u16,
status_text: String,
headers: Vec<(String, String)>,
body: Vec<u8>,
}
#[tauri::command]
pub async fn send_tauri_http_request(
client: State<'_, Client>,
url: String,
opts: HttpRequestOptions
) -> Result<HttpResponse, String> {
//println!("URL: {}", url);
// Build the request
let mut req = client
.request(Method::from_bytes(opts.method.as_bytes()).map_err(|e| e.to_string())?, &url);
// Apply headers
for (k, v) in opts.headers {
req = req.header(&k, &v);
}
// Attach body if present
if let Some(body) = opts.body {
req = req.body(body);
}
// Send and await the response
let resp = req.send().await.map_err(|e| e.to_string())?;
// Read status, headers, and body
let status = resp.status().as_u16();
let status_text = resp.status().canonical_reason().unwrap_or("").to_string();
let headers = resp
.headers()
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body = resp.bytes().await.map_err(|e| e.to_string())?.to_vec();
Ok(HttpResponse { status, status_text, headers, body })
}

View File

@@ -18,6 +18,7 @@ mod config;
mod portmaster;
mod traymenu;
mod window;
mod commands;
use log::{debug, error, info};
use portmaster::PortmasterExt;
@@ -145,9 +146,16 @@ fn main() {
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout)
};
// Create a single HTTP client that:
// - Pools and reuses connections for better performance
// - Is exposed to UI through 'send_tauri_http_request()' command
// - Such requests execute directly from the Tauri app binary, not from the WebView process
let http_client = commands::tauri_http::create_http_client();
let app = tauri::Builder::default()
// make HTTP client accessible in commands ('send_tauri_http_request()')
.manage(http_client)
.plugin(tauri_plugin_websocket::init())
.plugin(tauri_plugin_http::init())
// Shell plugin for open_external support
.plugin(tauri_plugin_shell::init())
// Initialize Logging plugin.
@@ -181,7 +189,8 @@ fn main() {
portmaster::commands::get_state,
portmaster::commands::set_state,
portmaster::commands::should_show,
portmaster::commands::should_handle_prompts
portmaster::commands::should_handle_prompts,
commands::tauri_http::send_tauri_http_request,
])
// Setup the app an any listeners
.setup(move |app| {