Skip to main content

ureq/unversioned/
resolver.rs

1//! Name resolvers.
2//!
3//! **NOTE resolver does not (yet) [follow semver][super].**
4//!
5//! _NOTE: Resolver is deep configuration of ureq and is not required for regular use._
6//!
7//! Name resolving is pluggable. The resolver's duty is to take a URI and translate it
8//! to a socket address (IP + port). This is done as a separate step in regular ureq use.
9//! The hostname is looked up and provided to the [`Connector`](crate::transport::Connector).
10//!
11//! In some situations it might be desirable to not do this lookup, or to use another system
12//! than DNS for it.
13use std::fmt::{self, Debug};
14use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs};
15use std::sync::mpsc::{self, RecvTimeoutError};
16use std::thread::{self};
17use std::vec::IntoIter;
18
19use http::Uri;
20use http::uri::{Authority, Scheme};
21
22use crate::Error;
23use crate::config::Config;
24use crate::http;
25use crate::transport::NextTimeout;
26use crate::util::{SchemeExt, UriExt};
27
28/// Trait for name resolvers.
29pub trait Resolver: Debug + Send + Sync + 'static {
30    /// Resolve the URI to a socket address.
31    ///
32    /// The implementation should resolve within the given _timeout_.
33    ///
34    /// The resolver must guarantee at least one returned address, or error with
35    /// `Error::HostNotFound`.
36    fn resolve(
37        &self,
38        uri: &Uri,
39        config: &Config,
40        timeout: NextTimeout,
41    ) -> Result<ResolvedSocketAddrs, Error>;
42
43    /// Produce an empty array of addresses.
44    fn empty(&self) -> ResolvedSocketAddrs {
45        fn uninited_socketaddr() -> SocketAddr {
46            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
47        }
48
49        ArrayVec::from_fn(|_| uninited_socketaddr())
50    }
51}
52
53/// Max number of socket addresses to keep from the resolver.
54const MAX_ADDRS: usize = 16;
55
56pub use ureq_proto::ArrayVec;
57
58/// Addresses as returned by the resolver.
59pub type ResolvedSocketAddrs = ArrayVec<SocketAddr, MAX_ADDRS>;
60
61/// Default resolver implementation.
62///
63/// Uses std::net [`ToSocketAddrs`](https://doc.rust-lang.org/std/net/trait.ToSocketAddrs.html) to
64/// do the lookup. Can optionally spawn a thread to abort lookup if the relevant timeout is set.
65#[derive(Default)]
66pub struct DefaultResolver {
67    _private: (),
68}
69
70impl DefaultResolver {
71    /// Helper to combine scheme host and port to a single string.
72    ///
73    /// This knows about the default ports for http, https and socks proxies which
74    /// can then be omitted from the `Authority`.
75    pub fn host_and_port(scheme: &Scheme, authority: &Authority) -> Option<String> {
76        let port = authority.port_u16().or_else(|| scheme.default_port())?;
77
78        Some(format!("{}:{}", authority.host(), port))
79    }
80}
81
82impl Resolver for DefaultResolver {
83    fn resolve(
84        &self,
85        uri: &Uri,
86        config: &Config,
87        timeout: NextTimeout,
88    ) -> Result<ResolvedSocketAddrs, Error> {
89        uri.ensure_valid_url()?;
90
91        // unwrap is ok due to ensure_full_url() above.
92        let scheme = uri.scheme().unwrap();
93        let authority = uri.authority().unwrap();
94
95        if cfg!(feature = "_test") {
96            let mut v = ArrayVec::from_fn(|_| "0.0.0.0:1".parse().unwrap());
97            v.push(SocketAddr::V4(SocketAddrV4::new(
98                Ipv4Addr::new(10, 0, 0, 1),
99                authority
100                    .port_u16()
101                    .or_else(|| scheme.default_port())
102                    // unwrap is ok because ensure_valid_url() above.
103                    .unwrap(),
104            )));
105            return Ok(v);
106        }
107
108        // This will be on the form "myspecialhost.org:1234". The port is mandatory.
109        // unwrap is ok because ensure_valid_url() above.
110        let addr = DefaultResolver::host_and_port(scheme, authority).unwrap();
111
112        // Determine if we want to use the async behavior.
113        let use_sync = timeout.after.is_not_happening();
114
115        let iter = if use_sync {
116            trace!("Resolve: {}", addr);
117            // When timeout is not set, we do not spawn any threads.
118            addr.to_socket_addrs()?
119        } else {
120            trace!("Resolve with timeout ({:?}): {} ", timeout, addr);
121            resolve_async(addr, timeout)?
122        };
123
124        let ip_family = config.ip_family();
125        let wanted = ip_family.keep_wanted(iter);
126
127        let mut result = self.empty();
128        for addr in wanted.take(MAX_ADDRS) {
129            result.push(addr);
130        }
131
132        debug!("Resolved: {:?}", result);
133
134        if result.is_empty() {
135            Err(Error::HostNotFound)
136        } else {
137            Ok(result)
138        }
139    }
140}
141
142fn resolve_async(addr: String, timeout: NextTimeout) -> Result<IntoIter<SocketAddr>, Error> {
143    // TODO(martin): On Linux we have getaddrinfo_a which is a libc async way of
144    // doing host lookup. We should make a subcrate that uses a native async method
145    // when possible, and otherwise fall back on this thread behavior.
146    let (tx, rx) = mpsc::sync_channel(1);
147    thread::spawn(move || tx.send(addr.to_socket_addrs()).ok());
148
149    match rx.recv_timeout(*timeout.after) {
150        Ok(v) => Ok(v?),
151        Err(c) => match c {
152            // Timeout results in None
153            RecvTimeoutError::Timeout => Err(Error::Timeout(timeout.reason)),
154            // The sender going away is nonsensical. Did the thread just die?
155            RecvTimeoutError::Disconnected => unreachable!("mpsc sender gone"),
156        },
157    }
158}
159
160impl fmt::Debug for DefaultResolver {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        f.debug_struct("DefaultResolver").finish()
163    }
164}
165
166#[cfg(test)]
167mod test {
168    use crate::transport::time::Duration;
169
170    use super::*;
171
172    #[test]
173    fn unknown_scheme() {
174        let uri: Uri = "foo://some:42/123".parse().unwrap();
175        let config = Config::default();
176        let err = DefaultResolver::default()
177            .resolve(
178                &uri,
179                &config,
180                NextTimeout {
181                    after: Duration::NotHappening,
182                    reason: crate::Timeout::Global,
183                },
184            )
185            .unwrap_err();
186        assert!(matches!(err, Error::BadUri(_)));
187        assert_eq!(err.to_string(), "bad uri: unknown scheme: foo");
188    }
189}