trillium_tokio/
client.rs

1use crate::TokioTransport;
2use async_compat::Compat;
3use std::{
4    future::Future,
5    io::{Error, ErrorKind, Result},
6    time::Duration,
7};
8use tokio::net::TcpStream;
9use trillium_server_common::{
10    async_trait,
11    url::{Host, Url},
12    Connector, Transport,
13};
14
15/**
16configuration for the tcp Connector
17*/
18#[derive(Default, Debug, Clone, Copy)]
19pub struct ClientConfig {
20    /// disable [nagle's algorithm](https://en.wikipedia.org/wiki/Nagle%27s_algorithm)
21    /// see [`TcpStream::set_nodelay`] for more info
22    pub nodelay: Option<bool>,
23
24    /// time to live for the tcp protocol. set [`TcpStream::set_ttl`] for more info
25    pub ttl: Option<u32>,
26
27    /// sets SO_LINGER. I don't really understand this, but see
28    /// [`TcpStream::set_linger`] for more info
29    pub linger: Option<Option<Duration>>,
30}
31
32impl ClientConfig {
33    /// constructs a default ClientConfig
34    pub const fn new() -> Self {
35        Self {
36            nodelay: None,
37            ttl: None,
38            linger: None,
39        }
40    }
41
42    /// chainable setter to set default nodelay
43    pub const fn with_nodelay(mut self, nodelay: bool) -> Self {
44        self.nodelay = Some(nodelay);
45        self
46    }
47
48    /// chainable setter for ip ttl
49    pub const fn with_ttl(mut self, ttl: u32) -> Self {
50        self.ttl = Some(ttl);
51        self
52    }
53
54    /// chainable setter for linger
55    pub const fn with_linger(mut self, linger: Option<Duration>) -> Self {
56        self.linger = Some(linger);
57        self
58    }
59}
60
61#[async_trait]
62impl Connector for ClientConfig {
63    type Transport = TokioTransport<Compat<TcpStream>>;
64
65    async fn connect(&self, url: &Url) -> Result<Self::Transport> {
66        if url.scheme() != "http" {
67            return Err(Error::new(
68                ErrorKind::InvalidInput,
69                format!("unknown scheme {}", url.scheme()),
70            ));
71        }
72
73        let host = url
74            .host()
75            .ok_or_else(|| Error::new(ErrorKind::InvalidInput, format!("{url} missing host")))?;
76
77        let port = url
78            .port_or_known_default()
79            // this should be ok because we already checked that the scheme is http, which has a default port
80            .ok_or_else(|| Error::new(ErrorKind::InvalidInput, format!("{url} missing port")))?;
81
82        let mut tcp = match host {
83            Host::Domain(domain) => Self::Transport::connect((domain, port)).await?,
84            Host::Ipv4(ip) => Self::Transport::connect((ip, port)).await?,
85            Host::Ipv6(ip) => Self::Transport::connect((ip, port)).await?,
86        };
87
88        if let Some(nodelay) = self.nodelay {
89            tcp.set_nodelay(nodelay)?;
90        }
91
92        if let Some(ttl) = self.ttl {
93            tcp.set_ip_ttl(ttl)?;
94        }
95
96        if let Some(dur) = self.linger {
97            tcp.set_linger(dur)?;
98        }
99
100        Ok(tcp)
101    }
102
103    fn spawn<Fut: Future<Output = ()> + Send + 'static>(&self, fut: Fut) {
104        tokio::task::spawn(fut);
105    }
106}