mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-17 00:00:16 +02:00
* begin threading the state through * Pass state to extractors * make state extractor work * make sure nesting with different states work * impl Service for MethodRouter<()> * Fix some of axum-macro's tests * Implement more traits for `State` * Update examples to use `State` * consistent naming of request body param * swap type params * Default the state param to () * fix docs references * Docs and handler state refactoring * docs clean ups * more consistent naming * when does MethodRouter implement Service? * add missing docs * use `Router`'s default state type param * changelog * don't use default type param for FromRequest and RequestParts probably safer for library authors so you don't accidentally forget * fix examples * minor docs tweaks * clarify how to convert handlers into services * group methods in one impl block * make sure merged `MethodRouter`s can access state * fix docs link * test merge with same state type * Document how to access state from middleware * Port cookie extractors to use state to extract keys (#1250) * Updates ECOSYSTEM with a new sample project (#1252) * Avoid unhelpful compiler suggestion (#1251) * fix docs typo * document how library authors should access state * Add `RequestParts::with_state` * fix example * apply suggestions from review * add relevant changes to axum-extra and axum-core changelogs * Add `route_service_with_tsr` * fix trybuild expectations * make sure `SpaRouter` works with routers that have state * Change order of type params on FromRequest and RequestParts * reverse order of `RequestParts::with_state` args to match type params * Add `FromRef` trait (#1268) * Add `FromRef` trait * Remove unnecessary type params * format * fix docs link * format examples * Avoid unnecessary `MethodRouter` * apply suggestions from review Co-authored-by: Dani Pardo <[email protected]> Co-authored-by: Jonas Platte <[email protected]>
147 lines
4.1 KiB
Rust
147 lines
4.1 KiB
Rust
//! Simple in-memory key/value store showing features of axum.
|
|
//!
|
|
//! Run with:
|
|
//!
|
|
//! ```not_rust
|
|
//! cd examples && cargo run -p example-key-value-store
|
|
//! ```
|
|
|
|
use axum::{
|
|
body::Bytes,
|
|
error_handling::HandleErrorLayer,
|
|
extract::{ContentLengthLimit, Path, State},
|
|
handler::Handler,
|
|
http::StatusCode,
|
|
response::IntoResponse,
|
|
routing::{delete, get},
|
|
Router,
|
|
};
|
|
use std::{
|
|
borrow::Cow,
|
|
collections::HashMap,
|
|
net::SocketAddr,
|
|
sync::{Arc, RwLock},
|
|
time::Duration,
|
|
};
|
|
use tower::{BoxError, ServiceBuilder};
|
|
use tower_http::{
|
|
auth::RequireAuthorizationLayer, compression::CompressionLayer, trace::TraceLayer,
|
|
};
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
tracing_subscriber::registry()
|
|
.with(tracing_subscriber::EnvFilter::new(
|
|
std::env::var("RUST_LOG")
|
|
.unwrap_or_else(|_| "example_key_value_store=debug,tower_http=debug".into()),
|
|
))
|
|
.with(tracing_subscriber::fmt::layer())
|
|
.init();
|
|
|
|
let shared_state = SharedState::default();
|
|
|
|
// Build our application by composing routes
|
|
let app = Router::with_state(Arc::clone(&shared_state))
|
|
.route(
|
|
"/:key",
|
|
// Add compression to `kv_get`
|
|
get(kv_get.layer(CompressionLayer::new()))
|
|
// But don't compress `kv_set`
|
|
.post(kv_set),
|
|
)
|
|
.route("/keys", get(list_keys))
|
|
// Nest our admin routes under `/admin`
|
|
.nest("/admin", admin_routes(shared_state))
|
|
// Add middleware to all routes
|
|
.layer(
|
|
ServiceBuilder::new()
|
|
// Handle errors from middleware
|
|
.layer(HandleErrorLayer::new(handle_error))
|
|
.load_shed()
|
|
.concurrency_limit(1024)
|
|
.timeout(Duration::from_secs(10))
|
|
.layer(TraceLayer::new_for_http())
|
|
.into_inner(),
|
|
);
|
|
|
|
// Run our app with hyper
|
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
|
tracing::debug!("listening on {}", addr);
|
|
axum::Server::bind(&addr)
|
|
.serve(app.into_make_service())
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
type SharedState = Arc<RwLock<AppState>>;
|
|
|
|
#[derive(Default)]
|
|
struct AppState {
|
|
db: HashMap<String, Bytes>,
|
|
}
|
|
|
|
async fn kv_get(
|
|
Path(key): Path<String>,
|
|
State(state): State<SharedState>,
|
|
) -> Result<Bytes, StatusCode> {
|
|
let db = &state.read().unwrap().db;
|
|
|
|
if let Some(value) = db.get(&key) {
|
|
Ok(value.clone())
|
|
} else {
|
|
Err(StatusCode::NOT_FOUND)
|
|
}
|
|
}
|
|
|
|
async fn kv_set(
|
|
Path(key): Path<String>,
|
|
ContentLengthLimit(bytes): ContentLengthLimit<Bytes, { 1024 * 5_000 }>, // ~5mb
|
|
State(state): State<SharedState>,
|
|
) {
|
|
state.write().unwrap().db.insert(key, bytes);
|
|
}
|
|
|
|
async fn list_keys(State(state): State<SharedState>) -> String {
|
|
let db = &state.read().unwrap().db;
|
|
|
|
db.keys()
|
|
.map(|key| key.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join("\n")
|
|
}
|
|
|
|
fn admin_routes(state: SharedState) -> Router<SharedState> {
|
|
async fn delete_all_keys(State(state): State<SharedState>) {
|
|
state.write().unwrap().db.clear();
|
|
}
|
|
|
|
async fn remove_key(Path(key): Path<String>, State(state): State<SharedState>) {
|
|
state.write().unwrap().db.remove(&key);
|
|
}
|
|
|
|
Router::with_state(state)
|
|
.route("/keys", delete(delete_all_keys))
|
|
.route("/key/:key", delete(remove_key))
|
|
// Require bearer auth for all admin routes
|
|
.layer(RequireAuthorizationLayer::bearer("secret-token"))
|
|
}
|
|
|
|
async fn handle_error(error: BoxError) -> impl IntoResponse {
|
|
if error.is::<tower::timeout::error::Elapsed>() {
|
|
return (StatusCode::REQUEST_TIMEOUT, Cow::from("request timed out"));
|
|
}
|
|
|
|
if error.is::<tower::load_shed::error::Overloaded>() {
|
|
return (
|
|
StatusCode::SERVICE_UNAVAILABLE,
|
|
Cow::from("service is overloaded, try again later"),
|
|
);
|
|
}
|
|
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Cow::from(format!("Unhandled internal error: {}", error)),
|
|
)
|
|
}
|