use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
#[derive(Debug, Default)]
struct Ipv4OnlyResolver;
impl Resolve for Ipv4OnlyResolver {
fn resolve(&self, name: Name) -> Resolving {
let host = name.as_str().to_owned();
Box::pin(async move {
let addrs = tokio::net::lookup_host((host.as_str(), 0)).await?;
let v4: Vec<SocketAddr> = addrs.filter(SocketAddr::is_ipv4).collect();
Ok(Box::new(v4.into_iter()) as Addrs)
})
}
}
pub fn build_client(timeout_secs: u64, force_ipv4: bool) -> reqwest::Result<reqwest::Client> {
let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(timeout_secs));
if force_ipv4 {
builder = builder.dns_resolver(Arc::new(Ipv4OnlyResolver));
}
builder.build()
}
pub fn describe_reqwest_error(err: &reqwest::Error) -> String {
if let Some(status) = err.status() {
let reason = status.canonical_reason().unwrap_or("unknown");
return format!("HTTP {status} {reason}");
}
let kind = if err.is_timeout() {
"connection timed out"
} else if err.is_connect() {
"could not establish connection"
} else if err.is_request() {
"request could not be sent"
} else {
"network error"
};
let mut root: Option<String> = None;
let mut src = std::error::Error::source(err);
while let Some(e) = src {
root = Some(e.to_string());
src = e.source();
}
match root {
Some(cause) => format!("{kind} ({cause})"),
None => kind.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_client_succeeds_in_both_modes() {
assert!(build_client(10, false).is_ok());
assert!(build_client(10, true).is_ok());
}
}