Cookbook / Middleware and state
Shared state
Register database pools, clients and counters once and read them from any handler.
Register a value with app.state(..) and read it in any handler with req.state::<T>(). Values are shared across all requests and threads, so use interior mutability (Mutex, atomics) for anything that changes.
use axeon::{Json, Server, ServerError};
use axeon::json;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
struct Counters {
visits: AtomicU64,
}
type Notes = Mutex<HashMap<u64, String>>;
fn main() {
let mut app = Server::new();
app.state(Counters { visits: AtomicU64::new(0) });
app.state::<Notes>(Mutex::new(HashMap::new()));
app.get("/visit", |req| async move {
let counters = req.state::<Counters>().ok_or_else(|| ServerError::InternalError("missing state".into()))?;
let n = counters.visits.fetch_add(1, Ordering::Relaxed) + 1;
Ok::<_, ServerError>(Json(json!({ "visits": n })))
});
app.put("/notes/:id", |req| async move {
let id: u64 = req.param("id")?;
let text = req.text()?.to_string();
let notes = req.state::<Notes>().unwrap();
notes.lock().unwrap().insert(id, text);
Ok::<_, ServerError>(()) // 204 No Content
});
app.get("/notes/:id", |req| async move {
let id: u64 = req.param("id")?;
let notes = req.state::<Notes>().unwrap();
let note = notes.lock().unwrap().get(&id).cloned();
Ok::<_, ServerError>(note) // None becomes 404
});
app.listen("127.0.0.1:3000").expect("server failed to start");
}
Notes#
- State is looked up by type. Wrap two values of the same type in newtypes (
struct ReadPool(PgPool)) to tell them apart. - Never hold a
std::sync::Mutexguard across an.await. Copy what you need out, drop the guard, then await. For locks held across awaits usetokio::sync::Mutex. - Per-request data, such as the signed-in user, belongs in extensions, not state.