Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tokio-rs/axum/llms.txt
Use this file to discover all available pages before exploring further.
Axum supports streaming response bodies using the Body type, which allows you to send data incrementally rather than buffering the entire response in memory.
Basic usage
Return a Body from any Stream<Item = Result<Bytes, E>>:
use axum::{
body::{Body, Bytes},
extract::Request,
http::StatusCode,
routing::post,
Router,
};
use futures_util::Stream;
async fn handler(request: Request) -> Body {
// Stream the request body back as response
request.into_body()
}
let app = Router::new().route("/echo", post(handler));
Converting bodies to streams
From request body to data stream
use axum::{
body::{Body, Bytes},
extract::Request,
};
use futures_util::{Stream, TryStreamExt};
use std::io;
async fn stream_handler(request: Request) -> Result<(), (StatusCode, String)> {
// Convert body to a stream of Bytes
let stream = request.into_body().into_data_stream();
// Process the stream
stream_to_file("output.txt", stream).await
}
async fn stream_to_file<S, E>(path: &str, stream: S) -> Result<(), (StatusCode, String)>
where
S: Stream<Item = Result<Bytes, E>>,
E: Into<BoxError>,
{
// Convert stream to AsyncRead and write to file
let body_with_io_error = stream.map_err(io::Error::other);
let body_reader = StreamReader::new(body_with_io_error);
let mut file = File::create(path).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
tokio::io::copy(&mut body_reader, &mut file).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(())
}
Example adapted from examples/stream-to-file/src/main.rs:52-128.
Body utilities
Converting body to bytes
Use to_bytes to consume a body with a size limit:
use axum::body::{to_bytes, Body};
#[tokio::main]
async fn main() -> Result<(), axum_core::Error> {
let body = Body::from(vec![1, 2, 3]);
// Limit to 1MB (use usize::MAX for no limit)
let bytes = to_bytes(body, 1024 * 1024).await?;
assert_eq!(&bytes[..], &[1, 2, 3]);
Ok(())
}
Detecting size limit errors
use axum::body::{to_bytes, Body};
use http_body_util::LengthLimitError;
#[tokio::main]
async fn main() {
let body = Body::from(vec![1, 2, 3]);
match to_bytes(body, 1).await {
Ok(_) => panic!("should have hit the limit"),
Err(err) => {
let source = std::error::Error::source(&err).unwrap();
assert!(source.is::<LengthLimitError>());
}
}
}
See axum/src/body/mod.rs:14-54.
Creating streaming responses
From static data
use axum::body::Body;
async fn handler() -> Body {
Body::from("Hello, World!")
}
// Or from bytes
async fn bytes_handler() -> Body {
Body::from(vec![1, 2, 3, 4])
}
From a stream
use axum::body::{Body, Bytes};
use futures_util::stream;
async fn stream_response() -> Body {
let data = vec![
Bytes::from("chunk 1\n"),
Bytes::from("chunk 2\n"),
Bytes::from("chunk 3\n"),
];
let stream = stream::iter(data.into_iter().map(Ok::<_, std::io::Error>));
Body::from_stream(stream)
}
Common patterns
Streaming file downloads
use axum::{
body::Body,
extract::Path,
http::{header, StatusCode},
response::IntoResponse,
};
use tokio::fs::File;
use tokio_util::io::ReaderStream;
async fn download_file(Path(filename): Path<String>) -> impl IntoResponse {
let file = match File::open(&filename).await {
Ok(file) => file,
Err(_) => return Err((StatusCode::NOT_FOUND, "File not found")),
};
let stream = ReaderStream::new(file);
let body = Body::from_stream(stream);
Ok((
[
(header::CONTENT_TYPE, "application/octet-stream"),
(header::CONTENT_DISPOSITION, &format!("attachment; filename=\"{}\"", filename)),
],
body,
))
}
Proxying requests
use axum::{
body::Body,
extract::Request,
response::Response,
};
use reqwest::Client;
async fn proxy(request: Request) -> Result<Response, StatusCode> {
let client = Client::new();
// Forward the request body as a stream
let response = client
.post("http://backend-service/api")
.body(request.into_body())
.send()
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
// Stream the response back
let mut builder = Response::builder().status(response.status());
for (key, value) in response.headers() {
builder = builder.header(key, value);
}
Ok(builder.body(Body::from_stream(response.bytes_stream())).unwrap())
}
Handling multipart streams
use axum::{
body::Bytes,
extract::Multipart,
};
use futures_util::Stream;
async fn upload_handler(mut multipart: Multipart) -> Result<(), StatusCode> {
while let Ok(Some(field)) = multipart.next_field().await {
let file_name = field.file_name()
.ok_or(StatusCode::BAD_REQUEST)?
.to_owned();
// Process field as a stream
process_field_stream(file_name, field).await?;
}
Ok(())
}
async fn process_field_stream<S, E>(
name: String,
stream: S,
) -> Result<(), StatusCode>
where
S: Stream<Item = Result<Bytes, E>>,
{
// Process the stream...
Ok(())
}
Body types
HttpBody trait
The underlying HttpBody trait from the http-body crate:
use axum::body::HttpBody;
// Re-exported as:
use http_body::Body as HttpBody;
Bytes type
The Bytes type from the bytes crate for efficient byte buffers:
use axum::body::Bytes;
// Re-exported as:
use bytes::Bytes;
BodyDataStream
Convert a Body to a Stream<Item = Result<Bytes, Error>>:
use axum::body::{Body, BodyDataStream};
let body = Body::from("data");
let stream: BodyDataStream = body.into_data_stream();
Buffering
- Streaming avoids loading entire responses into memory
- Useful for large files, proxying, or real-time data
- Consider backpressure when streaming from slow sources
Size limits
use axum::body::to_bytes;
// Always set appropriate limits when consuming bodies
let bytes = to_bytes(body, 10 * 1024 * 1024).await?; // 10MB limit
See also