diff options
author | Lia Lenckowski <lialenck@protonmail.com> | 2023-08-28 12:42:43 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-08-28 14:08:03 +0200 |
commit | 951c4e90b573f3d14a137bade0853fb3f0f21a5d (patch) | |
tree | 0e181b277c08db9ee1075d14ccf9e4249d3be2f3 | |
parent | 56fb681279b2f2221eef933617d521469c6e6d83 (diff) | |
download | gnix-951c4e90b573f3d14a137bade0853fb3f0f21a5d.tar gnix-951c4e90b573f3d14a137bade0853fb3f0f21a5d.tar.bz2 gnix-951c4e90b573f3d14a137bade0853fb3f0f21a5d.tar.zst |
supporting listening on a list of addresses
Signed-off-by: metamuffin <metamuffin@disroot.org>
-rw-r--r-- | Cargo.lock | 96 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | readme.md | 3 | ||||
-rw-r--r-- | src/config.rs | 36 | ||||
-rw-r--r-- | src/main.rs | 59 |
5 files changed, 148 insertions, 48 deletions
@@ -144,52 +144,89 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.29", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -212,6 +249,7 @@ dependencies = [ "anyhow", "bytes", "env_logger", + "futures", "futures-util", "headers", "http-body-util", @@ -221,6 +259,7 @@ dependencies = [ "markup", "mime_guess", "percent-encoding", + "proc-macro2", "rustls", "rustls-pemfile", "serde", @@ -487,7 +526,7 @@ checksum = "1a927f0e237dcbdd8c1a8ab03c4e1e8b1999804c448ebf06ff3b5512506c8150" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -592,18 +631,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -717,7 +756,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -789,6 +828,17 @@ dependencies = [ ] [[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] name = "termcolor" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -814,7 +864,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -845,7 +895,7 @@ checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1003,7 +1053,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -1025,7 +1075,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -20,6 +20,7 @@ tokio-rustls = "0.23.4" tokio = { version = "1.25.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["io"] } futures-util = "0.3.26" +futures = "0.3.26" # Config serde = { version = "1.0.152", features = ["derive"] } @@ -38,3 +39,4 @@ mime_guess = "2.0.4" bytes = "1.4.0" anyhow = "1.0.69" thiserror = "1.0.38" +proc-macro2 = "1.0.66" @@ -17,7 +17,8 @@ configuration file is written in TOML and could look like this: ```toml # Both the [http] and [https] sections are optional [http] -bind = "127.0.0.1:8080" +# the value for 'bind' can either be a string or a list of strings +bind = [ "127.0.0.1:8080", "[::1]:8080" ] [https] bind = "127.0.0.1:8443" diff --git a/src/config.rs b/src/config.rs index 9e542d7..89a2b54 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use anyhow::Context; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs::read_to_string, net::SocketAddr, path::PathBuf}; +use serde::{Deserialize, Serialize, Deserializer, de::{Visitor, Error, SeqAccess, value}}; +use std::{collections::HashMap, fmt, fs::read_to_string, net::SocketAddr, path::PathBuf}; #[derive(Debug, Serialize, Deserialize)] pub struct Config { @@ -21,12 +21,14 @@ pub struct Limits { #[derive(Debug, Serialize, Deserialize)] pub struct HttpConfig { - pub bind: SocketAddr, + #[serde(deserialize_with = "string_or_seq")] + pub bind: Vec<SocketAddr>, } #[derive(Debug, Serialize, Deserialize)] pub struct HttpsConfig { - pub bind: SocketAddr, + #[serde(deserialize_with = "string_or_seq")] + pub bind: Vec<SocketAddr>, pub tls_cert: PathBuf, pub tls_key: PathBuf, } @@ -45,6 +47,32 @@ pub struct FileserverConfig { pub index: bool, } +// fall back to expecting a single string and putting that in a 1-length vector +fn string_or_seq<'de, D>(des: D) -> Result<Vec<SocketAddr>, D::Error> +where D: Deserializer<'de> { + struct StringOrList; + impl<'de> Visitor<'de> for StringOrList { + type Value = Vec<SocketAddr>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("sequence or list") + } + + fn visit_str<E>(self, val: &str) -> Result<Vec<SocketAddr>, E> + where E: Error { + let addr = SocketAddr::deserialize(value::StrDeserializer::new(val))?; + Ok(vec![addr]) + } + + fn visit_seq<A>(self, val: A) -> Result<Vec<SocketAddr>, A::Error> + where A: SeqAccess<'de> { + Vec::<SocketAddr>::deserialize(value::SeqAccessDeserializer::new(val)) + } + } + + des.deserialize_any(StringOrList) +} + impl Config { pub fn load(path: &str) -> anyhow::Result<Config> { let raw = read_to_string(path).context("reading config file")?; diff --git a/src/main.rs b/src/main.rs index f51cd67..aa13609 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use crate::{ }; use anyhow::{anyhow, bail, Context, Result}; use error::ServiceError; +use futures::future::try_join_all; use http_body_util::{combinators::BoxBody, BodyExt}; use hyper::{ body::Incoming, @@ -88,14 +89,24 @@ async fn serve_http(state: Arc<State>) -> Result<()> { Some(n) => n, None => return Ok(()), }; - let listener = TcpListener::bind(http_config.bind).await?; + + let listen_futs: Result<Vec<()>> = try_join_all(http_config.bind + .iter() + .map(|e| async { + let l = TcpListener::bind(e.clone()).await?; + loop { + let (stream, addr) = l.accept().await.context("accepting connection")?; + debug!("connection from {addr}"); + let config = state.clone(); + tokio::spawn(async move { serve_stream(config, stream, addr).await }); + } + })) + .await; + info!("serving http"); - loop { - let (stream, addr) = listener.accept().await.context("accepting connection")?; - debug!("connection from {addr}"); - let config = state.clone(); - tokio::spawn(async move { serve_stream(config, stream, addr).await }); - } + + listen_futs?; + Ok(()) } async fn serve_https(state: Arc<State>) -> Result<()> { @@ -116,22 +127,30 @@ async fn serve_https(state: Arc<State>) -> Result<()> { ]; Arc::new(cfg) }; - let listener = TcpListener::bind(https_config.bind).await?; let tls_acceptor = Arc::new(TlsAcceptor::from(tls_config)); + let listen_futs: Result<Vec<()>> = try_join_all(https_config.bind + .iter() + .map(|e| async { + let l = TcpListener::bind(e.clone()).await?; + loop { + let (stream, addr) = l.accept().await.context("accepting connection")?; + let state = state.clone(); + let tls_acceptor = tls_acceptor.clone(); + tokio::task::spawn(async move { + debug!("connection from {addr}"); + match tls_acceptor.accept(stream).await { + Ok(stream) => serve_stream(state, stream, addr).await, + Err(e) => warn!("error accepting tls: {e}"), + }; + }); + } + })) + .await; + info!("serving https"); - loop { - let (stream, addr) = listener.accept().await.context("accepting connection")?; - let state = state.clone(); - let tls_acceptor = tls_acceptor.clone(); - tokio::task::spawn(async move { - debug!("connection from {addr}"); - match tls_acceptor.accept(stream).await { - Ok(stream) => serve_stream(state, stream, addr).await, - Err(e) => warn!("error accepting tls: {e}"), - }; - }); - } + listen_futs?; + Ok(()) } pub async fn serve_stream<T: AsyncRead + AsyncWrite + Unpin + Send + 'static>( |