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)]
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}