various: shared HTTP client with connection pooling

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Id13c17e9352da970a289f4e3ad909c5b6a6a6964
This commit is contained in:
raf 2026-02-18 17:20:29 +03:00
commit 7ee9ee1159
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
6 changed files with 64 additions and 20 deletions

16
src/http.rs Normal file
View file

@ -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")
}

View file

@ -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

View file

@ -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<Arc<reqwest::Client>> =
std::sync::LazyLock::new(|| Arc::new(http::create_http_client()));
static RATE_LIMITER: std::sync::LazyLock<Arc<RateLimiter>> =
std::sync::LazyLock::new(|| Arc::new(RateLimiter::new(None)));
pub fn get_http_client() -> Arc<reqwest::Client> {
HTTP_CLIENT.clone()
}
pub fn create_platform(
platform: &str,
api_key: Option<String>,
@ -33,9 +40,21 @@ fn create_client(
api_key: Option<String>,
) -> Result<Box<dyn PlatformClient>> {
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}"

View file

@ -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<Client>,
api_key: Option<String>,
}
impl CurseForgePlatform {
pub fn new(api_key: Option<String>) -> Self {
Self {
client: Client::new(),
client: Arc::new(Client::new()),
api_key,
}
}
pub fn with_client(client: Arc<Client>, api_key: Option<String>) -> Self {
Self { client, api_key }
}
fn get_headers(&self) -> Result<reqwest::header::HeaderMap> {
let mut headers = reqwest::header::HeaderMap::new();

View file

@ -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<String>) -> Self {
pub fn with_client(client: Arc<Client>, token: Option<String>) -> Self {
Self {
client: Client::new(),
client: (*client).clone(),
token,
}
}

View file

@ -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<Client>,
}
impl ModrinthPlatform {
pub fn new() -> Self {
Self {
client: Client::new(),
client: Arc::new(Client::new()),
}
}
pub fn with_client(client: Arc<Client>) -> Self {
Self { client }
}
async fn request_project_url(&self, url: &str) -> Result<Project> {
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)
}