Add example for consuming request body in middleware and extractor (#861)

This commit is contained in:
David Pedersen 2022-03-18 16:41:38 +01:00 committed by GitHub
parent 30b2cf8f96
commit 33ee55e52c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 111 additions and 0 deletions

View file

@ -0,0 +1,14 @@
[package]
name = "example-consume-body-in-extractor-or-middleware"
version = "0.1.0"
edition = "2018"
publish = false
[dependencies]
axum = { path = "../../axum" }
hyper = "0.14"
tokio = { version = "1.0", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.2", features = ["map-request-body", "util"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

View file

@ -0,0 +1,97 @@
//! Run with
//!
//! ```not_rust
//! cargo run -p example-consume-body-in-extractor-or-middleware
//! ```
use axum::{
async_trait,
body::{self, BoxBody, Bytes, Full},
extract::{FromRequest, RequestParts},
http::{Request, StatusCode},
middleware::{self, Next},
response::{IntoResponse, Response},
routing::post,
Router,
};
use std::net::SocketAddr;
use tower::ServiceBuilder;
use tower_http::ServiceBuilderExt;
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_consume_body_in_extractor_or_middleware=debug".into()),
))
.with(tracing_subscriber::fmt::layer())
.init();
let app = Router::new().route("/", post(handler)).layer(
ServiceBuilder::new()
.map_request_body(body::boxed)
.layer(middleware::from_fn(print_request_body)),
);
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();
}
// middleware that shows how to consume the request body upfront
async fn print_request_body(
request: Request<BoxBody>,
next: Next<BoxBody>,
) -> Result<impl IntoResponse, Response> {
let request = buffer_request_body(request).await?;
Ok(next.run(request).await)
}
// the trick is to take the request apart, buffer the body, do what you need to do, then put
// the request back together
async fn buffer_request_body(request: Request<BoxBody>) -> Result<Request<BoxBody>, Response> {
let (parts, body) = request.into_parts();
// this wont work if the body is an long running stream
let bytes = hyper::body::to_bytes(body)
.await
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response())?;
do_thing_with_request_body(bytes.clone());
Ok(Request::from_parts(parts, body::boxed(Full::from(bytes))))
}
fn do_thing_with_request_body(bytes: Bytes) {
tracing::debug!(body = ?bytes);
}
async fn handler(_: PrintRequestBody, body: Bytes) {
tracing::debug!(?body, "handler received body");
}
// extractor that shows how to consume the request body upfront
struct PrintRequestBody;
#[async_trait]
impl FromRequest<BoxBody> for PrintRequestBody {
type Rejection = Response;
async fn from_request(req: &mut RequestParts<BoxBody>) -> Result<Self, Self::Rejection> {
let request = Request::from_request(req)
.await
.map_err(|err| err.into_response())?;
let request = buffer_request_body(request).await?;
*req = RequestParts::new(request);
Ok(Self)
}
}