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