Skip to main content

iota_http/
listener.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::time::Duration;
6
7/// Types that can listen for connections.
8pub trait Listener: Send + 'static {
9    /// The listener's IO type.
10    type Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static;
11
12    /// The listener's address type.
13    // all these bounds are necessary to add this information in a request extension
14    type Addr: Clone + Send + Sync + 'static;
15
16    /// Accept a new incoming connection to this listener.
17    ///
18    /// If the underlying accept call can return an error, this function must
19    /// take care of logging and retrying.
20    fn accept(&mut self) -> impl std::future::Future<Output = (Self::Io, Self::Addr)> + Send;
21
22    /// Returns the local address that this listener is bound to.
23    fn local_addr(&self) -> std::io::Result<Self::Addr>;
24}
25
26/// Extensions to [`Listener`].
27pub trait ListenerExt: Listener + Sized {
28    /// Run a mutable closure on every accepted `Io`.
29    ///
30    /// # Example
31    ///
32    /// ```
33    /// use iota_http::ListenerExt;
34    /// use tracing::trace;
35    ///
36    /// # async {
37    /// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
38    ///     .await
39    ///     .unwrap()
40    ///     .tap_io(|tcp_stream| {
41    ///         if let Err(err) = tcp_stream.set_nodelay(true) {
42    ///             trace!("failed to set TCP_NODELAY on incoming connection: {err:#}");
43    ///         }
44    ///     });
45    /// # };
46    /// ```
47    fn tap_io<F>(self, tap_fn: F) -> TapIo<Self, F>
48    where
49        F: FnMut(&mut Self::Io) + Send + 'static,
50    {
51        TapIo {
52            listener: self,
53            tap_fn,
54        }
55    }
56}
57
58impl<L: Listener> ListenerExt for L {}
59
60impl Listener for tokio::net::TcpListener {
61    type Io = tokio::net::TcpStream;
62    type Addr = std::net::SocketAddr;
63
64    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
65        loop {
66            match Self::accept(self).await {
67                Ok(tup) => return tup,
68                Err(e) => handle_accept_error(e).await,
69            }
70        }
71    }
72
73    #[inline]
74    fn local_addr(&self) -> std::io::Result<Self::Addr> {
75        Self::local_addr(self)
76    }
77}
78
79#[derive(Debug)]
80pub struct TcpListenerWithOptions {
81    inner: tokio::net::TcpListener,
82    keepalive: Option<Duration>,
83}
84
85impl TcpListenerWithOptions {
86    pub fn new<A: std::net::ToSocketAddrs>(
87        addr: A,
88        keepalive: Option<Duration>,
89    ) -> Result<Self, crate::BoxError> {
90        let std_listener = std::net::TcpListener::bind(addr)?;
91        std_listener.set_nonblocking(true)?;
92        let listener = tokio::net::TcpListener::from_std(std_listener)?;
93
94        Ok(Self::from_listener(listener, keepalive))
95    }
96
97    /// Creates a new `TcpIncoming` from an existing `tokio::net::TcpListener`.
98    pub fn from_listener(listener: tokio::net::TcpListener, keepalive: Option<Duration>) -> Self {
99        Self {
100            inner: listener,
101            keepalive,
102        }
103    }
104
105    // Consistent with hyper-0.14, this function does not return an error.
106    fn set_accepted_socket_options(&self, stream: &tokio::net::TcpStream) {
107        if let Err(e) = stream.set_nodelay(true) {
108            tracing::warn!("error trying to set TCP nodelay: {}", e);
109        }
110
111        if let Some(timeout) = self.keepalive {
112            let sock_ref = socket2::SockRef::from(&stream);
113            let sock_keepalive = socket2::TcpKeepalive::new().with_time(timeout);
114
115            if let Err(e) = sock_ref.set_tcp_keepalive(&sock_keepalive) {
116                tracing::warn!("error trying to set TCP keepalive: {}", e);
117            }
118        }
119    }
120}
121
122impl Listener for TcpListenerWithOptions {
123    type Io = tokio::net::TcpStream;
124    type Addr = std::net::SocketAddr;
125
126    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
127        let (io, addr) = Listener::accept(&mut self.inner).await;
128        self.set_accepted_socket_options(&io);
129        (io, addr)
130    }
131
132    #[inline]
133    fn local_addr(&self) -> std::io::Result<Self::Addr> {
134        Listener::local_addr(&self.inner)
135    }
136}
137
138// Uncomment once we update tokio to >=1.41.0
139// #[cfg(unix)]
140// impl Listener for tokio::net::UnixListener {
141//     type Io = tokio::net::UnixStream;
142//     type Addr = std::os::unix::net::SocketAddr;
143
144//     async fn accept(&mut self) -> (Self::Io, Self::Addr) {
145//         loop {
146//             match Self::accept(self).await {
147//                 Ok((io, addr)) => return (io, addr.into()),
148//                 Err(e) => handle_accept_error(e).await,
149//             }
150//         }
151//     }
152
153//     #[inline]
154//     fn local_addr(&self) -> std::io::Result<Self::Addr> {
155//         Self::local_addr(self).map(Into::into)
156//     }
157// }
158
159/// Return type of [`ListenerExt::tap_io`].
160///
161/// See that method for details.
162pub struct TapIo<L, F> {
163    listener: L,
164    tap_fn: F,
165}
166
167impl<L, F> std::fmt::Debug for TapIo<L, F>
168where
169    L: Listener + std::fmt::Debug,
170{
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        f.debug_struct("TapIo")
173            .field("listener", &self.listener)
174            .finish_non_exhaustive()
175    }
176}
177
178impl<L, F> Listener for TapIo<L, F>
179where
180    L: Listener,
181    F: FnMut(&mut L::Io) + Send + 'static,
182{
183    type Io = L::Io;
184    type Addr = L::Addr;
185
186    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
187        let (mut io, addr) = self.listener.accept().await;
188        (self.tap_fn)(&mut io);
189        (io, addr)
190    }
191
192    fn local_addr(&self) -> std::io::Result<Self::Addr> {
193        self.listener.local_addr()
194    }
195}
196
197async fn handle_accept_error(e: std::io::Error) {
198    if is_connection_error(&e) {
199        return;
200    }
201
202    // [From `hyper::Server` in 0.14](https://github.com/hyperium/hyper/blob/v0.14.27/src/server/tcp.rs#L186)
203    //
204    // > A possible scenario is that the process has hit the max open files
205    // > allowed, and so trying to accept a new connection will fail with
206    // > `EMFILE`. In some cases, it's preferable to just wait for some time, if
207    // > the application will likely close some files (or connections), and try
208    // > to accept the connection again. If this option is `true`, the error
209    // > will be logged at the `error` level, since it is still a big deal,
210    // > and then the listener will sleep for 1 second.
211    //
212    // hyper allowed customizing this but axum does not.
213    tracing::error!("accept error: {e}");
214    tokio::time::sleep(Duration::from_secs(1)).await;
215}
216
217fn is_connection_error(e: &std::io::Error) -> bool {
218    use std::io::ErrorKind;
219
220    matches!(
221        e.kind(),
222        ErrorKind::ConnectionRefused
223            | ErrorKind::ConnectionAborted
224            | ErrorKind::ConnectionReset
225            | ErrorKind::BrokenPipe
226            | ErrorKind::Interrupted
227            | ErrorKind::WouldBlock
228            | ErrorKind::TimedOut
229    )
230}