Skip to main content

trillium_smol/
client.rs

1use crate::{SmolRuntime, SmolTransport};
2use async_net::TcpStream;
3use std::{
4    io::{Error, ErrorKind, Result},
5    net::SocketAddr,
6};
7use trillium_server_common::{Connector, Destination, Transport, url::Url};
8
9/// configuration for the tcp Connector
10#[derive(Default, Debug, Clone, Copy)]
11pub struct ClientConfig {
12    /// disable [nagle's algorithm](https://en.wikipedia.org/wiki/Nagle%27s_algorithm)
13    pub nodelay: Option<bool>,
14
15    /// set a time to live for the tcp protocol
16    pub ttl: Option<u32>,
17}
18
19impl ClientConfig {
20    /// constructs a default ClientConfig
21    pub const fn new() -> Self {
22        Self {
23            nodelay: None,
24            ttl: None,
25        }
26    }
27
28    /// chainable setter to set default nodelay
29    pub const fn with_nodelay(mut self, nodelay: bool) -> Self {
30        self.nodelay = Some(nodelay);
31        self
32    }
33
34    /// chainable setter for ip ttl
35    pub const fn with_ttl(mut self, ttl: u32) -> Self {
36        self.ttl = Some(ttl);
37        self
38    }
39}
40
41impl Connector for ClientConfig {
42    type Runtime = SmolRuntime;
43    type Transport = SmolTransport<TcpStream>;
44    type Udp = crate::SmolUdpSocket;
45
46    fn runtime(&self) -> Self::Runtime {
47        SmolRuntime::default()
48    }
49
50    async fn connect(&self, url: &Url) -> Result<Self::Transport> {
51        self.connect_to(Destination::from_url(url)?).await
52    }
53
54    async fn connect_to(&self, destination: Destination) -> Result<Self::Transport> {
55        if destination.secure() {
56            return Err(Error::new(
57                ErrorKind::InvalidInput,
58                "this connector does not support TLS",
59            ));
60        }
61
62        let addrs = destination.addrs();
63        let mut tcp = if addrs.is_empty() {
64            let host = destination.host().ok_or_else(|| {
65                Error::new(
66                    ErrorKind::InvalidInput,
67                    "destination has neither host nor addresses",
68                )
69            })?;
70            Self::Transport::connect((host, destination.port())).await?
71        } else {
72            Self::Transport::connect(addrs).await?
73        };
74
75        if let Some(nodelay) = self.nodelay {
76            tcp.set_nodelay(nodelay)?;
77        }
78
79        if let Some(ttl) = self.ttl {
80            tcp.set_ip_ttl(ttl)?;
81        }
82
83        Ok(tcp)
84    }
85
86    async fn resolve(&self, host: &str, port: u16) -> Result<Vec<SocketAddr>> {
87        async_net::resolve((host, port)).await
88    }
89}
90
91/// A [`Connector`] that dials a fixed Unix domain socket path.
92///
93/// Every connection opens a fresh `UnixStream` to `path`, so a single `UnixClientConfig` is safe to
94/// share across a pooled [`Client`](https://docs.trillium.rs/trillium_client/struct.Client.html) making
95/// concurrent requests. The request URL's host and port are ignored — the socket path is the
96/// address — though the URL still supplies request metadata such as the `Host` header.
97///
98/// This handles only the single-socket case. To route different requests to different upstreams (a
99/// mix of TCP and Unix sockets, or several Unix sockets), compose connectors behind a routing
100/// [`Connector`] that dispatches on the [`Destination`].
101#[cfg(unix)]
102#[derive(Clone, Debug)]
103pub struct UnixClientConfig {
104    path: std::path::PathBuf,
105}
106
107#[cfg(unix)]
108impl UnixClientConfig {
109    /// Construct a `UnixClientConfig` that dials the provided Unix domain socket path.
110    pub fn new(path: impl Into<std::path::PathBuf>) -> Self {
111        Self { path: path.into() }
112    }
113
114    async fn dial(&self) -> Result<SmolTransport<async_net::unix::UnixStream>> {
115        SmolTransport::connect_unix(&self.path).await
116    }
117}
118
119#[cfg(unix)]
120impl Connector for UnixClientConfig {
121    type Runtime = SmolRuntime;
122    type Transport = SmolTransport<async_net::unix::UnixStream>;
123    type Udp = ();
124
125    async fn connect(&self, url: &Url) -> Result<Self::Transport> {
126        if url.scheme() == "https" {
127            return Err(Error::new(
128                ErrorKind::InvalidInput,
129                "this connector does not support TLS",
130            ));
131        }
132        self.dial().await
133    }
134
135    async fn connect_to(&self, destination: Destination) -> Result<Self::Transport> {
136        if destination.secure() {
137            return Err(Error::new(
138                ErrorKind::InvalidInput,
139                "this connector does not support TLS",
140            ));
141        }
142        self.dial().await
143    }
144
145    fn runtime(&self) -> Self::Runtime {
146        SmolRuntime::default()
147    }
148
149    async fn resolve(&self, _host: &str, _port: u16) -> Result<Vec<SocketAddr>> {
150        Ok(vec![])
151    }
152}