use std::time::Duration;
use reqwest::blocking::{Body, Client};
use reqwest::redirect::Policy;
use super::http_sender::{Headers, HttpRequest, HttpResponse, HttpSender};
use crate::errors::Api2ConvertError;
pub(crate) struct ReqwestSender {
no_redirect: Client,
follow: Client,
}
impl ReqwestSender {
pub(crate) fn new(connect_timeout: Duration) -> Result<Self, Api2ConvertError> {
let build = |policy: Policy| {
Client::builder()
.connect_timeout(connect_timeout)
.referer(false)
.redirect(policy)
.build()
.map_err(|e| {
Api2ConvertError::Config(format!(
"failed to build HTTP client: {}",
err_kind(&e)
))
})
};
Ok(ReqwestSender {
no_redirect: build(Policy::none())?,
follow: build(Policy::limited(5))?,
})
}
}
impl HttpSender for ReqwestSender {
fn send(&self, req: &HttpRequest) -> Result<HttpResponse, Api2ConvertError> {
reqwest::Url::parse(&req.url)
.map_err(|_| Api2ConvertError::Network("invalid request URL".to_string()))?;
let method = reqwest::Method::from_bytes(req.method.as_bytes())
.map_err(|_| Api2ConvertError::Network("invalid HTTP method".to_string()))?;
let client = if req.follow_redirects {
&self.follow
} else {
&self.no_redirect
};
let mut rb = client.request(method, &req.url);
if let Some(t) = req.timeout {
rb = rb.timeout(t);
}
for (name, value) in &req.headers {
rb = rb.header(name.as_str(), value.as_str());
}
if let Some(make_body) = &req.make_body {
let reader = make_body().map_err(|_| {
Api2ConvertError::Network("failed to open request body".to_string())
})?;
rb = rb.body(Body::new(reader));
} else if let Some(bytes) = &req.body {
rb = rb.body(bytes.clone());
}
let resp = rb
.send()
.map_err(|e| Api2ConvertError::Network(err_kind(&e)))?;
let status = resp.status().as_u16();
let mut headers = Headers::new();
for (name, value) in resp.headers().iter() {
if let Ok(v) = value.to_str() {
headers.insert(name.as_str(), v);
}
}
Ok(HttpResponse {
status,
headers,
body: Box::new(resp),
})
}
}
fn err_kind(e: &reqwest::Error) -> String {
if e.is_timeout() {
"request timed out".to_string()
} else if e.is_connect() {
"connection failed".to_string()
} else if e.is_redirect() {
"too many redirects".to_string()
} else if e.is_request() {
"malformed request".to_string()
} else if e.is_body() || e.is_decode() {
"failed to read response body".to_string()
} else {
"transport error".to_string()
}
}