ntex_net/connect/
resolve.rs

1use std::{fmt, io, marker, net};
2
3use ntex_rt::spawn_blocking;
4use ntex_service::{Service, ServiceCtx, ServiceFactory};
5use ntex_util::future::Either;
6
7use super::{Address, Connect, ConnectError};
8
9/// DNS Resolver Service
10pub struct Resolver<T>(marker::PhantomData<T>);
11
12impl<T> Resolver<T> {
13    /// Create new resolver instance with custom configuration and options.
14    pub fn new() -> Self {
15        Resolver(marker::PhantomData)
16    }
17}
18
19impl<T> Copy for Resolver<T> {}
20
21impl<T: Address> Resolver<T> {
22    /// Lookup ip addresses for provided host
23    pub async fn lookup(&self, req: Connect<T>) -> Result<Connect<T>, ConnectError> {
24        self.lookup_with_tag(req, "TCP-CLIENT").await
25    }
26
27    #[doc(hidden)]
28    /// Lookup ip addresses for provided host
29    pub async fn lookup_with_tag(
30        &self,
31        mut req: Connect<T>,
32        tag: &'static str,
33    ) -> Result<Connect<T>, ConnectError> {
34        if req.addr.is_some() || req.req.addr().is_some() {
35            Ok(req)
36        } else if let Ok(ip) = req.host().parse() {
37            req.addr = Some(Either::Left(net::SocketAddr::new(ip, req.port())));
38            Ok(req)
39        } else {
40            log::trace!("{}: DNS Resolver - resolving host {:?}", tag, req.host());
41
42            let host = if req.host().contains(':') {
43                req.host().to_string()
44            } else {
45                format!("{}:{}", req.host(), req.port())
46            };
47
48            let fut = spawn_blocking(move || net::ToSocketAddrs::to_socket_addrs(&host));
49            match fut.await {
50                Ok(Ok(ips)) => {
51                    let port = req.port();
52                    let req = req.set_addrs(ips.map(|mut ip| {
53                        ip.set_port(port);
54                        ip
55                    }));
56
57                    log::trace!(
58                        "{}: DNS Resolver - host {:?} resolved to {:?}",
59                        tag,
60                        req.host(),
61                        req.addrs()
62                    );
63
64                    if req.addr.is_none() {
65                        Err(ConnectError::NoRecords)
66                    } else {
67                        Ok(req)
68                    }
69                }
70                Ok(Err(e)) => {
71                    log::trace!(
72                        "{}: DNS Resolver - failed to resolve host {:?} err: {}",
73                        tag,
74                        req.host(),
75                        e
76                    );
77                    Err(ConnectError::Resolver(e))
78                }
79                Err(e) => {
80                    log::trace!(
81                        "{}: DNS Resolver - failed to resolve host {:?} err: {}",
82                        tag,
83                        req.host(),
84                        e
85                    );
86                    Err(ConnectError::Resolver(io::Error::other(e)))
87                }
88            }
89        }
90    }
91}
92
93impl<T> Default for Resolver<T> {
94    fn default() -> Resolver<T> {
95        Resolver::new()
96    }
97}
98
99impl<T> Clone for Resolver<T> {
100    fn clone(&self) -> Self {
101        *self
102    }
103}
104
105impl<T> fmt::Debug for Resolver<T> {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        f.debug_struct("Resolver").finish()
108    }
109}
110
111impl<T: Address, C> ServiceFactory<Connect<T>, C> for Resolver<T> {
112    type Response = Connect<T>;
113    type Error = ConnectError;
114    type Service = Resolver<T>;
115    type InitError = ();
116
117    async fn create(&self, _: C) -> Result<Self::Service, Self::InitError> {
118        Ok(*self)
119    }
120}
121
122impl<T: Address> Service<Connect<T>> for Resolver<T> {
123    type Response = Connect<T>;
124    type Error = ConnectError;
125
126    async fn call(
127        &self,
128        req: Connect<T>,
129        _: ServiceCtx<'_, Self>,
130    ) -> Result<Connect<T>, Self::Error> {
131        self.lookup(req).await
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use ntex_util::future::lazy;
139
140    #[allow(clippy::clone_on_copy)]
141    #[ntex::test]
142    async fn resolver() {
143        let resolver = Resolver::default().clone();
144        assert!(format!("{resolver:?}").contains("Resolver"));
145        let srv = resolver.pipeline(()).await.unwrap().bind();
146        assert!(lazy(|cx| srv.poll_ready(cx)).await.is_ready());
147
148        let res = srv.call(Connect::new("www.rust-lang.org")).await;
149        assert!(res.is_ok());
150
151        let res = srv.call(Connect::new("---11213")).await;
152        assert!(res.is_err());
153
154        let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
155        let res = srv
156            .call(Connect::new("www.rust-lang.org").set_addrs(vec![addr]))
157            .await
158            .unwrap();
159        let addrs: Vec<_> = res.addrs().collect();
160        assert_eq!(addrs.len(), 1);
161        assert!(addrs.contains(&addr));
162    }
163}