Skip to main content

ringline_http/
client.rs

1//! Top-level HTTP client with protocol dispatch.
2
3use std::net::SocketAddr;
4
5use crate::error::HttpError;
6use crate::h1_conn::H1Conn;
7use crate::h2_conn::H2AsyncConn;
8use crate::request::RequestBuilder;
9use crate::response::Response;
10use crate::streaming::StreamingResponse;
11
12enum ConnectionInner {
13    H2(Box<H2AsyncConn>),
14    H1(H1Conn),
15}
16
17/// HTTP client supporting both HTTP/2 and HTTP/1.1 connections.
18///
19/// # Example
20///
21/// ```rust,ignore
22/// let mut client = HttpClient::connect_h2(addr, "example.com").await?;
23/// let resp = client.get("/api/data").header("authorization", "Bearer tok").send().await?;
24/// assert_eq!(resp.status(), 200);
25/// ```
26pub struct HttpClient {
27    inner: ConnectionInner,
28    host: String,
29}
30
31impl HttpClient {
32    /// Connect using HTTP/2 over TLS.
33    pub async fn connect_h2(addr: SocketAddr, host: &str) -> Result<Self, HttpError> {
34        let h2 = H2AsyncConn::connect(addr, host).await?;
35        Ok(Self {
36            inner: ConnectionInner::H2(Box::new(h2)),
37            host: host.to_string(),
38        })
39    }
40
41    /// Connect using HTTP/2 over TLS with a timeout (milliseconds).
42    pub async fn connect_h2_with_timeout(
43        addr: SocketAddr,
44        host: &str,
45        timeout_ms: u64,
46    ) -> Result<Self, HttpError> {
47        let h2 = H2AsyncConn::connect_with_timeout(addr, host, timeout_ms).await?;
48        Ok(Self {
49            inner: ConnectionInner::H2(Box::new(h2)),
50            host: host.to_string(),
51        })
52    }
53
54    /// Connect using HTTP/1.1 over TLS.
55    pub async fn connect_h1(addr: SocketAddr, host: &str) -> Result<Self, HttpError> {
56        let h1 = H1Conn::connect_tls(addr, host).await?;
57        Ok(Self {
58            inner: ConnectionInner::H1(h1),
59            host: host.to_string(),
60        })
61    }
62
63    /// Connect using HTTP/1.1 over plaintext TCP.
64    pub async fn connect_h1_plain(addr: SocketAddr, host: &str) -> Result<Self, HttpError> {
65        let h1 = H1Conn::connect_plain(addr, host).await?;
66        Ok(Self {
67            inner: ConnectionInner::H1(h1),
68            host: host.to_string(),
69        })
70    }
71
72    /// Build a GET request.
73    pub fn get(&mut self, path: &str) -> RequestBuilder<'_> {
74        RequestBuilder::new(self, "GET", path)
75    }
76
77    /// Build a POST request.
78    pub fn post(&mut self, path: &str) -> RequestBuilder<'_> {
79        RequestBuilder::new(self, "POST", path)
80    }
81
82    /// Build a PUT request.
83    pub fn put(&mut self, path: &str) -> RequestBuilder<'_> {
84        RequestBuilder::new(self, "PUT", path)
85    }
86
87    /// Build a DELETE request.
88    pub fn delete(&mut self, path: &str) -> RequestBuilder<'_> {
89        RequestBuilder::new(self, "DELETE", path)
90    }
91
92    /// Send a request with the given method, path, headers, and optional body.
93    pub(crate) async fn send_request(
94        &mut self,
95        method: &str,
96        path: &str,
97        extra_headers: &[(&str, &str)],
98        body: Option<&[u8]>,
99    ) -> Result<Response, HttpError> {
100        match &mut self.inner {
101            ConnectionInner::H2(h2) => {
102                h2.send_request(method, path, &self.host, extra_headers, body)
103                    .await
104            }
105            ConnectionInner::H1(h1) => h1.send_request(method, path, extra_headers, body).await,
106        }
107    }
108
109    /// Send a request and return a streaming response after headers arrive.
110    pub(crate) async fn send_request_streaming(
111        &mut self,
112        method: &str,
113        path: &str,
114        extra_headers: &[(&str, &str)],
115        body: Option<&[u8]>,
116    ) -> Result<StreamingResponse<'_>, HttpError> {
117        match &mut self.inner {
118            ConnectionInner::H2(h2) => {
119                let stream = h2
120                    .send_request_streaming(method, path, &self.host, extra_headers, body)
121                    .await?;
122                Ok(StreamingResponse::H2(stream))
123            }
124            ConnectionInner::H1(h1) => {
125                let stream = h1
126                    .send_request_streaming(method, path, extra_headers, body)
127                    .await?;
128                Ok(StreamingResponse::H1(stream))
129            }
130        }
131    }
132
133    /// Close the underlying connection.
134    pub fn close(&self) {
135        match &self.inner {
136            ConnectionInner::H2(h2) => h2.close(),
137            ConnectionInner::H1(h1) => h1.close(),
138        }
139    }
140
141    /// Whether the peer has signalled the connection will close. For H1
142    /// this reflects `Connection: close` on the most recent response (or
143    /// the HTTP/1.0 default); for H2 this reflects a GOAWAY frame. The
144    /// [`Pool`](crate::pool::Pool) honours this automatically via the
145    /// guard returned by [`Pool::client`](crate::pool::Pool::client).
146    pub fn peer_will_close(&self) -> bool {
147        match &self.inner {
148            ConnectionInner::H2(h2) => h2.peer_will_close(),
149            ConnectionInner::H1(h1) => h1.peer_will_close(),
150        }
151    }
152}