axum_extra/body/
async_read_body.rs

1use axum::{
2    body::{Body, Bytes, HttpBody},
3    response::{IntoResponse, Response},
4    Error,
5};
6use pin_project_lite::pin_project;
7use std::{
8    pin::Pin,
9    task::{Context, Poll},
10};
11use tokio::io::AsyncRead;
12use tokio_util::io::ReaderStream;
13
14pin_project! {
15    /// An [`HttpBody`] created from an [`AsyncRead`].
16    ///
17    /// # Example
18    ///
19    /// `AsyncReadBody` can be used to stream the contents of a file:
20    ///
21    /// ```rust
22    /// use axum::{
23    ///     Router,
24    ///     routing::get,
25    ///     http::{StatusCode, header::CONTENT_TYPE},
26    ///     response::{Response, IntoResponse},
27    /// };
28    /// use axum_extra::body::AsyncReadBody;
29    /// use tokio::fs::File;
30    ///
31    /// async fn cargo_toml() -> Result<Response, (StatusCode, String)> {
32    ///     let file = File::open("Cargo.toml")
33    ///         .await
34    ///         .map_err(|err| {
35    ///             (StatusCode::NOT_FOUND, format!("File not found: {err}"))
36    ///         })?;
37    ///
38    ///     let headers = [(CONTENT_TYPE, "text/x-toml")];
39    ///     let body = AsyncReadBody::new(file);
40    ///     Ok((headers, body).into_response())
41    /// }
42    ///
43    /// let app = Router::new().route("/Cargo.toml", get(cargo_toml));
44    /// # let _: Router = app;
45    /// ```
46    #[cfg(feature = "async-read-body")]
47    #[derive(Debug)]
48    #[must_use]
49    pub struct AsyncReadBody {
50        #[pin]
51        body: Body,
52    }
53}
54
55impl AsyncReadBody {
56    /// Create a new `AsyncReadBody`.
57    pub fn new<R>(read: R) -> Self
58    where
59        R: AsyncRead + Send + 'static,
60    {
61        Self {
62            body: Body::from_stream(ReaderStream::new(read)),
63        }
64    }
65}
66
67impl HttpBody for AsyncReadBody {
68    type Data = Bytes;
69    type Error = Error;
70
71    #[inline]
72    fn poll_frame(
73        self: Pin<&mut Self>,
74        cx: &mut Context<'_>,
75    ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
76        self.project().body.poll_frame(cx)
77    }
78
79    #[inline]
80    fn is_end_stream(&self) -> bool {
81        self.body.is_end_stream()
82    }
83
84    #[inline]
85    fn size_hint(&self) -> http_body::SizeHint {
86        self.body.size_hint()
87    }
88}
89
90impl IntoResponse for AsyncReadBody {
91    fn into_response(self) -> Response {
92        self.body.into_response()
93    }
94}