1use std::{
6 borrow::Cow,
7 fmt,
8 net::{IpAddr, SocketAddr},
9};
10
11use faststr::FastStr;
12use http::uri::{Scheme, Uri};
13use volo::{client::Apply, context::Context, net::Address};
14
15use super::utils::{get_default_port, is_default_port};
16use crate::{
17 client::dns::Port,
18 context::ClientContext,
19 error::{
20 ClientError,
21 client::{Result, bad_scheme, no_address, port_unavailable, scheme_unavailable},
22 },
23 utils::consts,
24};
25
26#[derive(Clone, Debug, Default)]
28pub enum Target {
29 #[default]
30 None,
32 Remote(RemoteTarget),
34 #[cfg(target_family = "unix")]
36 Local(std::os::unix::net::SocketAddr),
37}
38
39impl fmt::Display for Target {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 Target::None => f.write_str("none"),
43 Target::Remote(rt) => write!(f, "{rt}"),
44 #[cfg(target_family = "unix")]
45 Target::Local(sa) => {
46 if let Some(path) = sa.as_pathname().and_then(std::path::Path::to_str) {
47 f.write_str(path)
48 } else {
49 f.write_str("[unnamed]")
50 }
51 }
52 }
53 }
54}
55
56#[derive(Clone, Debug)]
58pub struct RemoteTarget {
59 pub scheme: Scheme,
61 pub host: RemoteHost,
63 pub port: u16,
65}
66
67impl fmt::Display for RemoteTarget {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 f.write_str(self.scheme.as_str())?;
70 f.write_str("://")?;
71 write!(f, "{}", self.host)?;
72 if !is_default_port(&self.scheme, self.port) {
73 write!(f, ":{}", self.port)?;
74 }
75 Ok(())
76 }
77}
78
79#[derive(Clone, Debug)]
81pub enum RemoteHost {
82 Ip(IpAddr),
84 Name(FastStr),
86}
87
88impl fmt::Display for RemoteHost {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 match self {
91 Self::Ip(ip) => {
92 if ip.is_ipv4() {
93 write!(f, "{ip}")
94 } else {
95 write!(f, "[{ip}]")
96 }
97 }
98 Self::Name(name) => f.write_str(name),
99 }
100 }
101}
102
103fn check_scheme(scheme: &Scheme) -> Result<()> {
104 if scheme == &Scheme::HTTPS {
105 #[cfg(not(feature = "__tls"))]
106 {
107 tracing::error!("[Volo-HTTP] https is not allowed when feature `tls` is not enabled");
108 return Err(bad_scheme(scheme.clone()));
109 }
110 #[cfg(feature = "__tls")]
111 return Ok(());
112 }
113 if scheme == &Scheme::HTTP {
114 return Ok(());
115 }
116 tracing::error!("[Volo-HTTP] scheme '{scheme}' is unsupported");
117 Err(bad_scheme(scheme.clone()))
118}
119
120impl Target {
121 pub const unsafe fn new_host_unchecked(scheme: Scheme, host: FastStr, port: u16) -> Self {
131 Self::Remote(RemoteTarget {
132 scheme,
133 host: RemoteHost::Name(host),
134 port,
135 })
136 }
137
138 pub const unsafe fn new_addr_unchecked(scheme: Scheme, ip: IpAddr, port: u16) -> Self {
148 Self::Remote(RemoteTarget {
149 scheme,
150 host: RemoteHost::Ip(ip),
151 port,
152 })
153 }
154
155 pub fn new_host<S>(scheme: Option<Scheme>, host: S, port: Option<u16>) -> Result<Self>
157 where
158 S: Into<Cow<'static, str>>,
159 {
160 let scheme = scheme.unwrap_or(Scheme::HTTP);
161 check_scheme(&scheme)?;
162 let host = FastStr::from(host.into());
163 let port = match port {
164 Some(p) => p,
165 None => get_default_port(&scheme),
166 };
167 Ok(unsafe { Self::new_host_unchecked(scheme, host, port) })
169 }
170
171 pub fn new_addr(scheme: Option<Scheme>, ip: IpAddr, port: Option<u16>) -> Result<Self> {
173 let scheme = scheme.unwrap_or(Scheme::HTTP);
174 check_scheme(&scheme)?;
175 let port = match port {
176 Some(p) => p,
177 None => get_default_port(&scheme),
178 };
179 Ok(unsafe { Self::new_addr_unchecked(scheme, ip, port) })
181 }
182
183 pub fn from_host<S>(host: S) -> Self
185 where
186 S: Into<Cow<'static, str>>,
187 {
188 let host = FastStr::from(host.into());
189 unsafe { Self::new_host_unchecked(Scheme::HTTP, host, consts::HTTP_DEFAULT_PORT) }
191 }
192
193 pub fn from_uri(uri: &Uri) -> Result<Self> {
195 let scheme = uri.scheme().cloned().unwrap_or(Scheme::HTTP);
196 check_scheme(&scheme)?;
197 let host = uri.host().ok_or_else(no_address)?;
198 let port = match uri.port_u16() {
199 Some(p) => p,
200 None => get_default_port(&scheme),
201 };
202
203 Ok(unsafe {
205 match host.parse::<IpAddr>() {
206 Ok(ip) => Self::new_addr_unchecked(scheme, ip, port),
207 Err(_) => {
208 Self::new_host_unchecked(scheme, FastStr::from_string(host.to_owned()), port)
209 }
210 }
211 })
212 }
213
214 pub fn set_scheme(&mut self, scheme: Scheme) -> Result<()> {
219 let rt = match self.remote_mut() {
220 Some(rt) => rt,
221 None => {
222 tracing::warn!("[Volo-HTTP] set scheme to an empty target or uds is invalid");
223 return Err(scheme_unavailable());
224 }
225 };
226 check_scheme(&scheme)?;
227 if is_default_port(&rt.scheme, rt.port) {
228 rt.port = get_default_port(&scheme);
229 }
230 rt.scheme = scheme;
231 Ok(())
232 }
233
234 pub fn set_port(&mut self, port: u16) -> Result<()> {
236 let rt = match self.remote_mut() {
237 Some(rt) => rt,
238 None => {
239 tracing::warn!("[Volo-HTTP] set port to an empty target or uds is invalid");
240 return Err(port_unavailable());
241 }
242 };
243 rt.port = port;
244 Ok(())
245 }
246
247 pub fn is_none(&self) -> bool {
249 matches!(self, Target::None)
250 }
251
252 pub fn remote_ref(&self) -> Option<&RemoteTarget> {
254 match self {
255 Self::Remote(remote) => Some(remote),
256 _ => None,
257 }
258 }
259
260 pub fn remote_mut(&mut self) -> Option<&mut RemoteTarget> {
262 match self {
263 Self::Remote(remote) => Some(remote),
264 _ => None,
265 }
266 }
267
268 pub fn remote_ip(&self) -> Option<&IpAddr> {
270 if let Self::Remote(rt) = &self {
271 if let RemoteHost::Ip(ip) = &rt.host {
272 return Some(ip);
273 }
274 }
275 None
276 }
277
278 pub fn remote_host(&self) -> Option<&FastStr> {
280 if let Self::Remote(rt) = &self {
281 if let RemoteHost::Name(name) = &rt.host {
282 return Some(name);
283 }
284 }
285 None
286 }
287
288 #[cfg(target_family = "unix")]
290 pub fn unix_socket_addr(&self) -> Option<&std::os::unix::net::SocketAddr> {
291 if let Self::Local(sa) = &self {
292 Some(sa)
293 } else {
294 None
295 }
296 }
297
298 pub fn scheme(&self) -> Option<&Scheme> {
300 if let Self::Remote(rt) = self {
301 Some(&rt.scheme)
302 } else {
303 None
304 }
305 }
306
307 pub fn port(&self) -> Option<u16> {
309 if let Self::Remote(rt) = self {
310 Some(rt.port)
311 } else {
312 None
313 }
314 }
315}
316
317impl From<Address> for Target {
318 fn from(value: Address) -> Self {
319 match value {
320 Address::Ip(sa) => {
321 unsafe { Target::new_addr_unchecked(Scheme::HTTP, sa.ip(), sa.port()) }
323 }
324 #[cfg(target_family = "unix")]
325 Address::Unix(uds) => Target::Local(uds),
326 }
327 }
328}
329
330fn ipv6_strip_brackets(src: FastStr) -> FastStr {
331 let bytes = src.as_bytes();
332 match (bytes.first(), bytes.last()) {
333 (Some(b'['), Some(b']')) => unsafe { src.index(1, src.len() - 1) },
335 _ => src,
336 }
337}
338
339impl Apply<ClientContext> for Target {
340 type Error = ClientError;
341
342 fn apply(self, cx: &mut ClientContext) -> Result<(), Self::Error> {
343 cx.set_target(self.clone());
344
345 match self {
346 Self::Remote(rt) => {
347 match rt.host {
348 RemoteHost::Ip(ip) => {
349 let sa = SocketAddr::new(ip, rt.port);
350 let callee = cx.rpc_info_mut().callee_mut();
351 callee.set_service_name(FastStr::from_string(format!("{}", sa.ip())));
352 callee.set_address(Address::Ip(sa));
353 }
354 RemoteHost::Name(host) => {
355 let port = rt.port;
356 let callee = cx.rpc_info_mut().callee_mut();
357 callee.set_service_name(ipv6_strip_brackets(host));
358 callee.insert(Port(port));
362 }
363 }
364 }
365 #[cfg(target_family = "unix")]
366 Self::Local(uds) => {
367 let callee = cx.rpc_info_mut().callee_mut();
368 callee.set_address(Address::Unix(uds));
369 callee.set_service_name(FastStr::from_static_str("unix-domain-socket"));
370 }
371 Self::None => {}
372 }
373
374 Ok(())
375 }
376}
377
378#[cfg(test)]
379mod target_tests {
380 use faststr::FastStr;
381
382 use super::ipv6_strip_brackets;
383
384 #[test]
385 fn ipv6_strip_test() {
386 {
387 let s = FastStr::from_static_str("foo");
388 assert_eq!(ipv6_strip_brackets(s.clone()), s);
389 }
390 {
391 let s = FastStr::from_static_str("127.0.0.1");
392 assert_eq!(ipv6_strip_brackets(s.clone()), s);
393 }
394 {
395 let s = FastStr::from_static_str("[[[");
396 assert_eq!(ipv6_strip_brackets(s.clone()), s);
397 }
398 {
399 let s = FastStr::from_static_str("]]]");
400 assert_eq!(ipv6_strip_brackets(s.clone()), s);
401 }
402 {
403 let s = FastStr::from_static_str("(::1)");
404 assert_eq!(ipv6_strip_brackets(s.clone()), s);
405 }
406 {
407 let s = FastStr::from_static_str("[::1]");
408 assert_eq!(ipv6_strip_brackets(s), "::1");
409 }
410 }
411}