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::new(
87                        io::ErrorKind::Other,
88                        e,
89                    )))
90                }
91            }
92        }
93    }
94}
95
96impl<T> Default for Resolver<T> {
97    fn default() -> Resolver<T> {
98        Resolver::new()
99    }
100}
101
102impl<T> Clone for Resolver<T> {
103    fn clone(&self) -> Self {
104        *self
105    }
106}
107
108impl<T> fmt::Debug for Resolver<T> {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        f.debug_struct("Resolver").finish()
111    }
112}
113
114impl<T: Address, C> ServiceFactory<Connect<T>, C> for Resolver<T> {
115    type Response = Connect<T>;
116    type Error = ConnectError;
117    type Service = Resolver<T>;
118    type InitError = ();
119
120    async fn create(&self, _: C) -> Result<Self::Service, Self::InitError> {
121        Ok(*self)
122    }
123}
124
125impl<T: Address> Service<Connect<T>> for Resolver<T> {
126    type Response = Connect<T>;
127    type Error = ConnectError;
128
129    async fn call(
130        &self,
131        req: Connect<T>,
132        _: ServiceCtx<'_, Self>,
133    ) -> Result<Connect<T>, Self::Error> {
134        self.lookup(req).await
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use ntex_util::future::lazy;
142
143    #[allow(clippy::clone_on_copy)]
144    #[ntex::test]
145    async fn resolver() {
146        let resolver = Resolver::default().clone();
147        assert!(format!("{:?}", resolver).contains("Resolver"));
148        let srv = resolver.pipeline(()).await.unwrap().bind();
149        assert!(lazy(|cx| srv.poll_ready(cx)).await.is_ready());
150
151        let res = srv.call(Connect::new("www.rust-lang.org")).await;
152        assert!(res.is_ok());
153
154        let res = srv.call(Connect::new("---11213")).await;
155        assert!(res.is_err());
156
157        let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
158        let res = srv
159            .call(Connect::new("www.rust-lang.org").set_addrs(vec![addr]))
160            .await
161            .unwrap();
162        let addrs: Vec<_> = res.addrs().collect();
163        assert_eq!(addrs.len(), 1);
164        assert!(addrs.contains(&addr));
165    }
166}