diff --git a/examples/consume-body-in-extractor-or-middleware/Cargo.toml b/examples/consume-body-in-extractor-or-middleware/Cargo.toml new file mode 100644 index 00000000..e501985f --- /dev/null +++ b/examples/consume-body-in-extractor-or-middleware/Cargo.toml @@ -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"] } diff --git a/examples/consume-body-in-extractor-or-middleware/src/main.rs b/examples/consume-body-in-extractor-or-middleware/src/main.rs new file mode 100644 index 00000000..9b4e42a3 --- /dev/null +++ b/examples/consume-body-in-extractor-or-middleware/src/main.rs @@ -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, + next: Next, +) -> Result { + 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) -> Result, 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 for PrintRequestBody { + type Rejection = Response; + + async fn from_request(req: &mut RequestParts) -> Result { + 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) + } +}