saluki_io/net/dns/
hyper.rs

1use std::{
2    fmt,
3    future::Future,
4    io,
5    pin::Pin,
6    task::{Context, Poll},
7};
8
9use hyper_util::client::legacy::connect::{
10    dns::{GaiAddrs, GaiResolver, Name},
11    HttpConnector,
12};
13use metrics::Counter;
14use tower::Service;
15
16/// An [`HttpConnector`] that uses [`SystemResolver`].
17pub type SystemHttpConnector = HttpConnector<SystemResolver>;
18
19/// A DNS resolver for `hyper` backed by the operating system resolver.
20///
21/// Lookups are handled in the default Tokio blocking thread pool as calls to `getaddrinfo` are synchronous.
22#[derive(Clone)]
23pub struct SystemResolver {
24    inner: GaiResolver,
25    lookup_errors: Option<Counter>,
26}
27
28impl SystemResolver {
29    /// Creates a new [`SystemResolver`].
30    pub fn new() -> Self {
31        Self {
32            inner: GaiResolver::new(),
33            lookup_errors: None,
34        }
35    }
36
37    /// Sets a counter that's incremented when DNS lookup fails.
38    pub fn with_lookup_errors_counter(mut self, counter: Counter) -> Self {
39        self.lookup_errors = Some(counter);
40        self
41    }
42
43    /// Consumes `self` and creates a new [`SystemHttpConnector`] with this resolver.
44    pub fn into_http_connector(self) -> SystemHttpConnector {
45        SystemHttpConnector::new_with_resolver(self)
46    }
47}
48
49impl Default for SystemResolver {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55/// An error returned when a DNS lookup fails.
56#[derive(Debug)]
57pub struct DnsError(io::Error);
58
59impl fmt::Display for DnsError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(f, "failed to resolve host: {}", self.0)
62    }
63}
64
65impl std::error::Error for DnsError {
66    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
67        Some(&self.0)
68    }
69}
70
71impl Service<Name> for SystemResolver {
72    type Response = GaiAddrs;
73    type Error = DnsError;
74
75    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
76
77    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
78        // `GaiResolver` is stateless and always ready.
79        Poll::Ready(Ok(()))
80    }
81
82    fn call(&mut self, name: Name) -> Self::Future {
83        let fut = self.inner.call(name);
84        let lookup_errors = self.lookup_errors.clone();
85
86        Box::pin(async move {
87            match fut.await {
88                Ok(addrs) => Ok(addrs),
89                Err(error) => {
90                    if let Some(lookup_errors) = lookup_errors {
91                        lookup_errors.increment(1);
92                    }
93                    Err(DnsError(error))
94                }
95            }
96        })
97    }
98}