Files
Mycell-UI/src-tauri/src/api/mod.rs
syoul 7981fc571c fix(api): disable reqwest connection pool
Direct mycelium runs and our pkexec spawns are both healthy
(sidecar logs show acquired routes streaming for 20+s). Yet our
reqwest poller can't reach 127.0.0.1:port after the first
successful request. Smoking gun: failure happens ~10s after the
first reply — exactly when an idle keep-alive connection would
have been reaped.

Disable pooling (pool_max_idle_per_host(0)) so every call opens a
fresh TCP connection. Loopback overhead is negligible (~50us per
request) and we're immune to server-side idle closes. Also pin
connect_timeout to 3s so a wedged half-open doesn't block for
the full 10s request timeout.
2026-04-26 00:18:59 +02:00

80 lines
2.3 KiB
Rust

pub mod admin;
pub mod messages;
pub mod peers;
pub mod pubkey;
pub mod routes;
pub mod topics;
use crate::error::{AppError, AppResult};
use reqwest::{Client, Response};
use serde::de::DeserializeOwned;
use std::time::Duration;
/// Thin REST client for the mycelium daemon's HTTP API.
///
/// The base URL is set after the sidecar reports ready; clients are cheap
/// to clone (the inner `reqwest::Client` keeps a shared connection pool).
#[derive(Debug, Clone)]
pub struct MyceliumClient {
base: String,
http: Client,
}
impl MyceliumClient {
pub fn new(base: impl Into<String>) -> Self {
// Mycelium's HTTP server seems to drop idle keep-alive connections
// around the 10s mark; reusing a pooled stale connection surfaces
// as a generic "error sending request" once `start_daemon`
// returned. Open a fresh TCP connection per request — overhead is
// negligible on loopback and immune to server-side closes.
let http = Client::builder()
.pool_max_idle_per_host(0)
.timeout(Duration::from_secs(10))
.connect_timeout(Duration::from_secs(3))
.build()
.expect("reqwest client build");
Self {
base: base.into(),
http,
}
}
pub fn base_url(&self) -> &str {
&self.base
}
pub(crate) fn url(&self, path: &str) -> String {
format!("{}/api/v1{}", self.base, path)
}
pub(crate) async fn parse<T: DeserializeOwned>(resp: Response) -> AppResult<T> {
let status = resp.status();
if status.is_success() {
resp.json::<T>().await.map_err(AppError::from)
} else {
let body = resp.text().await.unwrap_or_default();
Err(AppError::DaemonStatus {
status: status.as_u16(),
body,
})
}
}
pub(crate) async fn check_status(resp: Response) -> AppResult<()> {
let status = resp.status();
if status.is_success() {
Ok(())
} else {
let body = resp.text().await.unwrap_or_default();
Err(AppError::DaemonStatus {
status: status.as_u16(),
body,
})
}
}
pub(crate) fn http(&self) -> &Client {
&self.http
}
}