futures_http/
client.rs

1use std::{future::Future, io::Result};
2
3use futures::{AsyncRead, AsyncWrite};
4use http::{Request, Response};
5
6use crate::body::BodyReader;
7use crate::reader::HttpReader;
8use crate::writer::HttpWriter;
9
10/// An asynchronous *Client* to make http *Requests* with.
11pub trait HttpSend {
12    /// Sends the **Request** via `stream` and returns a future of [`Response`]
13    fn send<S>(self, stream: S) -> impl Future<Output = Result<Response<BodyReader>>>
14    where
15        S: AsyncRead + AsyncWrite + Unpin + Send + 'static;
16}
17
18impl HttpSend for Request<BodyReader> {
19    fn send<S>(self, mut stream: S) -> impl Future<Output = Result<Response<BodyReader>>>
20    where
21        S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
22    {
23        async move {
24            stream.write_request(self).await?;
25
26            stream.read_response().await
27        }
28    }
29}
30
31#[cfg(feature = "with_rasi")]
32pub mod rasio {
33    use std::{
34        future::Future,
35        io::{Error, ErrorKind, Result},
36        net::{SocketAddr, ToSocketAddrs},
37        path::{Path, PathBuf},
38    };
39
40    use futures_boring::{
41        connect,
42        ssl::{SslConnector, SslMethod},
43    };
44    use http::{uri::Scheme, Request, Response};
45    use rasi::net::TcpStream;
46
47    use crate::body::BodyReader;
48
49    /// Options and flags which can be used to configure how a http client is opened.
50    #[derive(Default, Debug, Clone)]
51    pub struct HttpClientOptions {
52        raddrs: Option<Vec<SocketAddr>>,
53        server_name: Option<String>,
54        ca_file: Option<PathBuf>,
55        use_server_name_indication: bool,
56    }
57
58    impl HttpClientOptions {
59        /// Create a `HttpClientOptionsBuilder` instance to build `HttpClientOptions`.
60        pub fn new() -> HttpClientOptionsBuilder {
61            HttpClientOptionsBuilder {
62                ops: Ok(HttpClientOptions {
63                    use_server_name_indication: true,
64                    ..Default::default()
65                }),
66            }
67        }
68
69        async fn send(self, request: Request<BodyReader>) -> Result<Response<BodyReader>> {
70            let scheme = request.uri().scheme().ok_or(Error::new(
71                ErrorKind::InvalidInput,
72                "Unspecified request scheme",
73            ))?;
74
75            let host = request.uri().host().ok_or(Error::new(
76                ErrorKind::InvalidInput,
77                "Unspecified request uri",
78            ))?;
79
80            let port = request.uri().port_u16().unwrap_or_else(|| {
81                if scheme == &Scheme::HTTP {
82                    80
83                } else {
84                    443
85                }
86            });
87
88            let raddrs = if let Some(raddrs) = &self.raddrs {
89                raddrs.to_owned()
90            } else {
91                format!("{}:{}", host, port)
92                    .to_socket_addrs()?
93                    .collect::<Vec<_>>()
94            };
95
96            if scheme == &Scheme::HTTP {
97                let transport = TcpStream::connect(raddrs.as_slice()).await?;
98
99                return super::HttpSend::send(request, transport).await;
100            } else {
101                let stream = TcpStream::connect(raddrs.as_slice()).await?;
102
103                let mut config = SslConnector::builder(SslMethod::tls_client())
104                    .map_err(|err| Error::new(ErrorKind::InvalidInput, err))?;
105
106                if let Some(ca_file) = self.ca_file.to_owned() {
107                    log::trace!("load trust root ca: {:?}", ca_file);
108
109                    config
110                        .set_ca_file(ca_file)
111                        .map_err(|err| Error::new(ErrorKind::InvalidInput, err))?;
112                }
113
114                let mut config = config.build().configure().unwrap();
115
116                config.set_use_server_name_indication(self.use_server_name_indication);
117
118                let transport = connect(config, host, stream)
119                    .await
120                    .map_err(|err| Error::new(ErrorKind::ConnectionRefused, err))?;
121
122                return super::HttpSend::send(request, transport).await;
123            }
124        }
125    }
126
127    impl TryInto<HttpClientOptions> for &HttpClientOptions {
128        type Error = std::io::Error;
129
130        fn try_into(self) -> std::result::Result<HttpClientOptions, Self::Error> {
131            Ok(self.clone())
132        }
133    }
134
135    /// A `HttpClientOptions` builder.
136    pub struct HttpClientOptionsBuilder {
137        ops: Result<HttpClientOptions>,
138    }
139
140    impl HttpClientOptionsBuilder {
141        pub fn redirect<R: ToSocketAddrs>(self, raddrs: R) -> Self {
142            self.and_then(|mut ops| {
143                ops.raddrs = Some(
144                    raddrs
145                        .to_socket_addrs()
146                        .map(|iter| iter.collect::<Vec<_>>())?,
147                );
148                Ok(ops)
149            })
150        }
151
152        /// Set remote server's server name, this option will rewrite request's host field.
153        pub fn with_server_name(self, server_name: &str) -> Self {
154            self.and_then(|mut ops| {
155                ops.server_name = Some(server_name.to_string());
156
157                Ok(ops)
158            })
159        }
160
161        /// Set the server verification ca file, this is useful for self signed server.
162        pub fn with_ca_file<P: AsRef<Path>>(self, ca_file: P) -> Self {
163            self.and_then(|mut ops| {
164                ops.ca_file = Some(ca_file.as_ref().to_path_buf());
165
166                Ok(ops)
167            })
168        }
169
170        /// Configures the use of Server Name Indication (SNI) when connecting.
171        /// Defaults to true.
172        pub fn set_use_server_name_indication(self, value: bool) -> Self {
173            self.and_then(|mut ops| {
174                ops.use_server_name_indication = value;
175
176                Ok(ops)
177            })
178        }
179
180        fn and_then<F>(self, func: F) -> Self
181        where
182            F: FnOnce(HttpClientOptions) -> Result<HttpClientOptions>,
183        {
184            HttpClientOptionsBuilder {
185                ops: self.ops.and_then(func),
186            }
187        }
188    }
189
190    impl TryInto<HttpClientOptions> for HttpClientOptionsBuilder {
191        type Error = std::io::Error;
192        fn try_into(self) -> std::result::Result<HttpClientOptions, Self::Error> {
193            self.ops
194        }
195    }
196
197    /// An extension trait for [`Request`] with addition `send` method.
198    pub trait HttpClient {
199        /// Consume self and send [`Request`] via `stream` to peer.
200        ///
201        /// On success, returns [`Response`] from peer.
202        fn send<Op>(self, ops: Op) -> impl Future<Output = Result<Response<BodyReader>>>
203        where
204            Op: TryInto<HttpClientOptions, Error = std::io::Error>;
205    }
206
207    impl HttpClient for Request<BodyReader> {
208        fn send<Op>(self, ops: Op) -> impl Future<Output = Result<Response<BodyReader>>>
209        where
210            Op: TryInto<HttpClientOptions, Error = std::io::Error>,
211        {
212            async move {
213                let ops: HttpClientOptions = ops.try_into()?;
214
215                ops.send(self).await
216            }
217        }
218    }
219}