Skip to content

Commit 9e3f974

Browse files
0x676e67Copilotseanmonstar
authored
feat(dns): improve dns_resolver for better ergonomics and flexibility (#891)
* feat(dns): improve `dns_resolver` for better ergonomics and flexibility * Update src/core/client/connect/dns/resolve.rs Co-authored-by: Copilot <[email protected]> * backport seanmonstar/reqwest#2793 Co-authored-by: Sean McArthur <[email protected]> --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Sean McArthur <[email protected]>
1 parent a644502 commit 9e3f974

File tree

4 files changed

+67
-45
lines changed

4 files changed

+67
-45
lines changed

src/client/http/mod.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ use crate::{
6262
client::{HttpClient, connect::TcpConnectOptions, options::TransportOptions},
6363
rt::{TokioExecutor, tokio::TokioTimer},
6464
},
65-
dns::{DnsResolverWithOverrides, DynResolver, GaiResolver, Resolve},
65+
dns::{DnsResolverWithOverrides, DynResolver, GaiResolver, IntoResolve, Resolve},
6666
error::{self, BoxError, Error},
6767
header::OrigHeaderMap,
6868
http1::Http1Options,
@@ -250,10 +250,9 @@ impl ClientBuilder {
250250
}
251251
let proxies = Arc::new(proxies);
252252

253-
// Into parts for transport options
254253
let (tls_options, http1_options, http2_options) = config.transport_options.into_parts();
255254

256-
// Create the TLS connector with the provided options.
255+
// create the TLS connector with the provided options.
257256
let connector = {
258257
let resolver = {
259258
let mut resolver: Arc<dyn Resolve> = match config.dns_resolver {
@@ -272,7 +271,7 @@ impl ClientBuilder {
272271
DynResolver::new(resolver)
273272
};
274273

275-
// Apply http connector options
274+
// configured http connector options
276275
let http = |http: &mut HttpConnector| {
277276
http.enforce_http(false);
278277
http.set_keepalive(config.tcp_keepalive);
@@ -289,7 +288,7 @@ impl ClientBuilder {
289288
http.set_tcp_user_timeout(config.tcp_user_timeout);
290289
};
291290

292-
// Apply tls connector options
291+
// configured tls connector options
293292
let tls = |tls: TlsConnectorBuilder| {
294293
let alpn_protocol = match config.http_version_pref {
295294
HttpVersionPref::Http1 => Some(AlpnProtocol::HTTP1),
@@ -317,7 +316,7 @@ impl ClientBuilder {
317316
.build(config.connector_layers)?
318317
};
319318

320-
// Create client with the configured connector
319+
// create client with the configured connector
321320
let client = {
322321
let http2_only = matches!(config.http_version_pref, HttpVersionPref::Http2);
323322
let mut builder = HttpClient::builder(TokioExecutor::new());
@@ -333,10 +332,9 @@ impl ClientBuilder {
333332
builder.build(connector)
334333
};
335334

336-
// Create the client with the configured service layers
335+
// create the client with the configured service layers
337336
let client = {
338-
// Start with the base client service, which handles headers, original headers,
339-
// HTTPS-only, and proxies.
337+
// configured client service layer
340338
let service = ClientService::new(
341339
client,
342340
config.headers,
@@ -345,13 +343,13 @@ impl ClientBuilder {
345343
proxies,
346344
);
347345

348-
// Add cookie service layer if cookies are enabled.
346+
// configured cookie service layer if cookies are enabled.
349347
#[cfg(feature = "cookies")]
350348
let service = ServiceBuilder::new()
351349
.layer(CookieServiceLayer::new(config.cookie_store))
352350
.service(service);
353351

354-
// Add response decompression support (gzip, zstd, brotli, deflate) if enabled.
352+
// configured response decompression support (gzip, zstd, brotli, deflate) if enabled.
355353
#[cfg(any(
356354
feature = "gzip",
357355
feature = "zstd",
@@ -362,12 +360,12 @@ impl ClientBuilder {
362360
.layer(DecompressionLayer::new(config.accept_encoding))
363361
.service(service);
364362

365-
// Add a timeout layer for the response body.
363+
// configured timeout layer for the response body.
366364
let service = ServiceBuilder::new()
367365
.layer(ResponseBodyTimeoutLayer::new(config.timeout_options))
368366
.service(service);
369367

370-
// Add redirect following logic with the configured policy.
368+
// configured redirect following logic with the configured policy.
371369
let service = {
372370
let policy = FollowRedirectPolicy::new(config.redirect_policy)
373371
.with_referer(config.referer)
@@ -378,14 +376,14 @@ impl ClientBuilder {
378376
.service(service)
379377
};
380378

381-
// Add HTTP/2 retry logic.
379+
// configured HTTP/2 retry logic.
382380
let service = ServiceBuilder::new()
383381
.layer(RetryLayer::new(Http2RetryPolicy::new(
384382
config.http2_max_retry,
385383
)))
386384
.service(service);
387385

388-
// Add the configured layers to the service.
386+
// configured layers to the service.
389387
if config.layers.is_empty() {
390388
let service = ServiceBuilder::new()
391389
.layer(TimeoutLayer::new(config.timeout_options))
@@ -1262,8 +1260,11 @@ impl ClientBuilder {
12621260
/// Overrides for specific names passed to `resolve` and `resolve_to_addrs` will
12631261
/// still be applied on top of this resolver.
12641262
#[inline]
1265-
pub fn dns_resolver(mut self, resolver: Arc<dyn Resolve>) -> ClientBuilder {
1266-
self.config.dns_resolver = Some(resolver);
1263+
pub fn dns_resolver<R>(mut self, resolver: R) -> ClientBuilder
1264+
where
1265+
R: IntoResolve + Send + Sync + 'static,
1266+
{
1267+
self.config.dns_resolver = Some(resolver.into_resolve());
12671268
self
12681269
}
12691270

src/core/client/connect/dns/gai.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,7 @@ impl Iterator for SocketAddrs {
185185

186186
#[cfg(test)]
187187
mod tests {
188-
use std::{
189-
net::{Ipv4Addr, Ipv6Addr},
190-
str::FromStr,
191-
};
188+
use std::net::{Ipv4Addr, Ipv6Addr};
192189

193190
use super::*;
194191

@@ -245,7 +242,7 @@ mod tests {
245242
#[test]
246243
fn test_name_from_str() {
247244
const DOMAIN: &str = "test.example.com";
248-
let name = Name::from_str(DOMAIN).expect("Should be a valid domain");
245+
let name = Name::from(DOMAIN);
249246
assert_eq!(name.as_str(), DOMAIN);
250247
assert_eq!(name.to_string(), DOMAIN);
251248
}

src/core/client/connect/dns/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub use resolve::{Addrs, Name, Resolve, Resolving};
99

1010
pub(crate) use self::{
1111
gai::{GaiResolver, SocketAddrs},
12-
resolve::{DnsResolverWithOverrides, DynResolver},
12+
resolve::{DnsResolverWithOverrides, DynResolver, IntoResolve},
1313
sealed::{InternalResolve, resolve},
1414
};
1515

src/core/client/connect/dns/resolve.rs

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use std::{
22
collections::HashMap,
3-
error::Error,
43
fmt,
54
future::Future,
65
net::SocketAddr,
76
pin::Pin,
8-
str::FromStr,
97
sync::Arc,
108
task::{Context, Poll},
119
};
@@ -14,18 +12,6 @@ use tower::Service;
1412

1513
use crate::core::error::BoxError;
1614

17-
/// Error indicating a given string was not a valid domain name.
18-
#[derive(Debug)]
19-
pub struct InvalidNameError(());
20-
21-
impl fmt::Display for InvalidNameError {
22-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23-
f.write_str("Not a valid domain name")
24-
}
25-
}
26-
27-
impl Error for InvalidNameError {}
28-
2915
/// A domain name to resolve into IP addresses.
3016
#[derive(Clone, Hash, Eq, PartialEq)]
3117
pub struct Name {
@@ -46,6 +32,12 @@ impl Name {
4632
}
4733
}
4834

35+
impl From<&str> for Name {
36+
fn from(value: &str) -> Self {
37+
Name::new(value.into())
38+
}
39+
}
40+
4941
impl fmt::Debug for Name {
5042
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5143
fmt::Debug::fmt(&self.host, f)
@@ -58,14 +50,6 @@ impl fmt::Display for Name {
5850
}
5951
}
6052

61-
impl FromStr for Name {
62-
type Err = InvalidNameError;
63-
64-
fn from_str(host: &str) -> Result<Self, Self::Err> {
65-
Ok(Name::new(host.into()))
66-
}
67-
}
68-
6953
/// Alias for an `Iterator` trait object over `SocketAddr`.
7054
pub type Addrs = Box<dyn Iterator<Item = SocketAddr> + Send>;
7155

@@ -89,6 +73,46 @@ pub trait Resolve: Send + Sync {
8973
fn resolve(&self, name: Name) -> Resolving;
9074
}
9175

76+
/// Trait for converting types into a shared DNS resolver ([`Arc<dyn Resolve>`]).
77+
///
78+
/// Implemented for any [`Resolve`] type, [`Arc<T>`] where `T: Resolve`, and [`Arc<dyn Resolve>`].
79+
/// Enables ergonomic conversion to a trait object for use in APIs without manual Arc wrapping.
80+
pub trait IntoResolve {
81+
/// Converts the implementor into an [`Arc<dyn Resolve>`].
82+
///
83+
/// This method enables ergonomic conversion of concrete resolvers, [`Arc<T>`], or
84+
/// existing [`Arc<dyn Resolve>`] into a trait object suitable for APIs that expect
85+
/// a shared DNS resolver.
86+
fn into_resolve(self) -> Arc<dyn Resolve>;
87+
}
88+
89+
impl IntoResolve for Arc<dyn Resolve> {
90+
#[inline]
91+
fn into_resolve(self) -> Arc<dyn Resolve> {
92+
self
93+
}
94+
}
95+
96+
impl<R> IntoResolve for Arc<R>
97+
where
98+
R: Resolve + 'static,
99+
{
100+
#[inline]
101+
fn into_resolve(self) -> Arc<dyn Resolve> {
102+
self
103+
}
104+
}
105+
106+
impl<R> IntoResolve for R
107+
where
108+
R: Resolve + 'static,
109+
{
110+
#[inline]
111+
fn into_resolve(self) -> Arc<dyn Resolve> {
112+
Arc::new(self)
113+
}
114+
}
115+
92116
/// Adapter that wraps a [`Resolve`] trait object to work with Tower's `Service` trait.
93117
///
94118
/// This allows custom DNS resolvers implementing `Resolve` to be used in contexts

0 commit comments

Comments
 (0)