Skip to main content

ccxt_core/http_client/
builder.rs

1use crate::circuit_breaker::CircuitBreaker;
2use crate::error::{Error, Result};
3use crate::rate_limiter::RateLimiter;
4use crate::retry_strategy::RetryStrategy;
5use reqwest::Client;
6use std::sync::Arc;
7
8use super::config::HttpConfig;
9
10/// HTTP client with retry and rate limiting support
11#[derive(Debug, Clone)]
12pub struct HttpClient {
13    client: Client,
14    config: HttpConfig,
15    rate_limiter: Option<RateLimiter>,
16    retry_strategy: RetryStrategy,
17    circuit_breaker: Option<Arc<CircuitBreaker>>,
18}
19
20impl HttpClient {
21    /// Creates a new HTTP client with the given configuration.
22    ///
23    /// # Arguments
24    ///
25    /// * `config` - HTTP client configuration
26    ///
27    /// # Returns
28    ///
29    /// Returns a `Result` containing the initialized client or an error if client creation fails.
30    ///
31    /// # Errors
32    ///
33    /// Returns an error if:
34    /// - The proxy URL is invalid
35    /// - The HTTP client cannot be built
36    pub fn new(config: HttpConfig) -> Result<Self> {
37        let mut builder = Client::builder()
38            .timeout(config.timeout)
39            .connect_timeout(config.connect_timeout)
40            .pool_max_idle_per_host(config.pool_max_idle_per_host)
41            .pool_idle_timeout(config.pool_idle_timeout)
42            .gzip(true)
43            .user_agent(&config.user_agent);
44
45        if let Some(proxy_config) = &config.proxy {
46            let mut proxy = reqwest::Proxy::all(&proxy_config.url)
47                .map_err(|e| Error::network(format!("Invalid proxy URL: {e}")))?;
48
49            if let (Some(username), Some(password)) =
50                (&proxy_config.username, &proxy_config.password)
51            {
52                proxy = proxy.basic_auth(username, password);
53            }
54            builder = builder.proxy(proxy);
55        }
56
57        let client = builder
58            .build()
59            .map_err(|e| Error::network(format!("Failed to build HTTP client: {e}")))?;
60
61        let retry_strategy = RetryStrategy::new(config.retry_config.clone().unwrap_or_default());
62
63        let circuit_breaker = config
64            .circuit_breaker
65            .as_ref()
66            .map(|cb_config| Arc::new(CircuitBreaker::new(cb_config.clone())));
67
68        Ok(Self {
69            client,
70            config,
71            rate_limiter: None,
72            retry_strategy,
73            circuit_breaker,
74        })
75    }
76
77    /// Creates a new HTTP client with a custom rate limiter.
78    ///
79    /// # Arguments
80    ///
81    /// * `config` - HTTP client configuration
82    /// * `rate_limiter` - Pre-configured rate limiter instance
83    ///
84    /// # Returns
85    ///
86    /// Returns a `Result` containing the initialized client with rate limiter.
87    ///
88    /// # Errors
89    ///
90    /// Returns an error if client creation fails.
91    pub fn new_with_rate_limiter(config: HttpConfig, rate_limiter: RateLimiter) -> Result<Self> {
92        let mut client = Self::new(config)?;
93        client.rate_limiter = Some(rate_limiter);
94        Ok(client)
95    }
96
97    /// Sets the rate limiter for this client.
98    ///
99    /// # Arguments
100    ///
101    /// * `rate_limiter` - Rate limiter instance to use
102    pub fn set_rate_limiter(&mut self, rate_limiter: RateLimiter) {
103        self.rate_limiter = Some(rate_limiter);
104    }
105
106    /// Sets the retry strategy for this client.
107    ///
108    /// # Arguments
109    ///
110    /// * `strategy` - Retry strategy to use for failed requests
111    pub fn set_retry_strategy(&mut self, strategy: RetryStrategy) {
112        self.retry_strategy = strategy;
113    }
114
115    /// Sets the circuit breaker for this client.
116    ///
117    /// # Arguments
118    ///
119    /// * `circuit_breaker` - Circuit breaker instance to use
120    ///
121    /// # Example
122    ///
123    /// ```rust
124    /// use ccxt_core::http_client::{HttpClient, HttpConfig};
125    /// use ccxt_core::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig};
126    /// use std::sync::Arc;
127    ///
128    /// let mut client = HttpClient::new(HttpConfig::default()).unwrap();
129    /// let cb = Arc::new(CircuitBreaker::new(CircuitBreakerConfig::default()));
130    /// client.set_circuit_breaker(cb);
131    /// ```
132    pub fn set_circuit_breaker(&mut self, circuit_breaker: Arc<CircuitBreaker>) {
133        self.circuit_breaker = Some(circuit_breaker);
134    }
135
136    /// Returns a reference to the circuit breaker, if configured.
137    ///
138    /// This can be used to check the circuit breaker state or manually
139    /// record success/failure.
140    pub fn circuit_breaker(&self) -> Option<&Arc<CircuitBreaker>> {
141        self.circuit_breaker.as_ref()
142    }
143
144    /// Returns a reference to current HTTP configuration.
145    pub fn config(&self) -> &HttpConfig {
146        &self.config
147    }
148
149    /// Updates the HTTP configuration.
150    ///
151    /// # Arguments
152    ///
153    /// * `config` - New configuration to use
154    pub fn set_config(&mut self, config: HttpConfig) {
155        self.config = config;
156    }
157
158    /// Internal: Returns reference to the underlying reqwest client.
159    pub(crate) fn client(&self) -> &Client {
160        &self.client
161    }
162
163    /// Internal: Returns reference to the rate limiter, if configured.
164    pub(crate) fn rate_limiter(&self) -> Option<&RateLimiter> {
165        self.rate_limiter.as_ref()
166    }
167
168    /// Internal: Returns reference to the retry strategy.
169    pub(crate) fn retry_strategy(&self) -> &RetryStrategy {
170        &self.retry_strategy
171    }
172}