Skip to main content

yt_dlp/utils/
http.rs

1//! HTTP utilities and connection pooling.
2//!
3//! This module provides HTTP client utilities with connection pooling
4//! and optimal configuration for the library.
5
6use std::fmt;
7use std::sync::Arc;
8use std::time::Duration;
9
10use reqwest::Client;
11use reqwest::header::HeaderMap;
12
13use crate::client::proxy::ProxyConfig;
14
15// HTTP connection pool configuration
16const HTTP_POOL_IDLE_TIMEOUT_SECS: u64 = 90;
17const HTTP_POOL_MAX_IDLE_PER_HOST: usize = 32;
18const HTTP_TCP_KEEPALIVE_SECS: u64 = 60;
19const REQUEST_TIMEOUT_SECS: u64 = 60;
20const CONNECT_TIMEOUT_SECS: u64 = 10;
21
22const DEFAULT_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
23
24/// Configuration for building an HTTP client.
25#[derive(Debug, Clone, Default)]
26pub struct HttpClientConfig<'a> {
27    pub proxy: Option<&'a ProxyConfig>,
28    pub timeout: Option<Duration>,
29    pub user_agent: Option<String>,
30    pub default_headers: Option<HeaderMap>,
31    pub http2_adaptive_window: bool,
32}
33
34impl fmt::Display for HttpClientConfig<'_> {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(
37            f,
38            "HttpClientConfig(proxy={}, timeout={}, http2={})",
39            self.proxy.is_some(),
40            self.timeout
41                .map_or("default".to_string(), |d| format!("{}s", d.as_secs())),
42            self.http2_adaptive_window
43        )
44    }
45}
46
47/// Creates a new HTTP client with optimal pooling configuration.
48///
49/// # Arguments
50///
51/// * `config` - Client configuration (proxy, timeout, headers, etc.)
52///
53/// # Returns
54///
55/// An Arc-wrapped HTTP client configured with connection pooling
56///
57/// # Errors
58///
59/// Returns an error if the HTTP client cannot be built
60pub fn build_http_client(config: HttpClientConfig) -> crate::error::Result<Arc<Client>> {
61    let timeout = config.timeout.unwrap_or(Duration::from_secs(REQUEST_TIMEOUT_SECS));
62
63    tracing::debug!(
64        has_proxy = config.proxy.is_some(),
65        timeout_secs = timeout.as_secs(),
66        pool_idle_timeout_secs = HTTP_POOL_IDLE_TIMEOUT_SECS,
67        max_idle_per_host = HTTP_POOL_MAX_IDLE_PER_HOST,
68        http2 = config.http2_adaptive_window,
69        "⚙️ Creating HTTP client with connection pooling"
70    );
71
72    let mut builder = Client::builder()
73        .timeout(timeout)
74        .connect_timeout(Duration::from_secs(CONNECT_TIMEOUT_SECS))
75        .pool_idle_timeout(Duration::from_secs(HTTP_POOL_IDLE_TIMEOUT_SECS))
76        .pool_max_idle_per_host(HTTP_POOL_MAX_IDLE_PER_HOST)
77        .tcp_keepalive(Duration::from_secs(HTTP_TCP_KEEPALIVE_SECS))
78        .tcp_nodelay(true)
79        .user_agent(config.user_agent.as_deref().unwrap_or(DEFAULT_USER_AGENT));
80
81    if config.http2_adaptive_window {
82        builder = builder.http2_adaptive_window(true);
83    }
84
85    if let Some(headers) = config.default_headers {
86        builder = builder.default_headers(headers);
87    }
88
89    if let Some(proxy_config) = config.proxy {
90        match proxy_config.to_reqwest_proxy() {
91            Ok(proxy) => {
92                tracing::debug!("⚙️ Adding proxy configuration to HTTP client");
93                builder = builder.proxy(proxy);
94            }
95            Err(e) => {
96                tracing::warn!(error = %e, "Proxy configuration failed — client will connect directly without proxy");
97            }
98        }
99    }
100
101    let client = builder.build()?;
102
103    tracing::debug!("✅ HTTP client created successfully");
104
105    Ok(Arc::new(client))
106}
107
108/// Creates a new HTTP client with optimal pooling configuration (simple API).
109///
110/// # Arguments
111///
112/// * `proxy` - Optional proxy configuration
113///
114/// # Returns
115///
116/// An Arc-wrapped HTTP client configured with connection pooling
117///
118/// # Errors
119///
120/// Returns an error if the HTTP client cannot be built
121pub fn create_http_client(proxy: Option<&ProxyConfig>) -> crate::error::Result<Arc<Client>> {
122    build_http_client(HttpClientConfig {
123        proxy,
124        ..Default::default()
125    })
126}