From 7ee9ee1159d1bdb9a2ba2ced9fa68f09b0ec4089 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 18 Feb 2026 17:20:29 +0300 Subject: [PATCH] various: shared HTTP client with connection pooling Signed-off-by: NotAShelf Change-Id: Id13c17e9352da970a289f4e3ad909c5b6a6a6964 --- src/http.rs | 16 ++++++++++++++++ src/main.rs | 5 +---- src/platform.rs | 27 +++++++++++++++++++++++---- src/platform/curseforge.rs | 10 +++++++--- src/platform/github.rs | 6 +++--- src/platform/modrinth.rs | 20 ++++++++++++++------ 6 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 src/http.rs diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..17ba51f --- /dev/null +++ b/src/http.rs @@ -0,0 +1,16 @@ +use std::time::Duration; + +use reqwest::Client; + +pub fn create_http_client() -> Client { + Client::builder() + .pool_max_idle_per_host(10) + .pool_idle_timeout(Duration::from_secs(30)) + .tcp_keepalive(Duration::from_secs(60)) + .tcp_nodelay(true) + .connect_timeout(Duration::from_secs(15)) + .timeout(Duration::from_secs(30)) + .user_agent("Pakker/0.1.0") + .build() + .expect("Failed to build HTTP client") +} diff --git a/src/main.rs b/src/main.rs index 3fdbf3a..aa7a529 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod error; mod export; mod fetch; mod git; +mod http; mod ipc; mod model; mod platform; @@ -23,8 +24,6 @@ use clap::Parser; use cli::{Cli, Commands}; use error::PakkerError; -use crate::rate_limiter::RateLimiter; - #[tokio::main] async fn main() -> Result<(), PakkerError> { let cli = Cli::parse(); @@ -48,8 +47,6 @@ async fn main() -> Result<(), PakkerError> { let lockfile_path = working_dir.join("pakker-lock.json"); let config_path = working_dir.join("pakker.json"); - let _rate_limiter = std::sync::Arc::new(RateLimiter::new(None)); - match cli.command { Commands::Init(args) => { cli::commands::init::execute(args, &lockfile_path, &config_path).await diff --git a/src/platform.rs b/src/platform.rs index e64ebc1..c9e0589 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -10,11 +10,18 @@ pub use github::GitHubPlatform; pub use modrinth::ModrinthPlatform; pub use traits::PlatformClient; -use crate::{error::Result, rate_limiter::RateLimiter}; +use crate::{error::Result, http, rate_limiter::RateLimiter}; + +static HTTP_CLIENT: std::sync::LazyLock> = + std::sync::LazyLock::new(|| Arc::new(http::create_http_client())); static RATE_LIMITER: std::sync::LazyLock> = std::sync::LazyLock::new(|| Arc::new(RateLimiter::new(None))); +pub fn get_http_client() -> Arc { + HTTP_CLIENT.clone() +} + pub fn create_platform( platform: &str, api_key: Option, @@ -33,9 +40,21 @@ fn create_client( api_key: Option, ) -> Result> { match platform { - "modrinth" => Ok(Box::new(ModrinthPlatform::new())), - "curseforge" => Ok(Box::new(CurseForgePlatform::new(api_key))), - "github" => Ok(Box::new(GitHubPlatform::new(api_key))), + "modrinth" => { + Ok(Box::new(ModrinthPlatform::with_client(get_http_client()))) + }, + "curseforge" => { + Ok(Box::new(CurseForgePlatform::with_client( + get_http_client(), + api_key, + ))) + }, + "github" => { + Ok(Box::new(GitHubPlatform::with_client( + get_http_client(), + api_key, + ))) + }, _ => { Err(crate::error::PakkerError::ConfigError(format!( "Unknown platform: {platform}" diff --git a/src/platform/curseforge.rs b/src/platform/curseforge.rs index 368f917..d36efee 100644 --- a/src/platform/curseforge.rs +++ b/src/platform/curseforge.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use reqwest::Client; @@ -20,18 +20,22 @@ const LOADER_VERSION_TYPE_ID: i32 = 68441; const DEPENDENCY_RELATION_TYPE_REQUIRED: u32 = 3; pub struct CurseForgePlatform { - client: Client, + client: Arc, api_key: Option, } impl CurseForgePlatform { pub fn new(api_key: Option) -> Self { Self { - client: Client::new(), + client: Arc::new(Client::new()), api_key, } } + pub fn with_client(client: Arc, api_key: Option) -> Self { + Self { client, api_key } + } + fn get_headers(&self) -> Result { let mut headers = reqwest::header::HeaderMap::new(); diff --git a/src/platform/github.rs b/src/platform/github.rs index cfc4f65..0c7a735 100644 --- a/src/platform/github.rs +++ b/src/platform/github.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use regex::Regex; @@ -20,9 +20,9 @@ pub struct GitHubPlatform { } impl GitHubPlatform { - pub fn new(token: Option) -> Self { + pub fn with_client(client: Arc, token: Option) -> Self { Self { - client: Client::new(), + client: (*client).clone(), token, } } diff --git a/src/platform/modrinth.rs b/src/platform/modrinth.rs index 31a8bd2..69f81a2 100644 --- a/src/platform/modrinth.rs +++ b/src/platform/modrinth.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use reqwest::Client; @@ -14,16 +14,20 @@ use crate::{ const MODRINTH_API_BASE: &str = "https://api.modrinth.com/v2"; pub struct ModrinthPlatform { - client: Client, + client: Arc, } impl ModrinthPlatform { pub fn new() -> Self { Self { - client: Client::new(), + client: Arc::new(Client::new()), } } + pub fn with_client(client: Arc) -> Self { + Self { client } + } + async fn request_project_url(&self, url: &str) -> Result { let response = self.client.get(url).send().await?; if !response.status().is_success() { @@ -295,13 +299,17 @@ struct ModrinthDependency { #[cfg(test)] mod tests { + use std::sync::Arc; + use reqwest::Client; use super::*; impl ModrinthPlatform { - fn with_client(client: Client) -> Self { - Self { client } + fn with_raw_client(client: Client) -> Self { + Self { + client: Arc::new(client), + } } } @@ -309,7 +317,7 @@ mod tests { -> (ModrinthPlatform, mockito::ServerGuard) { let server = mockito::Server::new_async().await; let client = Client::new(); - let platform = ModrinthPlatform::with_client(client); + let platform = ModrinthPlatform::with_raw_client(client); (platform, server) }