1use std::{
2 fs::File,
3 io::{self, copy, Cursor, Error, ErrorKind},
4 time::Duration,
5};
6
7use hyper::{body::Bytes, client::HttpConnector, Body, Client, Method, Request, Response};
8use hyper_tls::HttpsConnector;
9use reqwest::{header::CONTENT_TYPE, ClientBuilder};
10use tokio::time::timeout;
11use url::Url;
12
13pub fn create_get(url: &str, path: &str) -> io::Result<Request<Body>> {
15 let uri = match join_uri(url, path) {
16 Ok(u) => u,
17 Err(e) => return Err(e),
18 };
19
20 let req = match Request::builder()
21 .method(Method::GET)
22 .uri(uri.as_str())
23 .body(Body::empty())
24 {
25 Ok(r) => r,
26 Err(e) => {
27 return Err(Error::new(
28 ErrorKind::Other,
29 format!("failed to create request {}", e),
30 ));
31 }
32 };
33
34 Ok(req)
35}
36
37const JSON_CONTENT_TYPE: &str = "application/json";
38
39pub fn create_json_post(url: &str, path: &str, d: &str) -> io::Result<Request<Body>> {
41 let uri = join_uri(url, path)?;
42
43 let req = match Request::builder()
44 .method(Method::POST)
45 .header("content-type", JSON_CONTENT_TYPE)
46 .uri(uri.as_str())
47 .body(Body::from(String::from(d)))
48 {
49 Ok(r) => r,
50 Err(e) => {
51 return Err(Error::new(
52 ErrorKind::Other,
53 format!("failed to create request {}", e),
54 ));
55 }
56 };
57
58 Ok(req)
59}
60
61pub async fn read_bytes(
63 req: Request<Body>,
64 timeout_dur: Duration,
65 is_https: bool,
66 check_status_code: bool,
67) -> io::Result<Bytes> {
68 let resp = send_req(req, timeout_dur, is_https).await?;
69 if !resp.status().is_success() {
70 log::warn!(
71 "unexpected HTTP response code {} (server error {})",
72 resp.status(),
73 resp.status().is_server_error()
74 );
75 if check_status_code {
76 return Err(Error::new(
77 ErrorKind::Other,
78 format!(
79 "unexpected HTTP response code {} (server error {})",
80 resp.status(),
81 resp.status().is_server_error()
82 ),
83 ));
84 }
85 }
86
87 let future_task = hyper::body::to_bytes(resp);
90 let ret = timeout(timeout_dur, future_task).await;
91
92 let bytes;
93 match ret {
94 Ok(result) => match result {
95 Ok(b) => bytes = b,
96 Err(e) => {
97 return Err(Error::new(
98 ErrorKind::Other,
99 format!("failed to read response {}", e),
100 ));
101 }
102 },
103 Err(e) => {
104 return Err(Error::new(
105 ErrorKind::Other,
106 format!("failed to read response {}", e),
107 ));
108 }
109 }
110
111 Ok(bytes)
112}
113
114async fn send_req(
116 req: Request<Body>,
117 timeout_dur: Duration,
118 is_https: bool,
119) -> io::Result<Response<Body>> {
120 let mut connector = HttpConnector::new();
124 connector.set_connect_timeout(Some(Duration::from_secs(5)));
126
127 let task = {
128 if !is_https {
129 let cli = Client::builder().build(connector);
130 cli.request(req)
131 } else {
132 let https_connector = HttpsConnector::new_with_connector(connector);
134 let cli = Client::builder().build(https_connector);
135 cli.request(req)
136 }
137 };
138
139 let res = timeout(timeout_dur, task).await?;
140 match res {
141 Ok(resp) => Ok(resp),
142 Err(e) => {
143 return Err(Error::new(
144 ErrorKind::Other,
145 format!("failed to fetch response {}", e),
146 ))
147 }
148 }
149}
150
151#[test]
152fn test_read_bytes_timeout() {
153 let _ = env_logger::builder()
154 .filter_level(log::LevelFilter::Info)
155 .is_test(true)
156 .try_init();
157
158 macro_rules! ab {
159 ($e:expr) => {
160 tokio_test::block_on($e)
161 };
162 }
163
164 let ret = join_uri("http://localhost:12", "invalid");
165 assert!(ret.is_ok());
166 let u = ret.unwrap();
167 let u = u.to_string();
168
169 let ret = Request::builder()
170 .method(hyper::Method::POST)
171 .uri(u)
172 .body(Body::empty());
173 assert!(ret.is_ok());
174 let req = ret.unwrap();
175 let ret = ab!(read_bytes(req, Duration::from_secs(1), false, true));
176 assert!(!ret.is_ok());
177}
178
179pub fn join_uri(url: &str, path: &str) -> io::Result<Url> {
180 let mut uri = match Url::parse(url) {
181 Ok(u) => u,
182 Err(e) => {
183 return Err(Error::new(
184 ErrorKind::Other,
185 format!("failed to parse client URL {}", e),
186 ))
187 }
188 };
189
190 if !path.is_empty() {
191 match uri.join(path) {
192 Ok(u) => uri = u,
193 Err(e) => {
194 return Err(Error::new(
195 ErrorKind::Other,
196 format!("failed to join parsed URL {}", e),
197 ));
198 }
199 }
200 }
201
202 Ok(uri)
203}
204
205#[test]
206fn test_join_uri() {
207 let ret = Url::parse("http://localhost:9850/ext/X/sendMultiple");
208 let expected = ret.unwrap();
209
210 let ret = join_uri("http://localhost:9850/", "/ext/X/sendMultiple");
211 assert!(ret.is_ok());
212 let t = ret.unwrap();
213 assert_eq!(t, expected);
214
215 let ret = join_uri("http://localhost:9850", "/ext/X/sendMultiple");
216 assert!(ret.is_ok());
217 let t = ret.unwrap();
218 assert_eq!(t, expected);
219
220 let ret = join_uri("http://localhost:9850", "ext/X/sendMultiple");
221 assert!(ret.is_ok());
222 let t = ret.unwrap();
223 assert_eq!(t, expected);
224}
225
226pub async fn download_file(ep: &str, file_path: &str) -> io::Result<()> {
228 log::info!("downloading the file via {}", ep);
229 let resp = reqwest::get(ep)
230 .await
231 .map_err(|e| Error::new(ErrorKind::Other, format!("failed reqwest::get {}", e)))?;
232
233 let mut content = Cursor::new(
234 resp.bytes()
235 .await
236 .map_err(|e| Error::new(ErrorKind::Other, format!("failed bytes {}", e)))?,
237 );
238
239 let mut f = File::create(file_path)?;
240 copy(&mut content, &mut f)?;
241
242 Ok(())
243}
244
245pub async fn get_non_tls(url: &str, url_path: &str) -> io::Result<Vec<u8>> {
247 let joined = join_uri(url, url_path)?;
248 log::debug!("non-TLS HTTP get for {:?}", joined);
249
250 let output = {
251 if url.starts_with("https") {
252 log::info!("sending via danger_accept_invalid_certs");
253 let cli = ClientBuilder::new()
254 .user_agent(env!("CARGO_PKG_NAME"))
255 .danger_accept_invalid_certs(true)
256 .timeout(Duration::from_secs(15))
257 .connection_verbose(true)
258 .build()
259 .map_err(|e| {
260 Error::new(
261 ErrorKind::Other,
262 format!("failed ClientBuilder build {}", e),
263 )
264 })?;
265 let resp = cli.get(joined.as_str()).send().await.map_err(|e| {
266 Error::new(ErrorKind::Other, format!("failed ClientBuilder send {}", e))
267 })?;
268 let out = resp.bytes().await.map_err(|e| {
269 Error::new(ErrorKind::Other, format!("failed ClientBuilder send {}", e))
270 })?;
271 out.into()
272 } else {
273 let req = create_get(url, url_path)?;
274 let buf = match read_bytes(
275 req,
276 Duration::from_secs(15),
277 url.starts_with("https"),
278 false,
279 )
280 .await
281 {
282 Ok(b) => b,
283 Err(e) => return Err(e),
284 };
285 buf.to_vec()
286 }
287 };
288 Ok(output)
289}
290
291#[test]
293fn test_get_non_tls() {
294 use tokio::runtime::Runtime;
295
296 let _ = env_logger::builder().is_test(true).try_init();
297
298 let rt = Runtime::new().unwrap();
299 let out = rt
300 .block_on(get_non_tls(
301 "https://api.github.com",
302 "repos/ava-labs/avalanchego/releases/latest",
303 ))
304 .unwrap();
305 println!("out: {}", String::from_utf8(out).unwrap());
306}
307
308pub async fn post_non_tls(url: &str, url_path: &str, data: &str) -> io::Result<Vec<u8>> {
310 let joined = join_uri(url, url_path)?;
311 log::debug!("non-TLS HTTP post {}-byte data to {:?}", data.len(), joined);
312
313 let output = {
314 if url.starts_with("https") {
315 log::info!("sending via danger_accept_invalid_certs");
316
317 let cli = ClientBuilder::new()
318 .user_agent(env!("CARGO_PKG_NAME"))
319 .danger_accept_invalid_certs(true)
320 .timeout(Duration::from_secs(15))
321 .connection_verbose(true)
322 .build()
323 .map_err(|e| {
324 Error::new(
325 ErrorKind::Other,
326 format!("failed ClientBuilder build {}", e),
327 )
328 })?;
329 let resp = cli
330 .post(joined.as_str())
331 .header(CONTENT_TYPE, "application/json")
332 .body(data.to_string())
333 .send()
334 .await
335 .map_err(|e| {
336 Error::new(ErrorKind::Other, format!("failed ClientBuilder send {}", e))
337 })?;
338 let out = resp.bytes().await.map_err(|e| {
339 Error::new(ErrorKind::Other, format!("failed ClientBuilder send {}", e))
340 })?;
341 out.into()
342 } else {
343 let req = create_json_post(url, url_path, data)?;
344 let buf = match read_bytes(req, Duration::from_secs(15), false, false).await {
345 Ok(b) => b,
346 Err(e) => return Err(e),
347 };
348 buf.to_vec()
349 }
350 };
351 Ok(output)
352}