From 6e7229e4ac5d8cb2f8ca1f50554f85c503041a40 Mon Sep 17 00:00:00 2001 From: oxalica Date: Sat, 31 Aug 2024 01:25:25 -0400 Subject: [PATCH] Make more server constants configurable --- Cargo.lock | 12 ++++++++++++ blahd/Cargo.toml | 1 + blahd/config.example.toml | 16 ++++++++++++++++ blahd/src/config.rs | 12 ++++++++++++ blahd/src/main.rs | 32 ++++++++++++++++---------------- 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb641a2..c46f205 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,7 @@ dependencies = [ "sd-notify", "serde", "serde-aux", + "serde-inline-default", "serde_json", "tokio", "tokio-stream", @@ -1455,6 +1456,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "serde-inline-default" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980133dc534d02ab08df3b384295223a45090c40a4c46240e3eaa982b495910" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_derive" version = "1.0.209" diff --git a/blahd/Cargo.toml b/blahd/Cargo.toml index f4ab712..77d4399 100644 --- a/blahd/Cargo.toml +++ b/blahd/Cargo.toml @@ -26,3 +26,4 @@ uuid = { version = "1", features = ["v4"] } blah = { path = "..", features = ["rusqlite"] } basic-toml = "0.1.9" +serde-inline-default = "0.2.0" diff --git a/blahd/config.example.toml b/blahd/config.example.toml index a38667f..c5b5ca3 100644 --- a/blahd/config.example.toml +++ b/blahd/config.example.toml @@ -1,13 +1,29 @@ [database] +# (Required) # The path to the main SQLite database. # It will be created and initialized if not exist. path = "/path/to/db.sqlite" [server] +# (Required) # The socket address to listen on. listen = "localhost:8080" +# (Required) # The global absolute URL prefix where this service is hosted. # It is for link generation and must not have trailing slash. base_url = "http://localhost:8080" + +# Maximum number of items in a single response, eg. get chat items. +# More items will be paged. +max_page_len = 1024 + +# Maximum request body length in bytes. +max_request_len = 4096 + +# Maximum length of a single event queue. +event_queue_len = 1024 + +# The maximum timestamp tolerence in seconds for request validation. +timestamp_tolerence_secs = 90 diff --git a/blahd/src/config.rs b/blahd/src/config.rs index bd5f028..b8b672b 100644 --- a/blahd/src/config.rs +++ b/blahd/src/config.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use anyhow::{ensure, Result}; use serde::Deserialize; +use serde_inline_default::serde_inline_default; #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] @@ -16,11 +17,22 @@ pub struct DatabaseConfig { pub path: PathBuf, } +#[serde_inline_default] #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] pub struct ServerConfig { pub listen: String, pub base_url: String, + + #[serde_inline_default(1024)] + pub max_page_len: usize, + #[serde_inline_default(4096)] // 4KiB + pub max_request_len: usize, + #[serde_inline_default(1024)] + pub event_queue_len: usize, + + #[serde_inline_default(90)] + pub timestamp_tolerence_secs: u64, } impl Config { diff --git a/blahd/src/main.rs b/blahd/src/main.rs index 20963a1..de94cb5 100644 --- a/blahd/src/main.rs +++ b/blahd/src/main.rs @@ -26,11 +26,6 @@ use tokio_stream::StreamExt; use utils::ExpiringSet; use uuid::Uuid; -const PAGE_LEN: usize = 64; -const EVENT_QUEUE_LEN: usize = 1024; -const MAX_BODY_LEN: usize = 4 << 10; // 4KiB -const TIMESTAMP_TOLERENCE: u64 = 90; - #[macro_use] mod middleware; mod config; @@ -105,7 +100,9 @@ impl AppState { Ok(Self { conn: Mutex::new(conn), room_listeners: Mutex::new(HashMap::new()), - used_nonces: Mutex::new(ExpiringSet::new(Duration::from_secs(TIMESTAMP_TOLERENCE))), + used_nonces: Mutex::new(ExpiringSet::new(Duration::from_secs( + config.server.timestamp_tolerence_secs, + ))), config, }) @@ -124,7 +121,7 @@ impl AppState { .expect("after UNIX epoch") .as_secs() .abs_diff(data.signee.timestamp); - if timestamp_diff > TIMESTAMP_TOLERENCE { + if timestamp_diff > self.config.server.timestamp_tolerence_secs { return Err(error_response!( StatusCode::BAD_REQUEST, "invalid_timestamp", @@ -161,7 +158,9 @@ async fn main_async(st: AppState) -> Result<()> { .with_state(st.clone()) // NB. This comes at last (outmost layer), so inner errors will still be wraped with // correct CORS headers. - .layer(tower_http::limit::RequestBodyLimitLayer::new(MAX_BODY_LEN)) + .layer(tower_http::limit::RequestBodyLimitLayer::new( + st.config.server.max_request_len, + )) .layer(tower_http::cors::CorsLayer::permissive()); let listener = tokio::net::TcpListener::bind(&st.config.server.listen) @@ -276,8 +275,7 @@ async fn room_get_item( WithRejection(params, _): WithRejection, ApiError>, OptionalAuth(user): OptionalAuth, ) -> Result { - let (room_meta, items) = - query_room_items(&st.conn.lock().unwrap(), ruuid, user.as_ref(), ¶ms)?; + let (room_meta, items) = query_room_items(&st, ruuid, user.as_ref(), ¶ms)?; // TODO: This format is to-be-decided. Or do we even need this interface other than // `feed.json`? @@ -289,7 +287,7 @@ async fn room_get_feed( WithRejection(Path(ruuid), _): WithRejection, ApiError>, params: Query, ) -> Result { - let (room_meta, items) = query_room_items(&st.conn.lock().unwrap(), ruuid, None, ¶ms)?; + let (room_meta, items) = query_room_items(&st, ruuid, None, ¶ms)?; let items = items .into_iter() @@ -314,7 +312,7 @@ async fn room_get_feed( let base_url = &st.config.server.base_url; let feed_url = format!("{base_url}/room/{ruuid}/feed.json"); - let next_url = (items.len() == PAGE_LEN).then(|| { + let next_url = (items.len() == st.config.server.max_page_len).then(|| { let last_id = &items.last().expect("page size is not 0").id; format!("{feed_url}?before_id={last_id}") }); @@ -401,12 +399,14 @@ fn get_room_if_readable( } fn query_room_items( - conn: &rusqlite::Connection, + st: &AppState, ruuid: Uuid, user: Option<&UserKey>, params: &GetRoomItemParams, ) -> Result<(RoomMetadata, Vec<(u64, ChatItem)>), ApiError> { - let (rid, title, attrs) = get_room_if_readable(conn, ruuid, user, |row| { + let conn = st.conn.lock().unwrap(); + + let (rid, title, attrs) = get_room_if_readable(&conn, ruuid, user, |row| { Ok(( row.get::<_, u64>("rid")?, row.get::<_, String>("title")?, @@ -432,7 +432,7 @@ fn query_room_items( named_params! { ":rid": rid, ":before_cid": params.before_id, - ":limit": PAGE_LEN, + ":limit": st.config.server.max_page_len, }, |row| { let cid = row.get::<_, u64>("cid")?; @@ -543,7 +543,7 @@ async fn room_event( let rx = match st.room_listeners.lock().unwrap().entry(rid) { Entry::Occupied(ent) => ent.get().subscribe(), Entry::Vacant(ent) => { - let (tx, rx) = broadcast::channel(EVENT_QUEUE_LEN); + let (tx, rx) = broadcast::channel(st.config.server.event_queue_len); ent.insert(tx); rx }