Skip to content
This repository was archived by the owner on Jan 21, 2026. It is now read-only.

Commit 10f4fc8

Browse files
committed
allow specifying http headers, proxy and user agent
1 parent 99d9595 commit 10f4fc8

File tree

5 files changed

+154
-23
lines changed

5 files changed

+154
-23
lines changed

Cargo.lock

Lines changed: 42 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ flate2 = "1.1.1"
2626
futures = "0.3.31"
2727
indicatif = { version = "0.17.11", optional = true }
2828
regex = { version = "1.11.1", default-features = false, features = ["std", "unicode-case", "unicode-perl"] }
29-
reqwest = { version = "0.12.15", default-features = false, features = ["rustls-tls", "stream", "http2", "blocking", "json"] }
29+
reqwest = { version = "0.12.15", default-features = false, features = ["rustls-tls", "stream", "http2", "blocking", "json", "socks"] }
3030
serde = { version = "1.0.219", features = ["derive"] }
3131
serde_json = "1.0.140"
3232
tar = "0.4.44"

src/bin/soar-dl/cli.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,16 @@ pub struct Args {
7676
/// Quiet mode
7777
#[arg(required = false, long, short)]
7878
pub quiet: bool,
79+
80+
/// Set proxy
81+
#[arg(required = false, long)]
82+
pub proxy: Option<String>,
83+
84+
/// Set request headers
85+
#[arg(required = false, long, short = 'H')]
86+
pub header: Option<Vec<String>>,
87+
88+
/// Set user agent
89+
#[arg(required = false, long, short = 'A')]
90+
pub user_agent: Option<String>,
7991
}

src/bin/soar-dl/main.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use std::sync::Arc;
1+
use std::{error::Error, sync::Arc};
22

33
use clap::Parser;
44
use cli::Args;
55
use download_manager::DownloadManager;
66
use progress::create_progress_bar;
7+
use soar_dl::http_client::{configure_http_client, create_http_header_map};
78

89
mod cli;
910
mod download_manager;
@@ -19,6 +20,28 @@ async fn main() {
1920
let progress_bar = create_progress_bar();
2021
let progress_callback = Arc::new(move |state| progress::handle_progress(state, &progress_bar));
2122

23+
let proxy = args.proxy.clone();
24+
let user_agent = args.user_agent.clone();
25+
let header = args.header.clone();
26+
27+
if let Err(err) = configure_http_client(|config| {
28+
config.proxy = proxy;
29+
30+
if let Some(user_agent) = user_agent {
31+
config.user_agent = Some(user_agent);
32+
}
33+
34+
if let Some(headers) = header {
35+
config.headers = Some(create_http_header_map(headers));
36+
}
37+
}) {
38+
error!("Error configuring HTTP client: {}", err);
39+
if let Some(source) = err.source() {
40+
error!(" Caused by: {}", source);
41+
}
42+
std::process::exit(1);
43+
};
44+
2245
let manager = DownloadManager::new(args, progress_callback);
2346
manager.execute().await;
2447
}

src/http_client.rs

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,98 @@
1-
use std::{sync::LazyLock, time::Duration};
1+
use std::{
2+
str::FromStr,
3+
sync::{Arc, LazyLock, RwLock},
4+
};
25

3-
use reqwest::{header::HeaderMap, Client};
6+
use reqwest::{
7+
header::{HeaderMap, HeaderName, HeaderValue},
8+
Client,
9+
};
410

5-
pub static SHARED_CLIENT: LazyLock<Client> = LazyLock::new(|| {
6-
Client::builder()
7-
.user_agent("pkgforge/soar")
8-
.build()
9-
.expect("failed to build default client")
11+
struct SharedClient {
12+
client: Client,
13+
config: ClientConfig,
14+
}
15+
16+
static SHARED_CLIENT_STATE: LazyLock<Arc<RwLock<SharedClient>>> = LazyLock::new(|| {
17+
let config = ClientConfig::default();
18+
let client = config.build().expect("failed to build default client");
19+
20+
Arc::new(RwLock::new(SharedClient { client, config }))
1021
});
1122

23+
pub static SHARED_CLIENT: LazyLock<Client> =
24+
LazyLock::new(|| SHARED_CLIENT_STATE.read().unwrap().client.clone());
25+
26+
#[derive(Clone, Debug)]
1227
pub struct ClientConfig {
1328
pub user_agent: Option<String>,
1429
pub headers: Option<HeaderMap>,
1530
pub proxy: Option<String>,
16-
pub timeout: Option<Duration>,
31+
}
32+
33+
impl Default for ClientConfig {
34+
fn default() -> Self {
35+
Self {
36+
user_agent: Some("pkgforge/soar".to_string()),
37+
headers: None,
38+
proxy: None,
39+
}
40+
}
1741
}
1842

1943
impl ClientConfig {
20-
pub fn build(self) -> Result<Client, reqwest::Error> {
44+
pub fn build(&self) -> Result<Client, reqwest::Error> {
2145
let mut builder = Client::builder();
2246

23-
if let Some(user_agent) = self.user_agent {
47+
if let Some(user_agent) = &self.user_agent {
2448
builder = builder.user_agent(user_agent);
2549
}
2650

27-
if let Some(headers) = self.headers {
28-
builder = builder.default_headers(headers);
29-
}
30-
31-
if let Some(proxy_url) = self.proxy {
32-
builder = builder.proxy(reqwest::Proxy::all(&proxy_url)?);
51+
if let Some(headers) = &self.headers {
52+
builder = builder.default_headers(headers.clone());
3353
}
3454

35-
if let Some(timeout) = self.timeout {
36-
builder = builder.timeout(timeout);
55+
if let Some(proxy_url) = &self.proxy {
56+
builder = builder.proxy(reqwest::Proxy::all(proxy_url)?);
3757
}
3858

3959
builder.build()
4060
}
4161
}
62+
63+
pub fn create_http_header_map(headers: Vec<String>) -> HeaderMap {
64+
let mut header_map = HeaderMap::new();
65+
66+
for header in headers {
67+
let parts: Vec<&str> = header.splitn(2, ':').collect();
68+
if parts.len() == 2 {
69+
let key = parts[0].trim();
70+
let value = parts[1].trim();
71+
72+
if let Ok(header_name) = HeaderName::from_str(key) {
73+
if let Ok(header_value) = HeaderValue::from_str(value) {
74+
header_map.insert(header_name, header_value);
75+
}
76+
}
77+
}
78+
}
79+
80+
header_map
81+
}
82+
83+
pub fn configure_http_client<F>(updater: F) -> Result<(), reqwest::Error>
84+
where
85+
F: FnOnce(&mut ClientConfig),
86+
{
87+
let mut state = SHARED_CLIENT_STATE.write().unwrap();
88+
let mut new_config = state.config.clone();
89+
90+
updater(&mut new_config);
91+
92+
let new_client = new_config.build()?;
93+
94+
state.client = new_client;
95+
state.config = new_config;
96+
97+
Ok(())
98+
}

0 commit comments

Comments
 (0)