monoio_netreq/http/
client.rs

1use std::rc::Rc;
2use std::time::Duration;
3
4use http::{HeaderMap, Request, Uri};
5use monoio::net::TcpStream;
6use monoio_http::common::body::HttpBody;
7use monoio_transports::connectors::TlsConnector;
8use monoio_transports::connectors::{Connector, TcpConnector, TlsStream};
9use monoio_transports::http::HttpConnector;
10
11use crate::{
12    error::{Error, Result, TransportError},
13    key::PoolKey,
14    Protocol,
15    request::HttpRequest,
16    response::Response,
17    apply_parameter_from_config,
18};
19
20enum HttpConnectorType {
21    HTTP(HttpConnector<TcpConnector, PoolKey, TcpStream>),
22    HTTPS(HttpConnector<TlsConnector<TcpConnector>, PoolKey, TlsStream<TcpStream>>),
23}
24
25#[derive(Default, Clone, Debug)]
26struct ClientConfig {
27    default_headers: Rc<HeaderMap>,
28}
29
30struct ClientInner {
31    config: ClientConfig,
32    http_connector: HttpConnectorType,
33}
34
35pub struct MonoioClient {
36    inner: Rc<ClientInner>,
37}
38
39impl MonoioClient {
40    pub fn builder() -> ClientBuilder {
41        ClientBuilder::default()
42    }
43}
44
45impl Clone for MonoioClient {
46    fn clone(&self) -> Self {
47        Self {
48            inner: self.inner.clone(),
49        }
50    }
51}
52
53#[derive(Default, Clone)]
54struct ClientBuilderConfig {
55    protocol: Protocol,
56    enable_https: bool,
57    pool_disabled: bool,
58    max_idle_connections: Option<usize>,
59    idle_timeout_duration: Option<Duration>,
60    read_timeout: Option<Duration>,
61    initial_max_streams: Option<usize>,
62    max_concurrent_streams: Option<u32>,
63    default_headers: HeaderMap,
64}
65
66#[derive(Default)]
67pub struct ClientBuilder {
68    build_config: ClientBuilderConfig,
69}
70
71impl ClientBuilder {
72    /// Sets default headers that will be applied to all requests made through this http.
73    /// These headers can be overridden by request-specific headers.
74    pub fn default_headers(mut self, val: HeaderMap) -> Self {
75        self.build_config.default_headers = val;
76        self
77    }
78
79    /// Disables the connection pooling feature.
80    /// When disabled, a new connection will be created for each request.
81    pub fn disable_connection_pool(mut self) -> Self {
82        self.build_config.pool_disabled = true;
83        self
84    }
85
86    /// Sets the maximum number of idle connections that can be kept in the connection pool.
87    /// Once this limit is reached, older idle connections will be dropped.
88    /// Applicable on feature pool, pool-hyper or pool-native-tls only
89    pub fn max_idle_connections(mut self, val: usize) -> Self {
90        self.build_config.max_idle_connections = Some(val);
91        self
92    }
93
94    /// Sets the duration after which an idle connection in the pool will be closed.
95    /// The timeout is specified in seconds.
96    /// Applicable on feature pool, pool-hyper or pool-native-tls only
97    pub fn idle_connection_timeout(mut self, val: u64) -> Self {
98        self.build_config.idle_timeout_duration = Some(Duration::from_secs(val));
99        self
100    }
101
102    /// Sets the read timeout for the HTTP/1.1 connections, has no effect on HTTP/2 connections
103    /// After this duration elapses without receiving any data, the read operation will fail.
104    /// The timeout value is specified in seconds.
105    pub fn set_read_timeout(mut self, val: u64) -> Self {
106        self.build_config.read_timeout = Some(Duration::from_secs(val));
107        self
108    }
109
110    /// Sets the initial maximum number of streams that can be created when a new HTTP/2 connection is established.
111    /// This value affects the initial stream capacity allocation and can be adjusted based on expected concurrent requests.
112    pub fn initial_max_streams(mut self, val: usize) -> Self {
113        self.build_config.initial_max_streams = Some(val);
114        self
115    }
116
117    /// Sets the maximum number of concurrent HTTP/2 streams allowed per connection.
118    /// Default is 100. Higher values allow more parallel requests on a single connection,
119    /// but may require more memory and processing resources.
120    pub fn max_concurrent_streams(mut self, val: u32) -> Self {
121        self.build_config.max_concurrent_streams = Some(val);
122        self
123    }
124
125    /// Forces the http to use HTTP/1.1 protocol only, disabling HTTP/2 support.
126    /// Useful when you need to ensure HTTP/1.1 compatibility.
127    pub fn http1_only(mut self) -> Self {
128        self.build_config.protocol = Protocol::Http1;
129        self
130    }
131
132    /// Enables HTTP/2 prior knowledge mode, assuming all connections will use HTTP/2.
133    /// This skips the HTTP/1.1 -> HTTP/2 upgrade process.
134    pub fn http2_prior_knowledge(mut self) -> Self {
135        self.build_config.protocol = Protocol::Http2;
136        self
137    }
138
139    /// Enables HTTPS/TLS support for secure connections.
140    /// Must be called to make HTTPS requests.
141    pub fn enable_https(mut self) -> Self {
142        self.build_config.enable_https = true;
143        self
144    }
145}
146
147impl ClientBuilder {
148    pub fn build(self) -> MonoioClient {
149        let build_config = self.build_config.clone();
150        let config = ClientConfig::default();
151        let tcp_connector = TcpConnector::default();
152
153        let mut http_connector = if build_config.enable_https {
154            // TLS implemented Connector
155            // Client will negotiate the connection type using ALPN, no need to set Protocols explicitly
156            let alpn = match build_config.protocol {
157                Protocol::Http1 => vec!["http/1.1"],
158                Protocol::Http2 => vec!["h2"],
159                Protocol::Auto => vec!["http/1.1", "h2"],
160            };
161
162            let tls_connector = TlsConnector::new_with_tls_default(tcp_connector, Some(alpn));
163
164            #[cfg(feature = "pool")]
165            let https_connector = HttpConnectorType::HTTPS(HttpConnector::new_with_pool_options(
166                tls_connector,
167                build_config.max_idle_connections,
168                build_config.idle_timeout_duration,
169            ));
170            #[cfg(not(feature = "pool"))]
171            let https_connector = HttpConnectorType::HTTPS(HttpConnector::new(tls_connector));
172
173            https_connector
174        } else {
175            // Default TCP Connector without TLS support
176            #[cfg(not(feature = "pool"))]
177            let mut connector = HttpConnector::new(tcp_connector);
178            #[cfg(feature = "pool")]
179            let mut connector = HttpConnector::new_with_pool_options(
180                tcp_connector,
181                build_config.max_idle_connections,
182                build_config.idle_timeout_duration,
183            );
184
185            if build_config.protocol.is_protocol_h1() {
186                connector.set_http1_only();
187            }
188
189            // Assumes prior http2 knowledge
190            if build_config.protocol.is_protocol_h2() {
191                connector.set_http2_only();
192            }
193
194            HttpConnectorType::HTTP(connector)
195        };
196
197        if let Some(val) = build_config.initial_max_streams {
198            apply_parameter_from_config!(
199                http_connector,
200                h2_builder().initial_max_send_streams(val)
201            );
202        }
203
204        if let Some(val) = build_config.max_concurrent_streams {
205            apply_parameter_from_config!(http_connector, h2_builder().max_concurrent_streams(val));
206        }
207
208        apply_parameter_from_config!(http_connector, set_read_timeout(build_config.read_timeout));
209
210        let inner = Rc::new(ClientInner {
211            config,
212            http_connector,
213        });
214
215        MonoioClient { inner }
216    }
217}
218
219impl MonoioClient {
220    /// Returns a new http request with default parameters
221    pub fn make_request(&self) -> HttpRequest<MonoioClient> {
222        let mut request = HttpRequest::new(self.clone());
223        for (key, val) in self.inner.config.default_headers.iter() {
224            request = request.set_header(key, val)
225        }
226
227        request
228    }
229
230    pub(crate) async fn send_request(
231        &self,
232        req: Request<HttpBody>,
233        uri: Uri,
234    ) -> Result<Response<HttpBody>> {
235        let key = uri.try_into().map_err(|e| Error::UriKeyError(e))?;
236
237        let (response, _) = match self.inner.http_connector
238        {
239            HttpConnectorType::HTTP(ref connector) => {
240                let mut conn = connector
241                    .connect(key)
242                    .await
243                    .map_err(|e| TransportError::HttpConnectorError(e))?;
244                conn.send_request(req).await
245            }
246
247            HttpConnectorType::HTTPS(ref connector) => {
248                let mut conn = connector
249                    .connect(key)
250                    .await
251                    .map_err(|e| TransportError::HttpConnectorError(e))?;
252                conn.send_request(req).await
253            }
254        };
255
256        response.map_err(|e| Error::HttpResponseError(e))
257    }
258}