deboa_compio/client/http/conn/
mod.rs1use crate::cert::{DeboaCertificate, DeboaIdentity};
19#[cfg(feature = "http1")]
20use deboa::request::Http1Request;
21#[cfg(feature = "http2")]
22use deboa::request::Http2Request;
23use deboa::{
24 conn::{ConnectionConfig, HttpConnectionDispatcher, ProtoConnection},
25 dns::DnsResolver,
26 errors::{DeboaError, RequestError},
27 response::DeboaResponse,
28 Result,
29};
30#[cfg(feature = "http3")]
31use deboa_h3::compio::Http3Request;
32use http::{Request, Version};
33use hyper_body_utils::HttpBody;
34use std::{marker::PhantomData, time::Duration};
35
36pub mod pool;
48
49#[cfg(feature = "http1")]
50pub(crate) type Http1Connection = BaseHttpConnection<Http1Request, HttpBody, HttpBody>;
51#[cfg(feature = "http2")]
52pub(crate) type Http2Connection = BaseHttpConnection<Http2Request, HttpBody, HttpBody>;
53#[cfg(feature = "http3")]
54pub(crate) type Http3Connection = BaseHttpConnection<Http3Request, HttpBody, HttpBody>;
55
56pub enum DeboaConnection {
64 #[cfg(feature = "http1")]
65 Http1(Box<Http1Connection>),
66 #[cfg(feature = "http2")]
67 Http2(Box<Http2Connection>),
68 #[cfg(feature = "http3")]
69 Http3(Box<Http3Connection>),
70}
71
72impl DeboaConnection {
73 #[cfg(feature = "http1")]
74 pub fn http1(conn: Http1Connection) -> Self {
75 DeboaConnection::Http1(Box::new(conn))
76 }
77
78 #[cfg(feature = "http2")]
79 pub fn http2(conn: Http2Connection) -> Self {
80 DeboaConnection::Http2(Box::new(conn))
81 }
82
83 #[cfg(feature = "http3")]
84 pub fn http3(conn: Http3Connection) -> Self {
85 DeboaConnection::Http3(Box::new(conn))
86 }
87
88 async fn send(&mut self, request: Request<HttpBody>) -> Result<DeboaResponse> {
89 match self {
90 #[cfg(feature = "http1")]
91 DeboaConnection::Http1(ref mut conn) => {
92 let (parts, body) = conn
93 .sender
94 .send_request(request)
95 .await
96 .map_err(|e| {
97 DeboaError::Request(RequestError::Send { message: e.to_string() })
98 })?
99 .into_parts();
100
101 Ok(DeboaResponse::new(http::Response::from_parts(
102 parts,
103 HttpBody::from_incoming(body),
104 )))
105 }
106 #[cfg(feature = "http2")]
107 DeboaConnection::Http2(ref mut conn) => {
108 let (parts, body) = conn
109 .sender
110 .send_request(request)
111 .await
112 .map_err(|e| {
113 DeboaError::Request(RequestError::Send { message: e.to_string() })
114 })?
115 .into_parts();
116
117 Ok(DeboaResponse::new(http::Response::from_parts(
118 parts,
119 HttpBody::from_incoming(body),
120 )))
121 }
122 #[cfg(feature = "http3")]
123 DeboaConnection::Http3(ref mut conn) => {
124 let response = conn
125 .sender
126 .send_request(request)
127 .await
128 .map_err(|e| {
129 DeboaError::Request(RequestError::Send { message: e.to_string() })
130 })?;
131
132 Ok(DeboaResponse::new(response))
133 }
134 #[allow(unreachable_patterns, clippy::needless_return)]
135 _ => {
136 return Err(DeboaError::UnsupportedProtocol);
137 }
138 }
139 }
140}
141
142impl HttpConnectionDispatcher for DeboaConnection {
143 async fn send_request(
154 &mut self,
155 request: Request<HttpBody>,
156 timeout: Duration,
157 ) -> Result<DeboaResponse> {
158 compio::time::timeout(timeout, self.send(request))
159 .await
160 .map_err(|_| {
161 DeboaError::Request(RequestError::Send { message: "Request timed out".to_string() })
162 })?
163 }
164}
165
166pub struct BaseHttpConnection<Sender, ReqBody, ResBody> {
172 pub(crate) sender: Sender,
173 pub(crate) req_body: PhantomData<ReqBody>,
174 pub(crate) res_body: PhantomData<ResBody>,
175}
176
177impl<Sender, ReqBody, ResBody> BaseHttpConnection<Sender, ReqBody, ResBody> {
178 pub(crate) fn new(sender: Sender) -> Self {
179 Self { sender, req_body: PhantomData, res_body: PhantomData }
180 }
181}
182
183pub struct ConnectionFactory {}
184
185impl ConnectionFactory {
186 pub async fn create_connection<'a, D>(
188 config: &'a ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>,
189 dns_resolver: &D,
190 ) -> Result<DeboaConnection>
191 where
192 D: DnsResolver,
193 {
194 let ips = dns_resolver
195 .resolve(
196 config
197 .host()
198 .to_string(),
199 config.port(),
200 )
201 .await?;
202 let ips = if config
203 .client_bind_addr()
204 .is_ipv4()
205 {
206 ips.into_iter()
207 .filter(|ip| ip.is_ipv4())
208 .collect::<Vec<_>>()
209 } else {
210 ips.into_iter()
211 .filter(|ip| ip.is_ipv6())
212 .collect::<Vec<_>>()
213 };
214
215 let Some(ip) = ips.first() else {
216 return Err(DeboaError::Request(RequestError::Send {
217 message: format!("No IP addresses found for hostname: {}", config.host()),
218 }));
219 };
220
221 #[cfg(any(feature = "http1", feature = "http2"))]
222 let stream = {
223 use compio::net::TcpStream;
224 use cyper_core::HyperStream;
225 use deboa::errors::ConnectionError;
226
227 let tcp_stream = TcpStream::connect(format!("{}:{}", ip, config.port()))
228 .await
229 .map_err(|e| {
230 DeboaError::Connection(ConnectionError::Tcp { message: e.to_string() })
231 })?;
232 let use_tls = config.scheme() == "https" || config.scheme() == "wss";
233 if !use_tls {
234 HyperStream::new_plain(tcp_stream)
235 } else {
236 #[cfg(feature = "rust-tls")]
237 {
238 use crate::client::tls::rustls::tcp::connect;
239 use crate::client::tls::rustls::TlsConnectionBuilder;
240 let tls_config = TlsConnectionBuilder::default()
241 .certificate(config.certificate())
242 .identity(config.identity())
243 .build_config()?;
244
245 HyperStream::new_tls(connect(tls_config, tcp_stream, config.host()).await?)
246 }
247
248 #[cfg(feature = "native-tls")]
249 {
250 use crate::client::tls::native::TlsConnectionBuilder;
251 let stream = TlsConnectionBuilder::new(tcp_stream, config.host())
252 .certificate(config.certificate())
253 .identity(config.identity())
254 .connect()
255 .await?;
256 HyperStream::new_tls(stream)
257 }
258 }
259 };
260
261 let conn = match config.protocol_version() {
262 #[cfg(feature = "http1")]
263 &Version::HTTP_11 => {
264 let conn = Http1Connection::connect(stream).await?;
265 DeboaConnection::http1(conn)
266 }
267 #[cfg(feature = "http2")]
268 &Version::HTTP_2 => {
269 let conn = Http2Connection::connect(stream).await?;
270 DeboaConnection::http2(conn)
271 }
272 #[cfg(feature = "http3")]
273 &Version::HTTP_3 => {
274 let stream = {
275 use crate::client::tls::rustls::udp::connect;
276 #[cfg(feature = "rust-tls")]
277 use crate::client::tls::rustls::TlsConnectionBuilder;
278 use compio_quic::Endpoint;
279 use deboa::errors::ConnectionError;
280 use std::net::SocketAddr;
281
282 let mut client_endpoint =
283 Endpoint::client(SocketAddr::new(*config.client_bind_addr(), 0))
284 .await
285 .map_err(|e| {
286 DeboaError::Connection(ConnectionError::Udp {
287 message: e.to_string(),
288 })
289 })?;
290
291 let tls_config = TlsConnectionBuilder::default()
292 .certificate(config.certificate())
293 .identity(config.identity())
294 .build_config()?;
295
296 connect(
297 tls_config,
298 &mut client_endpoint,
299 SocketAddr::new(*ip, config.port()),
300 config.host(),
301 )
302 .await?
303 };
304
305 let conn = Http3Connection::connect(stream).await?;
306 DeboaConnection::http3(conn)
307 }
308 _ => {
309 return Err(DeboaError::UnsupportedProtocol);
310 }
311 };
312
313 Ok(conn)
314 }
315}