Make more server constants configurable

This commit is contained in:
oxalica 2024-08-31 01:25:25 -04:00
parent abdc32b51f
commit 6e7229e4ac
5 changed files with 57 additions and 16 deletions

12
Cargo.lock generated
View file

@ -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"

View file

@ -26,3 +26,4 @@ uuid = { version = "1", features = ["v4"] }
blah = { path = "..", features = ["rusqlite"] }
basic-toml = "0.1.9"
serde-inline-default = "0.2.0"

View file

@ -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

View file

@ -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 {

View file

@ -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<Query<GetRoomItemParams>, ApiError>,
OptionalAuth(user): OptionalAuth,
) -> Result<impl IntoResponse, ApiError> {
let (room_meta, items) =
query_room_items(&st.conn.lock().unwrap(), ruuid, user.as_ref(), &params)?;
let (room_meta, items) = query_room_items(&st, ruuid, user.as_ref(), &params)?;
// 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<Path<Uuid>, ApiError>,
params: Query<GetRoomItemParams>,
) -> Result<impl IntoResponse, ApiError> {
let (room_meta, items) = query_room_items(&st.conn.lock().unwrap(), ruuid, None, &params)?;
let (room_meta, items) = query_room_items(&st, ruuid, None, &params)?;
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<T>(
}
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
}