commit e337c44c1a8928d8072b34cfd4c0b9e4299e6afe
parent 5017be83a04bca5a146a3d5ddf4640661748e1f7
Author: Silas Brack <silasbrack@gmail.com>
Date: Fri, 12 Jun 2026 19:49:16 +0200
perf: share reqwest connection pool across all TrailBase clients
Previously, every request created a new trailbase_client::Client
which internally created a new reqwest::Client, meaning no TCP
connection reuse to TrailBase. Under load, this caused hundreds of
simultaneous TCP connection setups to localhost, capping throughput
at ~220 rps despite TrailBase handling 1400+ rps.
Implements a SharedTransport that wraps a single reqwest::Client
(shared via Arc internally) and passes it to all TrailBase client
instances via ClientOptions.transport. Also bumps reqwest to 0.13
to match trailbase-client's version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
10 files changed, 96 insertions(+), 239 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -517,16 +517,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
-name = "errno"
-version = "0.3.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
-dependencies = [
- "libc",
- "windows-sys 0.61.2",
-]
-
-[[package]]
name = "eventsource-stream"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -572,21 +562,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
-name = "foreign-types"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
-dependencies = [
- "foreign-types-shared",
-]
-
-[[package]]
-name = "foreign-types-shared"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
-
-[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -858,22 +833,6 @@ dependencies = [
]
[[package]]
-name = "hyper-tls"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
-dependencies = [
- "bytes",
- "http-body-util",
- "hyper",
- "hyper-util",
- "native-tls",
- "tokio",
- "tokio-native-tls",
- "tower-service",
-]
-
-[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1166,12 +1125,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
-name = "linux-raw-sys"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
-
-[[package]]
name = "litemap"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1244,23 +1197,6 @@ dependencies = [
]
[[package]]
-name = "native-tls"
-version = "0.2.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
-dependencies = [
- "libc",
- "log",
- "openssl",
- "openssl-probe",
- "openssl-sys",
- "schannel",
- "security-framework",
- "security-framework-sys",
- "tempfile",
-]
-
-[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1329,49 +1265,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
-name = "openssl"
-version = "0.10.80"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
-dependencies = [
- "bitflags",
- "cfg-if",
- "foreign-types",
- "libc",
- "openssl-macros",
- "openssl-sys",
-]
-
-[[package]]
-name = "openssl-macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
-name = "openssl-sys"
-version = "0.9.116"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
-]
-
-[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1467,12 +1366,6 @@ dependencies = [
]
[[package]]
-name = "pkg-config"
-version = "0.3.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
-
-[[package]]
name = "potential_utf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1655,49 +1548,6 @@ dependencies = [
[[package]]
name = "reqwest"
-version = "0.12.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
-dependencies = [
- "base64",
- "bytes",
- "encoding_rs",
- "futures-core",
- "futures-util",
- "h2",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-rustls",
- "hyper-tls",
- "hyper-util",
- "js-sys",
- "log",
- "mime",
- "native-tls",
- "percent-encoding",
- "pin-project-lite",
- "rustls-pki-types",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "sync_wrapper",
- "tokio",
- "tokio-native-tls",
- "tokio-util",
- "tower",
- "tower-http",
- "tower-service",
- "url",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "wasm-streams 0.4.2",
- "web-sys",
-]
-
-[[package]]
-name = "reqwest"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
@@ -1723,6 +1573,8 @@ dependencies = [
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
+ "serde",
+ "serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
@@ -1733,7 +1585,7 @@ dependencies = [
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
- "wasm-streams 0.5.0",
+ "wasm-streams",
"web-sys",
]
@@ -1797,19 +1649,6 @@ dependencies = [
]
[[package]]
-name = "rustix"
-version = "1.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
-dependencies = [
- "bitflags",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys 0.61.2",
-]
-
-[[package]]
name = "rustls"
version = "0.23.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2072,10 +1911,12 @@ name = "simple-web-app"
version = "0.1.0"
dependencies = [
"askama",
+ "async-trait",
"axum",
"axum-extra",
"chrono",
- "reqwest 0.12.28",
+ "http",
+ "reqwest",
"serde",
"serde_json",
"thiserror 2.0.18",
@@ -2189,19 +2030,6 @@ dependencies = [
]
[[package]]
-name = "tempfile"
-version = "3.27.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
-dependencies = [
- "fastrand",
- "getrandom 0.3.4",
- "once_cell",
- "rustix",
- "windows-sys 0.61.2",
-]
-
-[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2324,16 +2152,6 @@ dependencies = [
]
[[package]]
-name = "tokio-native-tls"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
-dependencies = [
- "native-tls",
- "tokio",
-]
-
-[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2460,7 +2278,7 @@ dependencies = [
"http-body-util",
"jsonwebtoken",
"parking_lot",
- "reqwest 0.13.2",
+ "reqwest",
"serde",
"serde_json",
"serde_repr",
@@ -2518,12 +2336,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
-name = "vcpkg"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
-
-[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2620,19 +2432,6 @@ dependencies = [
[[package]]
name = "wasm-streams"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
-dependencies = [
- "futures-util",
- "js-sys",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
-]
-
-[[package]]
-name = "wasm-streams"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
diff --git a/Cargo.toml b/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2021"
axum = "0.8"
axum-extra = { version = "0.10", features = ["cookie"] }
askama = "0.12"
+async-trait = "0.1"
chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "net"] }
tokio-stream = { version = "0.1", features = ["sync"] }
@@ -14,6 +15,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
tracing = "0.1"
-reqwest = { version = "0.12", features = ["json", "stream"] }
+http = "1"
+reqwest = { version = "0.13", features = ["json", "stream"] }
trailbase-client = "0.8"
url = "2"
diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs
@@ -64,14 +64,14 @@ pub async fn login(
jar: CookieJar,
Form(form): Form<LoginForm>,
) -> Result<(CookieJar, Redirect), Html<String>> {
- let client = trailbase::new_client(&state.trailbase_url).map_err(|e| {
+ let client = trailbase::new_client(&state.http_client, &state.trailbase_url).map_err(|e| {
render_login_error(&format!("Client error: {}", e))
})?;
match trailbase::login(&client, &form.email, &form.password).await {
Ok(tokens) => {
// Ensure user has a profile, create one if missing
- if let Ok(auth_client) = trailbase::client_with_tokens(&state.trailbase_url, &tokens) {
+ if let Ok(auth_client) = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens) {
if let Some(user_id) = trailbase::extract_user_id_from_token(&tokens.auth_token) {
match trailbase::get_user_profile_by_id(&auth_client, &user_id).await {
Ok(Some(_)) => {},
@@ -135,7 +135,7 @@ pub async fn register(
return Err(render_register_error(&form, "Passwords do not match"));
}
- let client = trailbase::new_client(&state.trailbase_url).map_err(|e| {
+ let client = trailbase::new_client(&state.http_client, &state.trailbase_url).map_err(|e| {
render_register_error(&form, &format!("Client error: {}", e))
})?;
@@ -148,7 +148,7 @@ pub async fn register(
match trailbase::register(&client, &state.http_client, &form.email, &form.password).await {
Ok(tokens) => {
// Create authenticated client and user profile
- if let Ok(auth_client) = trailbase::client_with_tokens(&state.trailbase_url, &tokens) {
+ if let Ok(auth_client) = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens) {
if let Err(e) = trailbase::create_user_profile(&auth_client, &form.username).await {
tracing::warn!("Failed to create user profile: {}", e);
}
diff --git a/src/handlers/comment.rs b/src/handlers/comment.rs
@@ -29,7 +29,7 @@ pub async fn create_comment(
Html(r#"<meta http-equiv="refresh" content="0;url=/login">"#.to_string())
})?;
- let client = trailbase::client_with_tokens(&state.trailbase_url, &tokens)
+ let client = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens)
.map_err(|e| Html(format!("Client error: {}", e)))?;
if form.text.trim().is_empty() {
@@ -72,7 +72,7 @@ pub async fn show_reply_form(
let tokens = get_tokens_from_cookies(&jar)
.ok_or_else(|| AppError::Unauthorized("Login required".to_string()))?;
- let client = trailbase::client_with_tokens(&state.trailbase_url, &tokens)?;
+ let client = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens)?;
// Get the parent comment to show what we're replying to
let args = trailbase_client::ListArguments::new()
@@ -131,7 +131,7 @@ pub async fn submit_reply(
Html(r#"<meta http-equiv="refresh" content="0;url=/login">"#.to_string())
})?;
- let client = trailbase::client_with_tokens(&state.trailbase_url, &tokens)
+ let client = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens)
.map_err(|e| Html(format!("Client error: {}", e)))?;
if form.text.trim().is_empty() {
diff --git a/src/handlers/feed.rs b/src/handlers/feed.rs
@@ -47,8 +47,8 @@ async fn fetch_feed(
// Create client (with auth if available)
let tokens = get_tokens_from_cookies(jar);
let client = match &tokens {
- Some(t) => trailbase::client_with_tokens(&state.trailbase_url, t)?,
- None => trailbase::new_client(&state.trailbase_url)?,
+ Some(t) => trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, t)?,
+ None => trailbase::new_client(&state.http_client, &state.trailbase_url)?,
};
// Fetch categories/tags
diff --git a/src/handlers/story.rs b/src/handlers/story.rs
@@ -79,8 +79,8 @@ pub async fn show_story(
let request_start = std::time::Instant::now();
let tokens = get_tokens_from_cookies(&jar);
let client = match &tokens {
- Some(t) => trailbase::client_with_tokens(&state.trailbase_url, t)?,
- None => trailbase::new_client(&state.trailbase_url)?,
+ Some(t) => trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, t)?,
+ None => trailbase::new_client(&state.http_client, &state.trailbase_url)?,
};
// Fetch the story
@@ -171,7 +171,7 @@ pub async fn show_submit(
));
}
- let client = trailbase::new_client(&state.trailbase_url)?;
+ let client = trailbase::new_client(&state.http_client, &state.trailbase_url)?;
let all_tags = trailbase::get_categories(&client, 50).await.unwrap_or_default();
let submit = SubmitTemplate {
@@ -211,7 +211,7 @@ pub async fn submit_story(
Html(r#"<meta http-equiv="refresh" content="0;url=/login">"#.to_string())
})?;
- let client = trailbase::client_with_tokens(&state.trailbase_url, &tokens)
+ let client = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens)
.map_err(|e| render_submit_error(&form, &[], &format!("Client error: {}", e)))?;
let all_tags = trailbase::get_categories(&client, 50).await.unwrap_or_default();
@@ -293,7 +293,7 @@ pub async fn show_edit(
let tokens = get_tokens_from_cookies(&jar)
.ok_or_else(|| AppError::Unauthorized("Login required".to_string()))?;
- let client = trailbase::client_with_tokens(&state.trailbase_url, &tokens)?;
+ let client = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens)?;
let story = trailbase::get_story(&client, id)
.await?
@@ -345,7 +345,7 @@ pub async fn edit_story(
Html(r#"<meta http-equiv="refresh" content="0;url=/login">"#.to_string())
})?;
- let client = trailbase::client_with_tokens(&state.trailbase_url, &tokens)
+ let client = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens)
.map_err(|e| render_edit_error(id, &form, &[], &format!("Client error: {}", e)))?;
// Check ownership
@@ -437,8 +437,8 @@ pub async fn comments_stream(
// Helper to build a TrailBase client
let make_client = || -> Option<trailbase_client::Client> {
match &tokens {
- Some(t) => trailbase::client_with_tokens(&state.trailbase_url, t).ok(),
- None => trailbase::new_client(&state.trailbase_url).ok(),
+ Some(t) => trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, t).ok(),
+ None => trailbase::new_client(&state.http_client, &state.trailbase_url).ok(),
}
};
diff --git a/src/handlers/tag.rs b/src/handlers/tag.rs
@@ -29,8 +29,8 @@ pub async fn show_tag(
) -> Result<Html<String>, AppError> {
let tokens = get_tokens_from_cookies(&jar);
let client = match &tokens {
- Some(t) => trailbase::client_with_tokens(&state.trailbase_url, t)?,
- None => trailbase::new_client(&state.trailbase_url)?,
+ Some(t) => trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, t)?,
+ None => trailbase::new_client(&state.http_client, &state.trailbase_url)?,
};
// Find the category by name
diff --git a/src/handlers/user.rs b/src/handlers/user.rs
@@ -31,8 +31,8 @@ pub async fn show_user(
) -> Result<Html<String>, AppError> {
let tokens = get_tokens_from_cookies(&jar);
let client = match &tokens {
- Some(t) => trailbase::client_with_tokens(&state.trailbase_url, t)?,
- None => trailbase::new_client(&state.trailbase_url)?,
+ Some(t) => trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, t)?,
+ None => trailbase::new_client(&state.http_client, &state.trailbase_url)?,
};
// Get user profile
diff --git a/src/handlers/vote.rs b/src/handlers/vote.rs
@@ -55,7 +55,7 @@ pub async fn vote_story(
let tokens = get_tokens_from_cookies(&jar)
.ok_or_else(|| AppError::Unauthorized("Login required to vote".to_string()))?;
- let client = trailbase::client_with_tokens(&state.trailbase_url, &tokens)?;
+ let client = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens)?;
let voted = toggle_vote(&client, TARGET_TYPE_STORY, story_id).await?;
let new_score = trailbase::get_story(&client, story_id)
@@ -75,7 +75,7 @@ pub async fn vote_comment(
let tokens = get_tokens_from_cookies(&jar)
.ok_or_else(|| AppError::Unauthorized("Login required to vote".to_string()))?;
- let client = trailbase::client_with_tokens(&state.trailbase_url, &tokens)?;
+ let client = trailbase::client_with_tokens(&state.http_client, &state.trailbase_url, &tokens)?;
let voted = toggle_vote(&client, TARGET_TYPE_COMMENT, comment_id).await?;
let args = trailbase_client::ListArguments::new()
diff --git a/src/trailbase.rs b/src/trailbase.rs
@@ -1,5 +1,9 @@
+use std::borrow::Cow;
+
+use reqwest::header::HeaderMap;
+use reqwest::Method;
use serde::{Deserialize, Serialize};
-use trailbase_client::{Client, ClientOptions, CompareOp, Filter, ListArguments, Pagination, Tokens};
+use trailbase_client::{Client, ClientOptions, CompareOp, Filter, ListArguments, Pagination, Tokens, Transport};
pub use crate::trailbase_types::{Category, Story, Comment, Vote, UserProfile, StoryCategory};
@@ -7,6 +11,49 @@ pub use crate::trailbase_types::{Category, Story, Comment, Vote, UserProfile, St
pub const TARGET_TYPE_STORY: i64 = 1;
pub const TARGET_TYPE_COMMENT: i64 = 2;
+/// Transport that shares a single reqwest::Client connection pool across
+/// all TrailBase clients, avoiding per-request TCP connection setup.
+struct SharedTransport {
+ client: reqwest::Client,
+ url: url::Url,
+}
+
+impl SharedTransport {
+ fn new(client: reqwest::Client, url: url::Url) -> Self {
+ Self { client, url }
+ }
+}
+
+#[async_trait::async_trait]
+impl Transport for SharedTransport {
+ async fn fetch(
+ &self,
+ path: &str,
+ headers: HeaderMap,
+ method: Method,
+ body: Option<Vec<u8>>,
+ query_params: Option<&[(Cow<'static, str>, Cow<'static, str>)]>,
+ ) -> Result<http::Response<reqwest::Body>, trailbase_client::Error> {
+ let mut url = self.url.clone();
+ url.set_path(path);
+
+ if let Some(query_params) = query_params {
+ let mut params = url.query_pairs_mut();
+ for (key, value) in query_params {
+ params.append_pair(key, value);
+ }
+ }
+
+ let mut builder = self.client.request(method, url).headers(headers);
+ if let Some(body) = body {
+ builder = builder.body(body);
+ }
+ let request = builder.build()?;
+
+ Ok(self.client.execute(request).await?.into())
+ }
+}
+
#[derive(Debug, thiserror::Error)]
pub enum ClientError {
#[error("TrailBase error: {0}")]
@@ -156,20 +203,29 @@ fn base64_decode(input: &str) -> Option<Vec<u8>> {
Some(output)
}
-/// Create a new unauthenticated client
-pub fn new_client(base_url: &str) -> Result<Client, ClientError> {
- Ok(Client::new(base_url, None)?)
+fn make_transport(http_client: &reqwest::Client, base_url: &str) -> Result<Box<dyn Transport + Send + Sync>, ClientError> {
+ let url: url::Url = base_url.parse().map_err(|e| ClientError::Auth(format!("Invalid URL: {}", e)))?;
+ Ok(Box::new(SharedTransport::new(http_client.clone(), url)))
+}
+
+/// Create a new unauthenticated client using the shared connection pool
+pub fn new_client(http_client: &reqwest::Client, base_url: &str) -> Result<Client, ClientError> {
+ let opts = ClientOptions {
+ tokens: None,
+ transport: Some(make_transport(http_client, base_url)?),
+ };
+ Ok(Client::new(base_url, Some(opts))?)
}
-/// Create a client with existing tokens (from cookies)
-pub fn client_with_tokens(base_url: &str, tokens: &StoredTokens) -> Result<Client, ClientError> {
+/// Create a client with existing tokens using the shared connection pool
+pub fn client_with_tokens(http_client: &reqwest::Client, base_url: &str, tokens: &StoredTokens) -> Result<Client, ClientError> {
let opts = ClientOptions {
tokens: Some(Tokens {
auth_token: tokens.auth_token.clone(),
refresh_token: tokens.refresh_token.clone(),
csrf_token: None,
}),
- transport: None,
+ transport: Some(make_transport(http_client, base_url)?),
};
Ok(Client::new(base_url, Some(opts))?)
}