diff options
author | metamuffin <metamuffin@disroot.org> | 2024-06-22 12:54:39 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-06-22 12:54:39 +0200 |
commit | 4da8fe84c07e3f9e83f9b769f1670f4d52466001 (patch) | |
tree | bf4384034771001b136207c4df386daf99dac7fa | |
parent | 1451273fa59d14070e525562ec466a21128fa671 (diff) | |
download | gnix-4da8fe84c07e3f9e83f9b769f1670f4d52466001.tar gnix-4da8fe84c07e3f9e83f9b769f1670f4d52466001.tar.bz2 gnix-4da8fe84c07e3f9e83f9b769f1670f4d52466001.tar.zst |
add switch module
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | readme.md | 18 | ||||
-rw-r--r-- | src/modules/headers.rs | 6 | ||||
-rw-r--r-- | src/modules/mod.rs | 3 | ||||
-rw-r--r-- | src/modules/switch.rs | 71 |
6 files changed, 97 insertions, 5 deletions
@@ -577,7 +577,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gnix" -version = "2.0.0" +version = "2.1.0" dependencies = [ "aes-gcm-siv", "anyhow", @@ -1,6 +1,6 @@ [package] name = "gnix" -version = "2.0.0" +version = "2.1.0" edition = "2021" [dependencies] @@ -156,6 +156,24 @@ themselves; in that case the request is passed on. such a form is provided with the distribution of this software, usually in `/usr/share/gnix/login.html` (module) +- **module `switch`** + - Decides between two possible routes based on a condition. + - `condition`: + - `!is_websocket_upgrade`: Checks if a websocket was requested. + - `!is_get`: Checks if this is a GET request + - `!is_post`: Checks if this is a POST request + - `!path_starts_with <prefix>`: Checks if the URI path starts with some + prefix + - `!path_is <path>`: Checks if the URI path is exactly what you specified + - `!has_header <name>`: Checks if the request includes a certain header. + - `case_true` Handler with matched requests (module) + - `case_false` Handler for all other requests (module) + +- **module `headers`** + - Appends multiple headers to the response. + - `headers`: A map of all header name-value pairs. (object string:string) + - `inner`: The handler to add the headers to. (module) + #### Credentials config format Login credentials for `cookie_auth` and `http_basic_auth` are supplied as either diff --git a/src/modules/headers.rs b/src/modules/headers.rs index b1e452e..ad4595a 100644 --- a/src/modules/headers.rs +++ b/src/modules/headers.rs @@ -1,4 +1,4 @@ -use super::{Node, NodeKind, NodeResponse}; +use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; use crate::{config::DynNode, error::ServiceError}; use anyhow::Result; use futures::Future; @@ -29,8 +29,8 @@ impl NodeKind for HeadersKind { impl Node for Headers { fn handle<'a>( &'a self, - context: &'a mut super::NodeContext, - request: super::NodeRequest, + context: &'a mut NodeContext, + request: NodeRequest, ) -> Pin<Box<dyn Future<Output = Result<NodeResponse, ServiceError>> + Send + Sync + 'a>> { Box::pin(async move { let mut resp = self.inner.handle(context, request).await?; diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 2e08142..0fe9ca0 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -14,6 +14,7 @@ use hyper::{body::Incoming, Request, Response}; use proxy::ProxyKind; use serde_yaml::Value; use std::{net::SocketAddr, pin::Pin, sync::Arc}; +use switch::SwitchKind; pub mod accesslog; pub mod auth; @@ -23,6 +24,7 @@ pub mod files; pub mod headers; pub mod hosts; pub mod proxy; +pub mod switch; pub type NodeRequest = Request<Incoming>; pub type NodeResponse = Response<BoxBody<Bytes, ServiceError>>; @@ -37,6 +39,7 @@ pub static MODULES: &[&dyn NodeKind] = &[ &AccessLogKind, &ErrorKind, &HeadersKind, + &SwitchKind, ]; pub struct NodeContext { diff --git a/src/modules/switch.rs b/src/modules/switch.rs new file mode 100644 index 0000000..bbb9e98 --- /dev/null +++ b/src/modules/switch.rs @@ -0,0 +1,71 @@ +use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; +use crate::{config::DynNode, error::ServiceError}; +use anyhow::Result; +use futures::Future; +use headers::{HeaderMapExt, Upgrade}; +use hyper::Method; +use serde::Deserialize; +use std::{pin::Pin, sync::Arc}; + +pub struct SwitchKind; + +#[derive(Deserialize)] +pub struct Switch { + condition: Condition, + case_true: DynNode, + case_false: DynNode, +} + +impl NodeKind for SwitchKind { + fn name(&self) -> &'static str { + "switch" + } + fn instanciate(&self, config: serde_yaml::Value) -> Result<Arc<dyn Node>> { + Ok(Arc::new(serde_yaml::from_value::<Switch>(config)?)) + } +} + +impl Node for Switch { + fn handle<'a>( + &'a self, + context: &'a mut NodeContext, + request: NodeRequest, + ) -> Pin<Box<dyn Future<Output = Result<NodeResponse, ServiceError>> + Send + Sync + 'a>> { + Box::pin(async move { + let cond = self.condition.test(&request); + if cond { + &self.case_true + } else { + &self.case_false + } + .handle(context, request) + .await + }) + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +enum Condition { + IsWebsocketUpgrade, + IsPost, + IsGet, + HasHeader(String), + PathStartsWith(String), + PathIs(String), +} + +impl Condition { + pub fn test(&self, req: &NodeRequest) -> bool { + match self { + Condition::IsWebsocketUpgrade => { + req.headers().typed_get::<Upgrade>() == Some(Upgrade::websocket()) + } + Condition::HasHeader(name) => req.headers().contains_key(name), + Condition::PathStartsWith(path_prefix) => req.uri().path().starts_with(path_prefix), + Condition::PathIs(path) => req.uri().path() == path, + Condition::IsPost => req.method() == Method::POST, + Condition::IsGet => req.method() == Method::GET, + } + } +} |