From 6df5c2f96568aa859be3de3770736a0fc0f67d37 Mon Sep 17 00:00:00 2001 From: Jacob Rothstein Date: Thu, 6 Apr 2023 15:31:37 -0700 Subject: [PATCH] all servers minor feature: add support for Config::with_listener --- async-std/src/lib.rs | 2 +- server-common/src/binding.rs | 13 ++++++++ server-common/src/config.rs | 56 ++++++++++++++++++++++++++++++--- server-common/src/server.rs | 13 +++++--- smol/src/lib.rs | 2 +- tokio/examples/tokio_binding.rs | 26 +++++++++++++++ tokio/src/lib.rs | 2 +- 7 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 tokio/examples/tokio_binding.rs diff --git a/async-std/src/lib.rs b/async-std/src/lib.rs index e8c2b803fc..2353d71929 100644 --- a/async-std/src/lib.rs +++ b/async-std/src/lib.rs @@ -33,7 +33,7 @@ async fn main() { */ use trillium::Handler; -pub use trillium_server_common::Stopper; +pub use trillium_server_common::{Binding, Stopper}; mod client; pub use client::{ClientConfig, TcpConnector}; diff --git a/server-common/src/binding.rs b/server-common/src/binding.rs index 5eebc0f353..24255bc29b 100644 --- a/server-common/src/binding.rs +++ b/server-common/src/binding.rs @@ -19,6 +19,19 @@ pub enum Binding { Unix(U), } +impl Binding { + /// constructs a new tcp variant + pub fn tcp(tcp: impl Into) -> Self { + Self::Tcp(tcp.into()) + } + + #[cfg(unix)] + /// constructs a new unix variant + pub fn unix(unix: impl Into) -> Self { + Self::Unix(unix.into()) + } +} + impl, U> TryFrom for Binding { type Error = >::Error; diff --git a/server-common/src/config.rs b/server-common/src/config.rs index 0f631b1822..c0f5c43b75 100644 --- a/server-common/src/config.rs +++ b/server-common/src/config.rs @@ -54,7 +54,7 @@ In order to use this to _implement_ a trillium server, see */ #[derive(Debug)] -pub struct Config { +pub struct Config { pub(crate) acceptor: AcceptorType, pub(crate) port: Option, pub(crate) host: Option, @@ -63,6 +63,7 @@ pub struct Config { pub(crate) counter: CloneCounter, pub(crate) register_signals: bool, pub(crate) max_connections: Option, + pub(crate) binding: Option, server: PhantomData, } @@ -93,6 +94,9 @@ where /// Configures the server to listen on this port. The default is /// the PORT environment variable or 8080 pub fn with_port(mut self, port: u16) -> Self { + if self.binding.is_some() { + eprintln!("constructing a config with both a port and a pre-bound listener will ignore the port. this may be a panic in the future"); + } self.port = Some(port); self } @@ -101,6 +105,9 @@ where /// address. The default is the HOST environment variable or /// "localhost" pub fn with_host(mut self, host: &str) -> Self { + if self.binding.is_some() { + eprintln!("constructing a config with both a host and a pre-bound listener will ignore the host. this may be a panic in the future"); + } self.host = Some(host.into()); self } @@ -118,6 +125,10 @@ where /// for more /// information on this setting. pub fn with_nodelay(mut self) -> Self { + if self.binding.is_some() { + eprintln!("constructing a config with both nodelay and a pre-bound listener will ignore the nodelay setting. this may be a panic in the future"); + } + self.nodelay = true; self } @@ -145,6 +156,7 @@ where counter: self.counter, register_signals: self.register_signals, max_connections: self.max_connections, + binding: self.binding, } } @@ -163,17 +175,51 @@ where self.max_connections = max_connections; self } + + /// Takes a pre-bound listener. The specific listener argument for + /// this will depend on the runtime adapter's server type. If it + /// accepts a [`Binding`], use [`Binding::tcp`] or + /// [`Binding::unix`] to construct your intended variant. + /// + /// ## Note well + /// + /// Many of the other options on this config will be ignored if + /// you provide a listener. In particular, `host`, `port`, and + /// `nodelay` will be ignored. All of the other options will be + /// used. + /// + /// Additionally, cloning this config will not clone the listener. + pub fn with_listener(mut self, listener: ServerType::Listener) -> Self { + if self.host.is_some() { + eprintln!("constructing a config with both a host and a pre-bound listener will ignore the host. this may be a panic in the future"); + } + + if self.port.is_some() { + eprintln!("constructing a config with both a port and a pre-bound listener will ignore the port. this may be a panic in the future"); + } + + if self.nodelay { + eprintln!("constructing a config with nodelay and a pre-bound listener will ignore nodelay. this may be a panic in the future"); + } + + self.binding = Some(listener); + self + } } -impl Config { +impl Config { /// build a new config with default acceptor pub fn new() -> Self { Self::default() } } -impl Clone for Config { +impl Clone for Config { fn clone(&self) -> Self { + if self.binding.is_some() { + eprintln!("cloning a Config with a pre-bound listener will not clone the listener. this may be a panic in the future."); + } + Self { acceptor: self.acceptor.clone(), port: self.port, @@ -184,11 +230,12 @@ impl Clone for Config Default for Config { +impl Default for Config { fn default() -> Self { #[cfg(unix)] let max_connections = { @@ -213,6 +260,7 @@ impl Default for Config { counter: CloneCounter::new(), register_signals: cfg!(unix), max_connections, + binding: None, } } } diff --git a/server-common/src/server.rs b/server-common/src/server.rs index fc835f9f16..836f9a2dc6 100644 --- a/server-common/src/server.rs +++ b/server-common/src/server.rs @@ -68,10 +68,15 @@ pub trait Server: Send + 'static { /// [`Server::listener_from_tcp`] and /// [`Server::listener_from_unix`]. #[cfg(unix)] - fn build_listener(config: &Config) -> Self::Listener + fn build_listener(config: &mut Config) -> Self::Listener where A: Acceptor, { + if let Some(listener) = config.binding.take() { + log::debug!("taking prebound listener"); + return listener; + } + use std::os::unix::prelude::FromRawFd; let host = config.host(); if host.starts_with(|c| c == '/' || c == '.' || c == '~') { @@ -97,7 +102,7 @@ pub trait Server: Send + 'static { /// implementations could potentially implement this directly. To /// use this default logic, implement [`Server::listener_from_tcp`] #[cfg(not(unix))] - fn build_listener(config: &Config) -> Self::Listener + fn build_listener(config: &mut Config) -> Self::Listener where A: Acceptor, { @@ -147,7 +152,7 @@ pub trait Server: Send + 'static { /// implementation of this method contains the core logic of this /// Trait. fn run_async( - config: Config, + mut config: Config, mut handler: H, ) -> Pin + Send + 'static>> where @@ -163,7 +168,7 @@ pub trait Server: Send + 'static { log::error!("signals handling not supported on windows yet"); } - let mut listener = Self::build_listener(&config); + let mut listener = Self::build_listener(&mut config); let mut info = Self::info(&listener); info.server_description_mut().push_str(Self::DESCRIPTION); handler.init(&mut info).await; diff --git a/smol/src/lib.rs b/smol/src/lib.rs index 0198319e2f..5ed70562da 100644 --- a/smol/src/lib.rs +++ b/smol/src/lib.rs @@ -60,7 +60,7 @@ trillium_testing::with_server("ok", |url| async move { */ use trillium::Handler; -pub use trillium_server_common::Stopper; +pub use trillium_server_common::{Binding, Stopper}; mod client; pub use client::{ClientConfig, TcpConnector}; diff --git a/tokio/examples/tokio_binding.rs b/tokio/examples/tokio_binding.rs new file mode 100644 index 0000000000..bd5f84ce52 --- /dev/null +++ b/tokio/examples/tokio_binding.rs @@ -0,0 +1,26 @@ +use trillium_tokio::Binding; + +pub fn app() -> impl trillium::Handler { + |conn: trillium::Conn| async move { + let response = tokio::task::spawn(async { + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + "successfully spawned a task" + }) + .await + .unwrap(); + conn.ok(response) + } +} + +#[tokio::main] +pub async fn main() { + env_logger::init(); + let listener = tokio::net::TcpListener::bind("localhost:8080") + .await + .unwrap(); + + trillium_tokio::config() + .with_listener(Binding::tcp(listener)) + .run_async(app()) + .await; +} diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index 3eba66960c..edc492031c 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -34,7 +34,7 @@ async fn main() { use std::future::Future; use trillium::Handler; -pub use trillium_server_common::Stopper; +pub use trillium_server_common::{Binding, Stopper}; mod client; pub use client::{ClientConfig, TcpConnector};