diff --git a/Cargo.lock b/Cargo.lock index 1e0d6ff4..fa437e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4187,7 +4187,7 @@ dependencies = [ [[package]] name = "volo-http" -version = "0.3.0-rc.3" +version = "0.3.0" dependencies = [ "ahash", "async-broadcast", diff --git a/volo-http/Cargo.toml b/volo-http/Cargo.toml index eaac5baf..8e827d37 100644 --- a/volo-http/Cargo.toml +++ b/volo-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "volo-http" -version = "0.3.0-rc.3" +version = "0.3.0" edition.workspace = true homepage.workspace = true repository.workspace = true @@ -22,29 +22,22 @@ maintenance = { status = "actively-developed" } volo = { version = "0.10", path = "../volo" } ahash.workspace = true -async-broadcast.workspace = true bytes.workspace = true -chrono.workspace = true faststr.workspace = true futures.workspace = true futures-util.workspace = true -hickory-resolver.workspace = true http.workspace = true http-body.workspace = true http-body-util.workspace = true hyper.workspace = true hyper-util = { workspace = true, features = ["tokio"] } -ipnet.workspace = true itoa.workspace = true -memchr.workspace = true metainfo.workspace = true mime.workspace = true -mime_guess.workspace = true motore.workspace = true parking_lot.workspace = true paste.workspace = true pin-project.workspace = true -scopeguard.workspace = true simdutf8.workspace = true thiserror.workspace = true tokio = { workspace = true, features = [ @@ -62,7 +55,16 @@ url.workspace = true # =====optional===== # server optional -matchit = { workspace = true, optional = true } +ipnet = { workspace = true, optional = true } # client ip +matchit = { workspace = true, optional = true } # route matching +memchr = { workspace = true, optional = true } # sse +scopeguard = { workspace = true, optional = true } # defer + +# client optional +async-broadcast = { workspace = true, optional = true } # service discover +chrono = { workspace = true, optional = true } # stat +hickory-resolver = { workspace = true, optional = true } # dns resolver +mime_guess = { workspace = true, optional = true } # serde and form, query, json serde = { workspace = true, optional = true } @@ -106,8 +108,14 @@ full = [ http1 = ["hyper/http1", "hyper-util/http1"] -client = ["http1", "hyper/client"] # client core -server = ["http1", "hyper-util/server", "dep:matchit"] # server core +client = [ + "http1", "hyper/client", + "dep:async-broadcast", "dep:chrono", "dep:hickory-resolver", +] # client core +server = [ + "http1", "hyper-util/server", + "dep:ipnet", "dep:matchit", "dep:memchr", "dep:scopeguard", "dep:mime_guess", +] # server core __serde = ["dep:serde"] # a private feature for enabling `serde` by `serde_xxx` query = ["__serde", "dep:serde_urlencoded"] diff --git a/volo-http/src/client/dns.rs b/volo-http/src/client/dns.rs index 7ca16bc6..9fd6c649 100644 --- a/volo-http/src/client/dns.rs +++ b/volo-http/src/client/dns.rs @@ -7,7 +7,7 @@ use std::{net::SocketAddr, ops::Deref, sync::Arc}; use async_broadcast::Receiver; use faststr::FastStr; use hickory_resolver::{ - config::{ResolverConfig, ResolverOpts}, + config::{LookupIpStrategy, ResolverConfig, ResolverOpts}, AsyncResolver, TokioAsyncResolver, }; use volo::{ @@ -64,9 +64,27 @@ impl DnsResolver { impl Default for DnsResolver { fn default() -> Self { - Self { - resolver: AsyncResolver::tokio_from_system_conf().expect("failed to init dns resolver"), + let (conf, mut opts) = hickory_resolver::system_conf::read_system_conf() + .expect("[Volo-HTTP] DnsResolver: failed to parse dns config"); + if conf + .name_servers() + .first() + .expect("[Volo-HTTP] DnsResolver: no nameserver found") + .socket_addr + .is_ipv6() + { + // The default `LookupIpStrategy` is always `Ipv4thenIpv6`, it may not work in an IPv6 + // only environment. + // + // Here we trust the system configuration and check its first name server. + // + // If the first nameserver is an IPv4 address, we keep the default configuration. + // + // If the first nameserver is an IPv6 address, we need to update the policy to prefer + // IPv6 addresses. + opts.ip_strategy = LookupIpStrategy::Ipv6thenIpv4; } + Self::new(conf, opts) } } diff --git a/volo-http/src/server/extract.rs b/volo-http/src/server/extract.rs index 315aaae3..3aa81c89 100644 --- a/volo-http/src/server/extract.rs +++ b/volo-http/src/server/extract.rs @@ -528,7 +528,7 @@ where parts: Parts, body: B, ) -> Result { - if !content_type_eq(&parts.headers, mime::APPLICATION_WWW_FORM_URLENCODED) { + if !content_type_matches(&parts.headers, mime::APPLICATION, mime::WWW_FORM_URLENCODED) { return Err(crate::error::server::invalid_content_type()); } @@ -555,7 +555,7 @@ where parts: Parts, body: B, ) -> Result { - if !json_content_type(&parts.headers) { + if !content_type_matches(&parts.headers, mime::APPLICATION, mime::JSON) { return Err(crate::error::server::invalid_content_type()); } @@ -580,48 +580,26 @@ fn get_header_value(map: &HeaderMap, key: HeaderName) -> Option<&str> { map.get(key)?.to_str().ok() } -#[cfg(feature = "form")] -fn content_type_eq(map: &HeaderMap, val: mime::Mime) -> bool { - let Some(ty) = get_header_value(map, header::CONTENT_TYPE) else { - return false; - }; - ty == val.essence_str() -} +#[cfg(any(feature = "form", feature = "json"))] +fn content_type_matches( + headers: &HeaderMap, + ty: mime::Name<'static>, + subtype: mime::Name<'static>, +) -> bool { + use std::str::FromStr; -#[cfg(feature = "json")] -fn json_content_type(headers: &HeaderMap) -> bool { - let content_type = match headers.get(header::CONTENT_TYPE) { - Some(content_type) => content_type, - None => { - return false; - } + let Some(content_type) = headers.get(header::CONTENT_TYPE) else { + return false; }; - - let content_type = match content_type.to_str() { - Ok(s) => s, - Err(_) => { - return false; - } + let Ok(content_type) = content_type.to_str() else { + return false; }; - - let mime_type = match content_type.parse::() { - Ok(mime_type) => mime_type, - Err(_) => { - return false; - } + let Ok(mime) = mime::Mime::from_str(content_type) else { + return false; }; - // `application/json` or `application/json+foo` - if mime_type.type_() == mime::APPLICATION && mime_type.subtype() == mime::JSON { - return true; - } - - // `application/foo+json` - if mime_type.suffix() == Some(mime::JSON) { - return true; - } - - false + // `text/xml` or `image/svg+xml` + (mime.type_() == ty && mime.subtype() == subtype) || mime.suffix() == Some(subtype) } #[cfg(test)]