use std::net::{SocketAddr, ToSocketAddrs};
use crate::error::{Error, Result};
pub trait Resolver: Send + Sync + std::fmt::Debug {
fn resolve(&self, host: &str, port: u16) -> Result<Vec<SocketAddr>>;
}
#[derive(Debug, Default, Clone)]
pub struct StdResolver;
impl Resolver for StdResolver {
fn resolve(&self, host: &str, port: u16) -> Result<Vec<SocketAddr>> {
if let Some(addr) = crate::url::ip_literal_addr(host, port) {
return Ok(vec![addr]);
}
let host = crate::url::unbracket(host);
let addrs: Vec<SocketAddr> = (host, port).to_socket_addrs().map_err(Error::Io)?.collect();
if addrs.is_empty() {
return Err(Error::InvalidUrl(host.to_string()));
}
Ok(addrs)
}
}
pub(crate) fn connect_any(
addrs: &[SocketAddr],
timeout: Option<std::time::Duration>,
) -> std::io::Result<std::net::TcpStream> {
let mut last_err = None;
for addr in addrs {
let attempt = match timeout {
Some(t) => std::net::TcpStream::connect_timeout(addr, t),
None => std::net::TcpStream::connect(addr),
};
match attempt {
Ok(s) => return Ok(s),
Err(e) => last_err = Some(e),
}
}
Err(last_err.unwrap_or_else(|| {
std::io::Error::new(std::io::ErrorKind::NotFound, "no addresses to connect to")
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn std_resolver_resolves_localhost() {
let addrs = StdResolver.resolve("127.0.0.1", 80).unwrap();
assert_eq!(addrs[0], "127.0.0.1:80".parse().unwrap());
}
#[derive(Debug)]
struct Fixed(SocketAddr);
impl Resolver for Fixed {
fn resolve(&self, _host: &str, _port: u16) -> Result<Vec<SocketAddr>> {
Ok(vec![self.0])
}
}
#[test]
fn std_resolver_accepts_bracketed_ipv6_literal() {
let addrs = StdResolver.resolve("[::1]", 8080).unwrap();
assert_eq!(addrs, vec!["[::1]:8080".parse().unwrap()]);
let addrs = StdResolver.resolve("::1", 80).unwrap();
assert_eq!(addrs, vec!["[::1]:80".parse().unwrap()]);
let addrs = StdResolver.resolve("[fe80::1%253]", 80).unwrap();
match addrs[0] {
SocketAddr::V6(v6) => assert_eq!(v6.scope_id(), 3),
other => panic!("expected v6, got {other}"),
}
}
#[test]
fn connect_any_falls_back_to_later_address() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let good = listener.local_addr().unwrap();
let dead = {
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
l.local_addr().unwrap()
};
let s = connect_any(&[dead, good], Some(std::time::Duration::from_secs(2))).unwrap();
assert_eq!(s.peer_addr().unwrap(), good);
assert!(connect_any(&[dead], Some(std::time::Duration::from_secs(2))).is_err());
assert!(connect_any(&[], None).is_err());
}
#[test]
fn custom_resolver_is_consulted() {
let r = Fixed("10.1.2.3:443".parse().unwrap());
assert_eq!(r.resolve("ignored.example", 443).unwrap()[0].port(), 443);
}
}