Add streaming brotli compression and hot-reloadable config (livevue.toml)
Key changes: - **src/config.rs** – `LiveVueConfig` (server host/port, brotli quality + window size), loaded from `livevue.toml`. A notify-based file watcher reloads the config on any file change; a SIGHUP handler does the same on demand. Falls back to compiled-in defaults when the file is absent. - **src/brotli_layer.rs** – `BrotliBody`, a custom `http_body::Body` wrapper that streams brotli-compressed data through a single persistent `brotli::CompressorWriter`. The encoder survives across SSE event flushes, so its sliding window accumulates context from all prior events — progressively better compression as the stream grows. Both quality (0–11) and window size (lgwin 10–24, i.e. 1 KB – 16 MB) are taken from config. `brotli_compression` is an axum `from_fn_with_state` middleware that activates only when the client sends `Accept-Encoding: br`. - **src/server.rs** – `AppState` gains a `config: SharedConfig` field. `AppState::new` takes the config; `AppState::new_default` is a zero-config convenience constructor. - **livevue.toml** – documented example config with inline comments explaining each field and the brotli window-size tradeoff table. - **examples/todo** – loads config via `load_and_watch_config`, derives the bind address from `config.server`, and applies `brotli_compression` as a router layer. https://claude.ai/code/session_01UnQQkkwts64FPUsSzFfdQb
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
use livevue_rs::{
|
||||
ComponentTree, RenderContext, SharedState, SignalStore,
|
||||
ingest_signals, sse_handler,
|
||||
ComponentTree, RenderContext, SharedConfig, SharedState, SignalStore,
|
||||
brotli_compression, ingest_signals, sse_handler,
|
||||
};
|
||||
|
||||
use axum::extract::{Query as AxumQuery, State};
|
||||
use axum::middleware;
|
||||
use axum::response::{Html, IntoResponse};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
@@ -14,7 +15,7 @@ use uuid::Uuid;
|
||||
// Router
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn router(state: SharedState) -> Router {
|
||||
pub fn router(state: SharedState, config: SharedConfig) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(initial_page_load))
|
||||
.route("/about", get(initial_about_load))
|
||||
@@ -22,6 +23,10 @@ pub fn router(state: SharedState) -> Router {
|
||||
.route("/action/add_todo", post(action_add_todo))
|
||||
.route("/action/toggle_todo", post(action_toggle_todo))
|
||||
.route("/action/delete_todo", post(action_delete_todo))
|
||||
.layer(middleware::from_fn_with_state(
|
||||
config,
|
||||
brotli_compression,
|
||||
))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
|
||||
+12
-5
@@ -3,7 +3,7 @@ mod queries;
|
||||
mod todo_list;
|
||||
mod about;
|
||||
|
||||
use livevue_rs::{AppState, CacheKey, RenderContext, Store, spawn_fanout};
|
||||
use livevue_rs::{AppState, RenderContext, Store, load_and_watch_config, spawn_fanout};
|
||||
use crate::app::{app_wrapper, resolve_and_render};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -68,11 +68,17 @@ async fn main() -> anyhow::Result<()> {
|
||||
})
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Configuration (livevue.toml, hot-reloaded on change or SIGHUP)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
let config = load_and_watch_config("livevue.toml")?;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Application state
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
let state = AppState::new(store.clone(), render_fn);
|
||||
let state = AppState::new(store.clone(), render_fn, config.clone());
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Fanout task
|
||||
@@ -103,9 +109,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Server
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
let app = app::router(state);
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
|
||||
println!("Todo example listening on http://localhost:3000");
|
||||
let bind_addr = config.read().await.server.bind_addr();
|
||||
let app = app::router(state, config);
|
||||
let listener = tokio::net::TcpListener::bind(&bind_addr).await?;
|
||||
println!("Todo example listening on http://{bind_addr}");
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user