trillium/transport.rs
1use futures_lite::{AsyncRead, AsyncWrite};
2use std::{any::Any, borrow::Cow, io::Result, net::SocketAddr, time::Duration};
3
4/// # The interface that the http protocol is communicated over.
5///
6/// This trait supports several common interfaces supported by tcp
7/// streams, but also can be implemented for other stream types. All trait
8/// functions are currently optional.
9///
10/// **Note:** `Transport` uses the [`AsyncWrite`](futures_lite::AsyncWrite) and
11/// [`AsyncRead`](futures_lite::AsyncRead) traits from [`futures-lite`](futures_lite), which differ
12/// from the tokio `AsyncRead` / `AsyncWrite` traits. Runtime adapters handle bridging these at the
13/// boundary.
14#[allow(unused)]
15pub trait Transport: Any + AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static {
16 /// # Sets the linger duration of this transport by setting the `SO_LINGER` option
17 ///
18 /// See [`std::net::TcpStream::set_linger`]
19 /// Optional to implement.
20 ///
21 /// # Errors
22 ///
23 /// Return an error if this transport supports setting linger and
24 /// attempting to do so is unsuccessful.
25 fn set_linger(&mut self, linger: Option<Duration>) -> Result<()> {
26 Ok(())
27 }
28
29 /// # Sets the value of the `TCP_NODELAY` option on this transport.
30 ///
31 /// See [`std::net::TcpStream::set_nodelay`].
32 /// Optional to implement.
33 ///
34 /// # Errors
35 ///
36 /// Return an error if this transport supports setting nodelay and
37 /// attempting to do so is unsuccessful.
38 fn set_nodelay(&mut self, nodelay: bool) -> Result<()> {
39 Ok(())
40 }
41
42 /// # Sets the value for the `IP_TTL` option on this transport.
43 ///
44 /// See [`std::net::TcpStream::set_ttl`]
45 /// Optional to implement
46 ///
47 /// # Errors
48 ///
49 /// Return an error if this transport supports setting ttl and
50 /// attempting to do so is unsuccessful.
51 fn set_ip_ttl(&mut self, ttl: u32) -> Result<()> {
52 Ok(())
53 }
54
55 /// # Returns the socket address of the remote peer of this transport.
56 ///
57 /// # Errors
58 ///
59 /// Return an error if this transport supports retrieving the
60 /// remote peer but attempting to do so is unsuccessful.
61 fn peer_addr(&self) -> Result<Option<SocketAddr>> {
62 Ok(None)
63 }
64
65 /// # The protocol negotiated via TLS ALPN with the peer, if any.
66 ///
67 /// Used by the runtime adapter to dispatch between HTTP/1.1 and HTTP/2 on the same TLS
68 /// listener (`Some(b"h2")` → HTTP/2, anything else → HTTP/1.1) and by the client to pick
69 /// between HTTP/1.1 and HTTP/2 when speaking to a server.
70 ///
71 /// The returned [`Cow`] borrows from `self` when the backend exposes the protocol as a
72 /// borrowed slice (e.g. rustls) and is owned when the backend copies (e.g. native-tls). The
73 /// default returns `None` so transports that don't speak TLS — or don't want to opt into
74 /// ALPN-based dispatch — don't need to implement it.
75 fn negotiated_alpn(&self) -> Option<Cow<'_, [u8]>> {
76 None
77 }
78}
79
80impl Transport for Box<dyn Transport> {
81 fn set_linger(&mut self, linger: Option<Duration>) -> Result<()> {
82 (**self).set_linger(linger)
83 }
84
85 fn set_nodelay(&mut self, nodelay: bool) -> Result<()> {
86 (**self).set_nodelay(nodelay)
87 }
88
89 fn set_ip_ttl(&mut self, ttl: u32) -> Result<()> {
90 (**self).set_ip_ttl(ttl)
91 }
92
93 fn peer_addr(&self) -> Result<Option<SocketAddr>> {
94 (**self).peer_addr()
95 }
96
97 fn negotiated_alpn(&self) -> Option<Cow<'_, [u8]>> {
98 (**self).negotiated_alpn()
99 }
100}
101
102/// Erase a transport's type. A transport that is already a `Box<dyn Transport>` passes through
103/// as-is rather than gaining a second allocation and vtable hop.
104pub fn box_transport<T: Transport>(transport: T) -> Box<dyn Transport> {
105 same_type::<T, Box<dyn Transport>>(transport).unwrap_or_else(|transport| Box::new(transport))
106}
107
108/// The inverse of [`box_transport`].
109///
110/// # Panics
111///
112/// Panics if `T` is not the type that was erased.
113pub fn unbox_transport<T: Transport>(transport: Box<dyn Transport>) -> T {
114 same_type::<Box<dyn Transport>, T>(transport).unwrap_or_else(|transport| {
115 *(transport as Box<dyn Any>)
116 .downcast()
117 .expect("attempted to downcast to the wrong transport type")
118 })
119}
120
121/// `Ok` if `T` and `U` are the same type, without allocating. Each instantiation compares two
122/// constant `TypeId`s, so this optimizes down to whichever branch applies.
123fn same_type<T: Any, U: Any>(value: T) -> std::result::Result<U, T> {
124 let mut slot = Some(value);
125 if let Some(same) = (&mut slot as &mut dyn Any).downcast_mut::<Option<U>>() {
126 return Ok(same.take().expect("slot was just filled"));
127 }
128 Err(slot.expect("slot was just filled"))
129}
130
131impl Transport for trillium_http::Synthetic {}
132
133impl Transport for trillium_http::h2::H2Transport {
134 fn negotiated_alpn(&self) -> Option<Cow<'_, [u8]>> {
135 Some(Cow::Borrowed(b"h2"))
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::{Transport, box_transport, unbox_transport};
142 use trillium_http::Synthetic;
143
144 fn address(transport: &dyn Transport) -> *const () {
145 std::ptr::from_ref(transport).cast()
146 }
147
148 #[test]
149 fn an_erased_transport_is_not_boxed_again() {
150 let erased: Box<dyn Transport> = Box::new(Synthetic::from("hello"));
151 let original = address(&*erased);
152
153 let reboxed = box_transport(erased);
154 assert_eq!(address(&*reboxed), original);
155
156 let unboxed: Box<dyn Transport> = unbox_transport(reboxed);
157 assert_eq!(address(&*unboxed), original);
158
159 let _: Synthetic = unbox_transport(unboxed);
160 }
161}