Skip to content

Commit ae17b34

Browse files
authored
fix(volo-http): fix error on ipv6 literal address resolving (#597)
* fix(volo-http): fix error on ipv6 literal address resolving In previous implementation, the client cannot resolve IPv6 literal address in URI like `http://[::1]:8080/` and it will throw `BadHostName` without any context. This commit fixes that and adds context for errors like `BadScheme` and `BadHostName`, which will now print the error message along with its content, it should make troubleshooting easier. * chore(volo-http): bump volo-http to 0.4.1 --------- Signed-off-by: Yu Li <liyu.yukiteru@bytedance.com>
1 parent adcd9bb commit ae17b34

File tree

7 files changed

+109
-53
lines changed

7 files changed

+109
-53
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/src/http/example-http-client.rs

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,6 @@ async fn main() -> Result<(), BoxError> {
2121
.finish();
2222
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
2323

24-
// simple `get` function with dns resolve
25-
println!(
26-
"{}",
27-
get("http://httpbin.org/get").await?.into_string().await?
28-
);
29-
30-
// HTTPS `get`
31-
#[cfg(feature = "__tls")]
32-
{
33-
println!(
34-
"{}",
35-
get("https://httpbin.org/get").await?.into_string().await?
36-
);
37-
}
38-
3924
// create client by builder
4025
let client = {
4126
let mut builder = ClientBuilder::new().layer_outer_front(TargetLayer::new_address(
@@ -48,29 +33,6 @@ async fn main() -> Result<(), BoxError> {
4833
builder.build()?
4934
};
5035

51-
// set host and override the default one
52-
println!(
53-
"{}",
54-
client
55-
.request_builder()
56-
.host("httpbin.org")
57-
.uri("/get")
58-
.send()
59-
.await?
60-
.into_string()
61-
.await?
62-
);
63-
64-
println!(
65-
"{}",
66-
client
67-
.get("http://127.0.0.1:8080/")
68-
.send()
69-
.await?
70-
.into_string()
71-
.await?
72-
);
73-
7436
// use default target address
7537
println!(
7638
"{:?}",
@@ -108,6 +70,15 @@ async fn main() -> Result<(), BoxError> {
10870
.into_string()
10971
.await?
11072
);
73+
println!(
74+
"{}",
75+
client
76+
.get("http://[::1]:8080/")
77+
.send()
78+
.await?
79+
.into_string()
80+
.await?
81+
);
11182

11283
// invalid request because there is no target address
11384
println!(
@@ -119,5 +90,20 @@ async fn main() -> Result<(), BoxError> {
11990
.expect_err("this request should fail"),
12091
);
12192

93+
// simple `get` function with dns resolve
94+
println!(
95+
"{}",
96+
get("http://httpbin.org/get").await?.into_string().await?
97+
);
98+
99+
// HTTPS `get`
100+
#[cfg(feature = "__tls")]
101+
{
102+
println!(
103+
"{}",
104+
get("https://httpbin.org/get").await?.into_string().await?
105+
);
106+
}
107+
122108
Ok(())
123109
}

volo-http/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "volo-http"
3-
version = "0.4.0"
3+
version = "0.4.1"
44
edition.workspace = true
55
homepage.workspace = true
66
repository.workspace = true

volo-http/src/client/dns.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use std::{
66
net::{IpAddr, SocketAddr},
7-
ops::Deref,
7+
ops::{Deref, Index},
88
sync::Arc,
99
};
1010

@@ -61,6 +61,14 @@ impl DnsResolver {
6161

6262
/// Resolve a host to an IP address.
6363
pub async fn resolve(&self, host: &str) -> Option<IpAddr> {
64+
let host = {
65+
let bytes = host.as_bytes();
66+
match (bytes.first(), bytes.last()) {
67+
(Some(b'['), Some(b']')) => host.index(1..host.len() - 1),
68+
_ => host,
69+
}
70+
};
71+
6472
// Note that the Resolver will try to parse the host as an IP address first, so we don't
6573
// need to parse it manually.
6674
self.resolver.lookup_ip(host).await.ok()?.into_iter().next()
@@ -144,7 +152,9 @@ impl Discover for DnsResolver {
144152
return Ok(vec![Arc::new(instance)]);
145153
};
146154
tracing::error!("[Volo-HTTP] DnsResolver: no address resolved");
147-
Err(LoadBalanceError::Discover(Box::new(bad_host_name())))
155+
Err(LoadBalanceError::Discover(Box::new(bad_host_name(
156+
endpoint.service_name(),
157+
))))
148158
}
149159

150160
fn key(&self, endpoint: &Endpoint) -> Self::Key {
@@ -155,3 +165,31 @@ impl Discover for DnsResolver {
155165
None
156166
}
157167
}
168+
169+
#[cfg(test)]
170+
mod dns_tests {
171+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
172+
173+
use crate::client::dns::DnsResolver;
174+
175+
#[tokio::test]
176+
async fn static_resolve() {
177+
let resolver = DnsResolver::default();
178+
179+
assert_eq!(
180+
resolver.resolve("127.0.0.1").await,
181+
Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
182+
);
183+
assert_eq!(
184+
resolver.resolve("::1").await,
185+
Some(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
186+
);
187+
assert_eq!(
188+
resolver.resolve("[::1]").await,
189+
Some(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
190+
);
191+
assert_eq!(resolver.resolve("[::1").await, None);
192+
assert_eq!(resolver.resolve("::1]").await, None);
193+
assert_eq!(resolver.resolve("[::1]:8080").await, None);
194+
}
195+
}

volo-http/src/client/target.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
client::dns::Port,
1818
context::ClientContext,
1919
error::{
20-
client::{bad_address, bad_scheme, no_address, Result},
20+
client::{bad_scheme, no_address, port_unavailable, scheme_unavailable, Result},
2121
ClientError,
2222
},
2323
utils::consts,
@@ -105,7 +105,7 @@ fn check_scheme(scheme: &Scheme) -> Result<()> {
105105
#[cfg(not(feature = "__tls"))]
106106
{
107107
tracing::error!("[Volo-HTTP] https is not allowed when feature `tls` is not enabled");
108-
return Err(bad_scheme());
108+
return Err(bad_scheme(scheme.clone()));
109109
}
110110
#[cfg(feature = "__tls")]
111111
return Ok(());
@@ -114,7 +114,7 @@ fn check_scheme(scheme: &Scheme) -> Result<()> {
114114
return Ok(());
115115
}
116116
tracing::error!("[Volo-HTTP] scheme '{scheme}' is unsupported");
117-
Err(bad_scheme())
117+
Err(bad_scheme(scheme.clone()))
118118
}
119119

120120
impl Target {
@@ -220,7 +220,7 @@ impl Target {
220220
Some(rt) => rt,
221221
None => {
222222
tracing::warn!("[Volo-HTTP] set scheme to an empty target or uds is invalid");
223-
return Err(bad_address());
223+
return Err(scheme_unavailable());
224224
}
225225
};
226226
check_scheme(&scheme)?;
@@ -237,7 +237,7 @@ impl Target {
237237
Some(rt) => rt,
238238
None => {
239239
tracing::warn!("[Volo-HTTP] set port to an empty target or uds is invalid");
240-
return Err(bad_address());
240+
return Err(port_unavailable());
241241
}
242242
};
243243
rt.port = port;
@@ -344,6 +344,7 @@ impl Apply<ClientContext> for Target {
344344
callee.set_address(Address::Ip(sa));
345345
}
346346
RemoteHost::Name(host) => {
347+
println!("Target::apply, host = {host}");
347348
let port = rt.port;
348349
tracing::trace!("[Volo-HTTP] Target::apply: set target to {host}:{port}");
349350
let callee = cx.rpc_info_mut().callee_mut();

volo-http/src/client/transport/connector.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl UnaryService<PeerInfo> for HttpMakeConnection {
9696
match self {
9797
Self::Plain(plain) => {
9898
if req.scheme != Scheme::HTTP {
99-
return Err(bad_scheme());
99+
return Err(bad_scheme(req.scheme));
100100
}
101101
plain.call(req).await
102102
}

volo-http/src/error/client.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -278,14 +278,39 @@ macro_rules! simple_error {
278278
}
279279
}
280280
};
281+
282+
($(#[$attr:meta])* $kind:ident => $name:ident($inner:ty) => $msg:literal) => {
283+
paste! {
284+
#[doc = $kind " error \"" $msg "\""]
285+
$(#[$attr])*
286+
#[derive(Debug, PartialEq, Eq)]
287+
pub struct $name($inner);
288+
289+
$(#[$attr])*
290+
impl ::std::fmt::Display for $name {
291+
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
292+
write!(f, concat!($msg, ": {}"), self.0)
293+
}
294+
}
295+
296+
$(#[$attr])*
297+
impl ::std::error::Error for $name {}
298+
299+
$(#[$attr])*
300+
pub(crate) fn [<$name:snake>](inner: $inner) -> ClientError {
301+
ClientError::new(ErrorKind::$kind, Some($name(inner)))
302+
}
303+
}
304+
};
281305
}
282306

283307
simple_error!(Builder => NoAddress => "missing target address");
284-
simple_error!(Builder => BadScheme => "bad scheme");
308+
simple_error!(Builder => BadScheme(::http::uri::Scheme) => "bad scheme");
285309
#[cfg(not(all(feature = "http1", feature = "http2")))]
286310
simple_error!(Builder => BadVersion => "bad http protocol version");
287-
simple_error!(Builder => BadHostName => "bad host name");
288-
simple_error!(Builder => BadAddress => "bad address");
311+
simple_error!(Builder => BadHostName(::faststr::FastStr) => "bad host name");
312+
simple_error!(Builder => SchemeUnavailable => "scheme is unavailable in current target");
313+
simple_error!(Builder => PortUnavailable => "port is unavailable in current target");
289314
simple_error!(Connect => Retry => "retry");
290315
simple_error!(Request => Timeout => "request timeout");
291316
simple_error!(LoadBalance => NoAvailableEndpoint => "no available endpoint");
@@ -302,8 +327,14 @@ mod client_error_tests {
302327
#[test]
303328
fn types_downcast() {
304329
assert!(no_address().source().unwrap().is::<NoAddress>());
305-
assert!(bad_scheme().source().unwrap().is::<BadScheme>());
306-
assert!(bad_host_name().source().unwrap().is::<BadHostName>());
330+
assert!(bad_scheme(::http::uri::Scheme::HTTP)
331+
.source()
332+
.unwrap()
333+
.is::<BadScheme>());
334+
assert!(bad_host_name(::faststr::FastStr::from_static_str("foo"))
335+
.source()
336+
.unwrap()
337+
.is::<BadHostName>());
307338
assert!(timeout().source().unwrap().is::<Timeout>());
308339
assert!(no_available_endpoint()
309340
.source()

0 commit comments

Comments
 (0)