use std::{
borrow::Cow,
fmt,
net::{IpAddr, SocketAddr},
};
use faststr::FastStr;
use http::uri::{Scheme, Uri};
use volo::{client::Apply, context::Context, net::Address};
use super::utils::{get_default_port, is_default_port};
use crate::{
client::dns::Port,
context::ClientContext,
error::{
ClientError,
client::{Result, bad_scheme, no_address, port_unavailable, scheme_unavailable},
},
utils::consts,
};
#[derive(Clone, Debug, Default)]
pub enum Target {
#[default]
None,
Remote(RemoteTarget),
#[cfg(target_family = "unix")]
Local(std::os::unix::net::SocketAddr),
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Target::None => f.write_str("none"),
Target::Remote(rt) => write!(f, "{rt}"),
#[cfg(target_family = "unix")]
Target::Local(sa) => {
if let Some(path) = sa.as_pathname().and_then(std::path::Path::to_str) {
f.write_str(path)
} else {
f.write_str("[unnamed]")
}
}
}
}
}
#[derive(Clone, Debug)]
pub struct RemoteTarget {
pub scheme: Scheme,
pub host: RemoteHost,
pub port: u16,
}
impl fmt::Display for RemoteTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.scheme.as_str())?;
f.write_str("://")?;
write!(f, "{}", self.host)?;
if !is_default_port(&self.scheme, self.port) {
write!(f, ":{}", self.port)?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub enum RemoteHost {
Ip(IpAddr),
Name(FastStr),
}
impl fmt::Display for RemoteHost {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ip(ip) => {
if ip.is_ipv4() {
write!(f, "{ip}")
} else {
write!(f, "[{ip}]")
}
}
Self::Name(name) => f.write_str(name),
}
}
}
fn check_scheme(scheme: &Scheme) -> Result<()> {
if scheme == &Scheme::HTTPS {
#[cfg(not(feature = "__tls"))]
{
tracing::error!("[Volo-HTTP] https is not allowed when feature `tls` is not enabled");
return Err(bad_scheme(scheme.clone()));
}
#[cfg(feature = "__tls")]
return Ok(());
}
if scheme == &Scheme::HTTP {
return Ok(());
}
tracing::error!("[Volo-HTTP] scheme '{scheme}' is unsupported");
Err(bad_scheme(scheme.clone()))
}
impl Target {
pub const unsafe fn new_host_unchecked(scheme: Scheme, host: FastStr, port: u16) -> Self {
Self::Remote(RemoteTarget {
scheme,
host: RemoteHost::Name(host),
port,
})
}
pub const unsafe fn new_addr_unchecked(scheme: Scheme, ip: IpAddr, port: u16) -> Self {
Self::Remote(RemoteTarget {
scheme,
host: RemoteHost::Ip(ip),
port,
})
}
pub fn new_host<S>(scheme: Option<Scheme>, host: S, port: Option<u16>) -> Result<Self>
where
S: Into<Cow<'static, str>>,
{
let scheme = scheme.unwrap_or(Scheme::HTTP);
check_scheme(&scheme)?;
let host = FastStr::from(host.into());
let port = match port {
Some(p) => p,
None => get_default_port(&scheme),
};
Ok(unsafe { Self::new_host_unchecked(scheme, host, port) })
}
pub fn new_addr(scheme: Option<Scheme>, ip: IpAddr, port: Option<u16>) -> Result<Self> {
let scheme = scheme.unwrap_or(Scheme::HTTP);
check_scheme(&scheme)?;
let port = match port {
Some(p) => p,
None => get_default_port(&scheme),
};
Ok(unsafe { Self::new_addr_unchecked(scheme, ip, port) })
}
pub fn from_host<S>(host: S) -> Self
where
S: Into<Cow<'static, str>>,
{
let host = FastStr::from(host.into());
unsafe { Self::new_host_unchecked(Scheme::HTTP, host, consts::HTTP_DEFAULT_PORT) }
}
pub fn from_uri(uri: &Uri) -> Result<Self> {
let scheme = uri.scheme().cloned().unwrap_or(Scheme::HTTP);
check_scheme(&scheme)?;
let host = uri.host().ok_or_else(no_address)?;
let port = match uri.port_u16() {
Some(p) => p,
None => get_default_port(&scheme),
};
Ok(unsafe {
match host.parse::<IpAddr>() {
Ok(ip) => Self::new_addr_unchecked(scheme, ip, port),
Err(_) => {
Self::new_host_unchecked(scheme, FastStr::from_string(host.to_owned()), port)
}
}
})
}
pub fn set_scheme(&mut self, scheme: Scheme) -> Result<()> {
let rt = match self.remote_mut() {
Some(rt) => rt,
None => {
tracing::warn!("[Volo-HTTP] set scheme to an empty target or uds is invalid");
return Err(scheme_unavailable());
}
};
check_scheme(&scheme)?;
if is_default_port(&rt.scheme, rt.port) {
rt.port = get_default_port(&scheme);
}
rt.scheme = scheme;
Ok(())
}
pub fn set_port(&mut self, port: u16) -> Result<()> {
let rt = match self.remote_mut() {
Some(rt) => rt,
None => {
tracing::warn!("[Volo-HTTP] set port to an empty target or uds is invalid");
return Err(port_unavailable());
}
};
rt.port = port;
Ok(())
}
pub fn is_none(&self) -> bool {
matches!(self, Target::None)
}
pub fn remote_ref(&self) -> Option<&RemoteTarget> {
match self {
Self::Remote(remote) => Some(remote),
_ => None,
}
}
pub fn remote_mut(&mut self) -> Option<&mut RemoteTarget> {
match self {
Self::Remote(remote) => Some(remote),
_ => None,
}
}
pub fn remote_ip(&self) -> Option<&IpAddr> {
if let Self::Remote(rt) = &self {
if let RemoteHost::Ip(ip) = &rt.host {
return Some(ip);
}
}
None
}
pub fn remote_host(&self) -> Option<&FastStr> {
if let Self::Remote(rt) = &self {
if let RemoteHost::Name(name) = &rt.host {
return Some(name);
}
}
None
}
#[cfg(target_family = "unix")]
pub fn unix_socket_addr(&self) -> Option<&std::os::unix::net::SocketAddr> {
if let Self::Local(sa) = &self {
Some(sa)
} else {
None
}
}
pub fn scheme(&self) -> Option<&Scheme> {
if let Self::Remote(rt) = self {
Some(&rt.scheme)
} else {
None
}
}
pub fn port(&self) -> Option<u16> {
if let Self::Remote(rt) = self {
Some(rt.port)
} else {
None
}
}
}
impl From<Address> for Target {
fn from(value: Address) -> Self {
match value {
Address::Ip(sa) => {
unsafe { Target::new_addr_unchecked(Scheme::HTTP, sa.ip(), sa.port()) }
}
#[cfg(target_family = "unix")]
Address::Unix(uds) => Target::Local(uds),
#[allow(unreachable_patterns)]
_ => unimplemented!("unsupported type of address"),
}
}
}
fn ipv6_strip_brackets(src: FastStr) -> FastStr {
let bytes = src.as_bytes();
match (bytes.first(), bytes.last()) {
(Some(b'['), Some(b']')) => unsafe { src.index(1, src.len() - 1) },
_ => src,
}
}
impl Apply<ClientContext> for Target {
type Error = ClientError;
fn apply(self, cx: &mut ClientContext) -> Result<(), Self::Error> {
cx.set_target(self.clone());
match self {
Self::Remote(rt) => {
match rt.host {
RemoteHost::Ip(ip) => {
let sa = SocketAddr::new(ip, rt.port);
let callee = cx.rpc_info_mut().callee_mut();
callee.set_service_name(FastStr::from_string(format!("{}", sa.ip())));
callee.set_address(Address::Ip(sa));
}
RemoteHost::Name(host) => {
let port = rt.port;
let callee = cx.rpc_info_mut().callee_mut();
callee.set_service_name(ipv6_strip_brackets(host));
callee.insert(Port(port));
}
}
}
#[cfg(target_family = "unix")]
Self::Local(uds) => {
let callee = cx.rpc_info_mut().callee_mut();
callee.set_address(Address::Unix(uds));
callee.set_service_name(FastStr::from_static_str("unix-domain-socket"));
}
Self::None => {}
}
Ok(())
}
}
#[cfg(test)]
mod target_tests {
use faststr::FastStr;
use super::ipv6_strip_brackets;
#[test]
fn ipv6_strip_test() {
{
let s = FastStr::from_static_str("foo");
assert_eq!(ipv6_strip_brackets(s.clone()), s);
}
{
let s = FastStr::from_static_str("127.0.0.1");
assert_eq!(ipv6_strip_brackets(s.clone()), s);
}
{
let s = FastStr::from_static_str("[[[");
assert_eq!(ipv6_strip_brackets(s.clone()), s);
}
{
let s = FastStr::from_static_str("]]]");
assert_eq!(ipv6_strip_brackets(s.clone()), s);
}
{
let s = FastStr::from_static_str("(::1)");
assert_eq!(ipv6_strip_brackets(s.clone()), s);
}
{
let s = FastStr::from_static_str("[::1]");
assert_eq!(ipv6_strip_brackets(s), "::1");
}
}
}