tenderdash_abci/server.rs
1//! Tenderdash ABCI Server.
2mod codec;
3mod generic;
4
5#[cfg(feature = "tcp")]
6use std::{
7 net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
8 str::FromStr,
9};
10
11use futures::Future;
12#[cfg(feature = "tcp")]
13use tokio::net::TcpListener;
14#[cfg(feature = "unix")]
15use tokio::net::UnixListener;
16use tokio::{
17 runtime::{Handle, Runtime},
18 task::JoinHandle,
19};
20pub use tokio_util::sync::CancellationToken;
21
22use self::generic::GenericServer;
23use crate::{Error, application::RequestDispatcher};
24
25#[cfg(not(any(feature = "tcp", feature = "unix")))]
26compile_error!("At least one of `tcp` or `unix` features must be enabled");
27
28/// ABCI Server handle.
29///
30/// Use [`Server::handle_connection()`] to accept connection and process all
31/// traffic in this connection. Each incoming connection will be processed using
32/// `app`.
33pub trait Server {
34 /// Process one incoming connection.
35 ///
36 /// Returns when the connection is terminated, [CancellationToken::cancel()]
37 /// is called or RequestDispatcher returns `None`.
38 ///
39 /// It is safe to call this method multiple times after it finishes;
40 /// however, errors must be examined and handled, as the connection
41 /// should not terminate. One exception is [Error::Cancelled], which
42 /// means server shutdown was requested.
43 fn next_client(&self) -> Result<(), Error>;
44
45 #[deprecated = "use `next_client()`"]
46 fn handle_connection(&self) -> Result<(), Error> {
47 self.next_client()
48 }
49}
50
51/// ABCI server builder that creates and starts ABCI server
52///
53/// Create new server with [`ServerBuilder::new()`], configure it as needed, and
54/// finalize using [`ServerBuilder::build()`]. This will create and start new
55/// ABCI server.
56///
57/// Use [`Server::next_client()`] to accept connection from ABCI client
58/// (Tenderdash) and start processing incoming requests. Each incoming
59/// connection will be processed using `app`.
60///
61/// # Examples
62///
63/// ```no_run
64/// struct MyAbciApplication {};
65/// impl tenderdash_abci::Application for MyAbciApplication {};
66/// let app = MyAbciApplication {};
67/// let bind_address = "unix:///tmp/abci.sock";
68/// let server = tenderdash_abci::ServerBuilder::new(app, &bind_address).build().expect("server failed");
69/// loop {
70/// if let Err(tenderdash_abci::Error::Cancelled()) = server.next_client() {
71/// break;
72/// }
73/// }
74/// ```
75pub struct ServerBuilder<D>
76where
77 D: RequestDispatcher,
78{
79 app: D,
80 bind_address: String,
81 cancel: Option<CancellationToken>,
82 server_runtime: Option<ServerRuntime>,
83}
84
85impl<'a, App: RequestDispatcher + 'a> ServerBuilder<App> {
86 /// Create new server builder.
87 ///
88 /// # Arguments
89 ///
90 /// * `address` - address in URI format, pointing either to TCP address and
91 /// port (eg. `tcp://0.0.0.0:1234`, `tcp://[::1]:1234`) or Unix socket
92 /// (`unix:///var/run/abci.sock`)
93 /// * `app` - request dispatcher, most likely implementation of Application
94 /// trait
95 pub fn new(app: App, address: &str) -> Self {
96 Self {
97 app,
98 bind_address: address.to_string(),
99 cancel: None,
100 server_runtime: None,
101 }
102 }
103
104 /// Build and start the ABCI server.
105 ///
106 /// # Return
107 ///
108 /// Returns [`Server`] which provides [`Server::next_client()`]
109 /// method. Call it in a loop to accept and process incoming
110 /// connections.
111 pub fn build(self) -> Result<Box<dyn Server + 'a>, crate::Error> {
112 let bind_address =
113 url::Url::parse(self.bind_address.as_ref()).expect("invalid bind address");
114 if bind_address.scheme() != "tcp" && bind_address.scheme() != "unix" {
115 panic!("app_address must be either tcp:// or unix://");
116 }
117 let server_runtime: ServerRuntime = self.server_runtime.unwrap_or_default();
118
119 let _guard = server_runtime.handle.enter();
120
121 // No cancel is defined, so we add some "mock"
122 let cancel = self.cancel.unwrap_or_default();
123
124 let server = match bind_address.scheme() {
125 #[cfg(feature = "tcp")]
126 "tcp" => Box::new(GenericServer::<App, TcpListener>::bind(
127 self.app,
128 parse_tcp_uri(bind_address),
129 cancel,
130 server_runtime,
131 )?) as Box<dyn Server + 'a>,
132 #[cfg(feature = "unix")]
133 "unix" => Box::new(GenericServer::<App, UnixListener>::bind(
134 self.app,
135 bind_address.path(),
136 cancel,
137 server_runtime,
138 )?) as Box<dyn Server + 'a>,
139 _ => panic!(
140 "listen address uses unsupported scheme `{}`",
141 bind_address.scheme()
142 ),
143 };
144
145 Ok(server)
146 }
147 /// Set a [CancellationToken] token to support graceful shutdown.
148 ///
149 /// Call [`CancellationToken::cancel()`] to stop the server gracefully.
150 ///
151 /// [`CancellationToken::cancel()`]: tokio_util::sync::CancellationToken::cancel()
152 pub fn with_cancel_token(self, cancel: CancellationToken) -> Self {
153 Self {
154 cancel: Some(cancel),
155 ..self
156 }
157 }
158 /// Set tokio [Runtime](tokio::runtime::Runtime) to use.
159 ///
160 /// By default, current tokio runtime is used. If no runtime is active
161 /// ([Handle::try_current()] returns error), new multi-threaded runtime
162 /// is started. If this is not what you want, use
163 /// [ServerBuilder::with_runtime()] to provide handler to correct Tokio
164 /// runtime.
165 ///
166 /// # Example
167 ///
168 /// ```
169 /// use tokio::runtime::{Handle, Runtime};
170 /// use tenderdash_abci::{RequestDispatcher, ServerBuilder, CancellationToken, Application};
171 ///
172 /// // Your custom RequestDispatcher implementation
173 /// struct MyApp;
174 ///
175 /// impl Application for MyApp {}
176 ///
177 /// // Create a Tokio runtime
178 /// let runtime = Runtime::new().unwrap();
179 /// let runtime_handle = runtime.handle().clone();
180 ///
181 /// // Create an instance of your RequestDispatcher implementation
182 /// let app = MyApp;
183 ///
184 /// // Create cancellation token
185 /// let cancel = CancellationToken::new();
186 /// # cancel.cancel();
187 /// // Create a ServerBuilder instance and set the runtime using with_runtime()
188 ///
189 /// let server = ServerBuilder::new(app, "tcp://0.0.0.0:17534")
190 /// .with_runtime(runtime_handle)
191 /// .with_cancel_token(cancel)
192 /// .build();
193 /// ```
194 ///
195 /// In this example, we first create a Tokio runtime and get its handle.
196 /// Then we create an instance of our `MyApp` struct that implements the
197 /// `RequestDispatcher` trait. We create a `ServerBuilder` instance by
198 /// calling `new()` with our `MyApp` instance and then use the
199 /// `with_runtime()` method to set the runtime handle. Finally, you can
200 /// continue building your server and eventually run it.
201 ///
202 /// [Handle::try_current()]: tokio::runtime::Handle::try_current()
203 pub fn with_runtime(self, runtime_handle: Handle) -> Self {
204 Self {
205 server_runtime: Some(ServerRuntime {
206 _runtime: None,
207 handle: runtime_handle,
208 }),
209 ..self
210 }
211 }
212}
213
214/// Server runtime that must be alive for the whole lifespan of the server
215pub struct ServerRuntime {
216 /// Runtime stored here to ensure it is never dropped
217 _runtime: Option<Runtime>,
218 pub handle: Handle,
219}
220
221impl ServerRuntime {
222 pub fn block_on<F: std::future::Future>(&self, future: F) -> F::Output {
223 self.handle.block_on(future)
224 }
225
226 pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
227 where
228 F: Future + Send + 'static,
229 F::Output: Send + 'static,
230 {
231 self.handle.spawn(future)
232 }
233}
234
235impl Default for ServerRuntime {
236 /// Return default server runtime.
237 ///
238 /// If tokio runtime is already initialized and entered, returns handle to
239 /// it. Otherwise, creates new runtime and returns handle AND the
240 /// runtime itself.
241 fn default() -> Self {
242 match Handle::try_current() {
243 Ok(runtime_handle) => Self {
244 handle: runtime_handle,
245 _runtime: None,
246 },
247 Err(_) => {
248 let rt = tokio::runtime::Builder::new_multi_thread()
249 .worker_threads(4)
250 .enable_all()
251 .build()
252 .expect("cannot create runtime");
253 tracing::trace!("created new runtime");
254 Self {
255 handle: rt.handle().clone(),
256 _runtime: Some(rt),
257 }
258 },
259 }
260 }
261}
262
263#[deprecated = "use `ServerBuilder::new(app, &bind_address).build()` instead"]
264pub fn start_server<'a, App: RequestDispatcher + 'a, Addr>(
265 bind_address: Addr,
266 app: App,
267) -> Result<Box<dyn Server + 'a>, crate::Error>
268where
269 Addr: AsRef<str>,
270{
271 ServerBuilder::new(app, bind_address.as_ref()).build()
272}
273#[cfg(feature = "tcp")]
274fn parse_tcp_uri(uri: url::Url) -> SocketAddr {
275 let host = uri.host_str().unwrap();
276 // remove '[' and ']' from ipv6 address, as per https://github.com/servo/rust-url/issues/770
277 let host = host.replace(['[', ']'], "");
278 let port = uri.port().expect("missing tcp port");
279
280 let ip = IpAddr::from_str(host.as_str())
281 .unwrap_or_else(|e| panic!("invalid listen address {}: {}", host, e));
282 match ip {
283 IpAddr::V4(a) => SocketAddr::V4(SocketAddrV4::new(a, port)),
284 IpAddr::V6(a) => SocketAddr::V6(SocketAddrV6::new(a, port, 0, 0)),
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use crate::server::parse_tcp_uri;
291
292 #[test]
293 fn test_parse_tcp_uri() {
294 struct TestCase<'a> {
295 uri: &'a str,
296 expect: &'a str,
297 }
298
299 let test_cases = [
300 TestCase {
301 uri: "tcp://0.0.0.0:1234",
302 expect: "0.0.0.0:1234",
303 },
304 TestCase {
305 uri: "tcp://[::]:1234",
306 expect: "[::]:1234",
307 },
308 TestCase {
309 uri: "tcp://[::1]:1234",
310 expect: "[::1]:1234",
311 },
312 TestCase {
313 uri: "tcp://[::ffff:ac11:1]:5678",
314 expect: "[::ffff:172.17.0.1]:5678",
315 },
316 ];
317
318 for test_case in test_cases {
319 let uri = url::Url::parse(test_case.uri).unwrap();
320
321 let addr = parse_tcp_uri(uri);
322 assert_eq!(test_case.expect, addr.to_string());
323 }
324 }
325}