mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-18 00:00:15 +02:00
* Only allow last extractor to mutate the request * Change `FromRequest` and add `FromRequestParts` trait (#1275) * Add `Once`/`Mut` type parameter for `FromRequest` and `RequestParts` * 🪄 * split traits * `FromRequest` for tuples * Remove `BodyAlreadyExtracted` * don't need fully qualified path * don't export `Once` and `Mut` * remove temp tests * depend on axum again Co-authored-by: Jonas Platte <[email protected]> * Port `Handler` and most extractors (#1277) * Port `Handler` and most extractors * Put `M` inside `Handler` impls, not trait itself * comment out tuples for now * fix lints * Reorder arguments to `Handler` (#1281) I think `Request<B>, Arc<S>` is better since its consistent with `FromRequest` and `FromRequestParts`. * Port most things in axum-extra (#1282) * Port `#[derive(TypedPath)]` and `#[debug_handler]` (#1283) * port #[derive(TypedPath)] * wip: #[debug_handler] * fix #[debug_handler] * don't need itertools * also require `Send` * update expected error * support fully qualified `self` * Implement FromRequest[Parts] for tuples (#1286) * Port docs for axum and axum-core (#1285) * Port axum-extra (#1287) * Port axum-extra * Update axum-core/Cargo.toml Co-authored-by: Jonas Platte <[email protected]> * remove `impl FromRequest for Either*` Co-authored-by: Jonas Platte <[email protected]> * New FromRequest[Parts] trait cleanup (#1288) * Make private module truly private again * Simplify tuple FromRequest implementation * Port `#[derive(FromRequest)]` (#1289) * fix tests * fix docs * revert examples * fix docs link * fix intra docs links * Port examples (#1291) * Document wrapping other extractors (#1292) * axum-extra doesn't need to depend on axum-core (#1294) Missed this in https://github.com/tokio-rs/axum/pull/1287 * Add `FromRequest` changes to changelogs (#1293) * Update changelog * Remove default type for `S` in `Handler` * Clarify which types have default types for `S` * Apply suggestions from code review Co-authored-by: Jonas Platte <[email protected]> Co-authored-by: Jonas Platte <[email protected]> * remove unused import * Rename `Mut` and `Once` (#1296) * fix trybuild expected output 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>,
|
|
State(state): State<SharedState>,
|
|
ContentLengthLimit(bytes): ContentLengthLimit<Bytes, { 1024 * 5_000 }>, // ~5mb
|
|
) {
|
|
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)),
|
|
)
|
|
}
|