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}