use std::{collections::HashMap, str::FromStr};
use async_executor::Executor;
use http_body_util::Full;
use hyper::{
Method, Request as HyperRequest,
body::Bytes,
client::conn::http1::handshake,
header::{ACCEPT, CONTENT_LENGTH, HOST, HeaderName, HeaderValue, USER_AGENT},
};
use url::Url;
use crate::{
client::stream::HttpStream,
shared::{hyper::HyperIo, request::Request, response::Response},
};
#[doc(hidden)]
#[allow(clippy::implicit_hasher)]
pub async fn fetch(
url: Url,
method: Option<Method>,
headers: Option<HashMap<String, String>>,
body: Option<Vec<u8>>,
) -> Result<Response, String> {
let body = match body {
Some(body) => Bytes::from(body),
None => Bytes::new(),
};
let mut request = HyperRequest::new(body);
*request.uri_mut() = url.to_string().parse().unwrap();
if let Some(method) = method {
*request.method_mut() = method;
}
if let Some(headers) = headers {
for (key, val) in headers {
let key = HeaderName::from_str(key.as_str()).map_err(|e| e.to_string())?;
let val = HeaderValue::from_str(val.as_str()).map_err(|e| e.to_string())?;
request.headers_mut().insert(key, val);
}
}
if !request.headers().contains_key(USER_AGENT.as_str()) {
let ua = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
let ua = HeaderValue::from_str(ua).unwrap();
request.headers_mut().insert(USER_AGENT, ua);
}
if !request.headers().contains_key(CONTENT_LENGTH.as_str()) && request.method() != Method::GET {
let len = request.body().len().to_string();
let len = HeaderValue::from_str(&len).unwrap();
request.headers_mut().insert(CONTENT_LENGTH, len);
}
if !request.headers().contains_key(ACCEPT.as_str()) {
let accept = HeaderValue::from_static("*/*");
request.headers_mut().insert(ACCEPT, accept);
}
let mut req = Request::from(request);
req.decompress = true;
let exec = Executor::new();
let fut = fetch_inner(&exec, url, req);
exec.run(fut).await
}
async fn fetch_inner(
exec: &Executor<'_>,
mut url: Url,
mut request: Request,
) -> Result<Response, String> {
loop {
let stream = HttpStream::connect_url(url.clone())
.await
.map_err(|e| e.to_string())?;
let (mut sender, conn) = handshake(HyperIo::from(stream))
.await
.map_err(|e| e.to_string())?;
exec.spawn(conn).detach();
let (mut parts, body) = request.clone_inner().into_parts();
if let Some(host) = parts.uri.host() {
let host = HeaderValue::from_str(host).unwrap();
parts.headers.insert(HOST, host);
}
let data = HyperRequest::from_parts(parts, Full::new(body.into_bytes()));
let incoming = sender.send_request(data).await.map_err(|e| e.to_string())?;
if super::try_follow_redirect(&mut url, &mut request, &incoming)
.map_err(ToString::to_string)?
{
continue;
}
break Response::from_incoming(incoming, request.decompress)
.await
.map_err(|e| e.to_string());
}
}