tower_embed/
response.rs

1use std::{
2    borrow::Cow,
3    convert::Infallible,
4    pin::Pin,
5    task::{Context, Poll},
6};
7
8use bytes::Bytes;
9use headers::HeaderMapExt;
10use http_body_util::BodyExt;
11
12type BoxBody = http_body_util::combinators::UnsyncBoxBody<Bytes, Infallible>;
13
14/// The body used in crate responses.
15#[derive(Debug)]
16pub struct ResponseBody(BoxBody);
17
18impl ResponseBody {
19    /// Create an empty response body.
20    pub(crate) fn empty() -> Self {
21        ResponseBody(http_body_util::Empty::new().boxed_unsync())
22    }
23
24    /// Create a new response body that contains a single chunk
25    pub(crate) fn full(data: Bytes) -> Self {
26        ResponseBody(http_body_util::Full::new(data).boxed_unsync())
27    }
28}
29
30impl http_body::Body for ResponseBody {
31    type Data = Bytes;
32    type Error = Infallible;
33
34    fn poll_frame(
35        mut self: Pin<&mut Self>,
36        cx: &mut Context<'_>,
37    ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
38        Pin::new(&mut self.0).poll_frame(cx)
39    }
40
41    fn is_end_stream(&self) -> bool {
42        self.0.is_end_stream()
43    }
44
45    fn size_hint(&self) -> http_body::SizeHint {
46        self.0.size_hint()
47    }
48}
49
50/// File information
51pub(crate) struct File {
52    pub content: Cow<'static, [u8]>,
53    pub content_type: headers::ContentType,
54    pub etag: headers::ETag,
55    pub last_modified: Option<headers::LastModified>,
56}
57
58/// Response future of [`ServeEmbed`]
59///
60/// [`ServeEmbed`]: crate::ServeEmbed
61pub struct ResponseFuture {
62    inner: ResponseFutureInner,
63}
64
65enum ResponseFutureInner {
66    Ready(Option<http::Response<ResponseBody>>),
67}
68
69impl ResponseFuture {
70    pub(crate) fn method_not_allowed() -> Self {
71        let response = http::Response::builder()
72            .header(
73                http::header::ALLOW,
74                http::HeaderValue::from_static("GET, HEAD"),
75            )
76            .status(http::StatusCode::METHOD_NOT_ALLOWED)
77            .body(ResponseBody::empty())
78            .unwrap();
79
80        Self {
81            inner: ResponseFutureInner::Ready(Some(response)),
82        }
83    }
84
85    pub(crate) fn file_not_found() -> Self {
86        let response = http::Response::builder()
87            .status(http::StatusCode::NOT_FOUND)
88            .body(ResponseBody::empty())
89            .unwrap();
90
91        Self {
92            inner: ResponseFutureInner::Ready(Some(response)),
93        }
94    }
95
96    pub(crate) fn not_modified() -> Self {
97        let response = http::Response::builder()
98            .status(http::StatusCode::NOT_MODIFIED)
99            .body(ResponseBody::empty())
100            .unwrap();
101
102        Self {
103            inner: ResponseFutureInner::Ready(Some(response)),
104        }
105    }
106
107    pub(crate) fn file(file: File) -> Self {
108        let mut response = http::Response::builder()
109            .status(http::StatusCode::OK)
110            .body(ResponseBody::full(match file.content {
111                Cow::Borrowed(bytes) => Bytes::from_static(bytes),
112                Cow::Owned(bytes) => Bytes::from_owner(bytes),
113            }))
114            .unwrap();
115
116        response.headers_mut().typed_insert(file.content_type);
117        response.headers_mut().typed_insert(file.etag);
118        if let Some(last_modified) = file.last_modified {
119            response.headers_mut().typed_insert(last_modified);
120        }
121
122        Self {
123            inner: ResponseFutureInner::Ready(Some(response)),
124        }
125    }
126}
127
128impl Future for ResponseFuture {
129    type Output = Result<http::Response<ResponseBody>, std::convert::Infallible>;
130
131    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
132        let response = match self.get_mut().inner {
133            ResponseFutureInner::Ready(ref mut response) => response
134                .take()
135                .expect("ResponseFuture polled after completion"),
136        };
137        Poll::Ready(Ok(response))
138    }
139}