Skip to main content

tower_http/services/fs/serve_dir/
future.rs

1use super::{
2    open_file::{FileOpened, FileRequestExtent, OpenFileOutput, RangeError},
3    DefaultServeDirFallback, ResponseBody,
4};
5use crate::{
6    body::UnsyncBoxBody, content_encoding::Encoding, services::fs::AsyncReadBody, BoxError,
7};
8use bytes::Bytes;
9use futures_core::future::BoxFuture;
10use futures_util::future::{FutureExt, TryFutureExt};
11use http::{
12    header::{self, ALLOW},
13    HeaderValue, Request, Response, StatusCode,
14};
15use http_body_util::{BodyExt, Empty, Full};
16use pin_project_lite::pin_project;
17use std::{
18    convert::Infallible,
19    future::Future,
20    io,
21    pin::Pin,
22    task::{ready, Context, Poll},
23};
24use tower_service::Service;
25
26pin_project! {
27    /// Response future of [`ServeDir::try_call()`][`super::ServeDir::try_call()`].
28    pub struct ResponseFuture<ReqBody, F = DefaultServeDirFallback> {
29        #[pin]
30        pub(super) inner: ResponseFutureInner<ReqBody, F>,
31    }
32}
33
34impl<ReqBody, F> ResponseFuture<ReqBody, F> {
35    pub(super) fn open_file_future(
36        future: BoxFuture<'static, io::Result<OpenFileOutput>>,
37        fallback_and_request: Option<(F, Request<ReqBody>)>,
38    ) -> Self {
39        Self {
40            inner: ResponseFutureInner::OpenFileFuture {
41                future,
42                fallback_and_request,
43            },
44        }
45    }
46
47    pub(super) fn invalid_path(fallback_and_request: Option<(F, Request<ReqBody>)>) -> Self {
48        Self {
49            inner: ResponseFutureInner::InvalidPath {
50                fallback_and_request,
51            },
52        }
53    }
54
55    pub(super) fn method_not_allowed() -> Self {
56        Self {
57            inner: ResponseFutureInner::MethodNotAllowed,
58        }
59    }
60}
61
62pin_project! {
63    #[project = ResponseFutureInnerProj]
64    pub(super) enum ResponseFutureInner<ReqBody, F> {
65        OpenFileFuture {
66            #[pin]
67            future: BoxFuture<'static, io::Result<OpenFileOutput>>,
68            fallback_and_request: Option<(F, Request<ReqBody>)>,
69        },
70        FallbackFuture {
71            future: BoxFuture<'static, Result<Response<ResponseBody>, Infallible>>,
72        },
73        InvalidPath {
74            fallback_and_request: Option<(F, Request<ReqBody>)>,
75        },
76        MethodNotAllowed,
77    }
78}
79
80impl<F, ReqBody, ResBody> Future for ResponseFuture<ReqBody, F>
81where
82    F: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible> + Clone,
83    F::Future: Send + 'static,
84    ResBody: http_body::Body<Data = Bytes> + Send + 'static,
85    ResBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
86{
87    type Output = io::Result<Response<ResponseBody>>;
88
89    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
90        loop {
91            let mut this = self.as_mut().project();
92
93            let new_state = match this.inner.as_mut().project() {
94                ResponseFutureInnerProj::OpenFileFuture {
95                    future: open_file_future,
96                    fallback_and_request,
97                } => match ready!(open_file_future.poll(cx)) {
98                    Ok(OpenFileOutput::FileOpened(file_output)) => {
99                        break Poll::Ready(Ok(build_response(*file_output)));
100                    }
101
102                    Ok(OpenFileOutput::Redirect { location }) => {
103                        let mut res = response_with_status(StatusCode::TEMPORARY_REDIRECT);
104                        res.headers_mut().insert(http::header::LOCATION, location);
105                        break Poll::Ready(Ok(res));
106                    }
107
108                    Ok(OpenFileOutput::FileNotFound | OpenFileOutput::InvalidFilename) => {
109                        if let Some((mut fallback, request)) = fallback_and_request.take() {
110                            call_fallback(&mut fallback, request)
111                        } else {
112                            break Poll::Ready(Ok(not_found()));
113                        }
114                    }
115
116                    Ok(OpenFileOutput::PreconditionFailed) => {
117                        break Poll::Ready(Ok(response_with_status(
118                            StatusCode::PRECONDITION_FAILED,
119                        )));
120                    }
121
122                    Ok(OpenFileOutput::NotModified {
123                        etag,
124                        last_modified,
125                    }) => {
126                        let mut res = response_with_status(StatusCode::NOT_MODIFIED);
127                        if let Some(etag) = etag {
128                            res.headers_mut()
129                                .insert(header::ETAG, etag.into_header_value());
130                        }
131                        if let Some(last_modified) = last_modified {
132                            res.headers_mut().insert(
133                                header::LAST_MODIFIED,
134                                HeaderValue::from_str(&last_modified.0.to_string()).unwrap(),
135                            );
136                        }
137                        break Poll::Ready(Ok(res));
138                    }
139
140                    Ok(OpenFileOutput::InvalidRedirectUri) => {
141                        break Poll::Ready(Ok(response_with_status(
142                            StatusCode::INTERNAL_SERVER_ERROR,
143                        )));
144                    }
145
146                    Err(err) => {
147                        if super::should_return_not_found(&err) {
148                            if let Some((mut fallback, request)) = fallback_and_request.take() {
149                                call_fallback(&mut fallback, request)
150                            } else {
151                                break Poll::Ready(Err(err));
152                            }
153                        } else {
154                            break Poll::Ready(Err(err));
155                        }
156                    }
157                },
158
159                ResponseFutureInnerProj::FallbackFuture { future } => {
160                    break Pin::new(future).poll(cx).map_err(|err| match err {})
161                }
162
163                ResponseFutureInnerProj::InvalidPath {
164                    fallback_and_request,
165                } => {
166                    if let Some((mut fallback, request)) = fallback_and_request.take() {
167                        call_fallback(&mut fallback, request)
168                    } else {
169                        break Poll::Ready(Ok(not_found()));
170                    }
171                }
172
173                ResponseFutureInnerProj::MethodNotAllowed => {
174                    let mut res = response_with_status(StatusCode::METHOD_NOT_ALLOWED);
175                    res.headers_mut()
176                        .insert(ALLOW, HeaderValue::from_static("GET,HEAD"));
177                    break Poll::Ready(Ok(res));
178                }
179            };
180
181            this.inner.set(new_state);
182        }
183    }
184}
185
186fn response_with_status(status: StatusCode) -> Response<ResponseBody> {
187    Response::builder()
188        .status(status)
189        .body(empty_body())
190        .unwrap()
191}
192
193fn not_found() -> Response<ResponseBody> {
194    response_with_status(StatusCode::NOT_FOUND)
195}
196
197pub(super) fn call_fallback<F, B, FResBody>(
198    fallback: &mut F,
199    req: Request<B>,
200) -> ResponseFutureInner<B, F>
201where
202    F: Service<Request<B>, Response = Response<FResBody>, Error = Infallible> + Clone,
203    F::Future: Send + 'static,
204    FResBody: http_body::Body<Data = Bytes> + Send + 'static,
205    FResBody::Error: Into<BoxError>,
206{
207    let future = fallback
208        .call(req)
209        .map_ok(|response| {
210            response
211                .map(|body| {
212                    UnsyncBoxBody::from_inner(
213                        body.map_err(|err| match err.into().downcast::<io::Error>() {
214                            Ok(err) => *err,
215                            Err(err) => io::Error::new(io::ErrorKind::Other, err),
216                        })
217                        .boxed_unsync(),
218                    )
219                })
220                .map(ResponseBody::new)
221        })
222        .boxed();
223
224    ResponseFutureInner::FallbackFuture { future }
225}
226
227fn build_response(output: FileOpened) -> Response<ResponseBody> {
228    let (maybe_file, size) = match output.extent {
229        FileRequestExtent::Full(file, size) => (Some(file), size),
230        FileRequestExtent::Head(size) => (None, size),
231    };
232
233    let mut builder = Response::builder()
234        .header(header::CONTENT_TYPE, output.mime_header_value)
235        .header(header::ACCEPT_RANGES, "bytes");
236
237    if let Some(encoding) = output
238        .maybe_encoding
239        .filter(|encoding| *encoding != Encoding::Identity)
240    {
241        builder = builder.header(header::CONTENT_ENCODING, encoding.into_header_value());
242    }
243
244    // Per RFC 9110 ยง12.5.3, Vary must be sent when the response could differ
245    // based on Accept-Encoding, even if this particular response is uncompressed.
246    if output.precompression_configured {
247        builder = builder.header(header::VARY, "accept-encoding");
248    }
249
250    if let Some(last_modified) = output.last_modified {
251        builder = builder.header(header::LAST_MODIFIED, last_modified.0.to_string());
252    }
253
254    if let Some(etag) = output.etag {
255        builder = builder.header(header::ETAG, etag.into_header_value());
256    }
257
258    match output.maybe_range {
259        Some(Ok(range)) => {
260            let body = if let Some(file) = maybe_file {
261                let range_size = range.end() - range.start() + 1;
262                ResponseBody::new(UnsyncBoxBody::from_inner(
263                    AsyncReadBody::with_capacity_limited(file, output.chunk_size, range_size)
264                        .boxed_unsync(),
265                ))
266            } else {
267                empty_body()
268            };
269
270            let content_length = if size == 0 {
271                0
272            } else {
273                range.end() - range.start() + 1
274            };
275
276            builder
277                .header(
278                    header::CONTENT_RANGE,
279                    format!("bytes {}-{}/{}", range.start(), range.end(), size),
280                )
281                .header(header::CONTENT_LENGTH, content_length)
282                .status(StatusCode::PARTIAL_CONTENT)
283                .body(body)
284                .unwrap()
285        }
286
287        Some(Err(RangeError::MultipleRangesNotSupported)) => {
288            let mut response = builder
289                .header(header::CONTENT_RANGE, format!("bytes */{}", size))
290                .status(StatusCode::RANGE_NOT_SATISFIABLE)
291                .body(body_from_bytes(Bytes::from(
292                    "Cannot serve multipart range requests",
293                )))
294                .unwrap();
295            response.headers_mut().remove(header::CONTENT_TYPE);
296            response.headers_mut().remove(header::CONTENT_ENCODING);
297            response
298        }
299
300        Some(Err(RangeError::Unsatisfiable)) => {
301            let mut response = builder
302                .header(header::CONTENT_RANGE, format!("bytes */{}", size))
303                .status(StatusCode::RANGE_NOT_SATISFIABLE)
304                .body(empty_body())
305                .unwrap();
306            response.headers_mut().remove(header::CONTENT_TYPE);
307            response.headers_mut().remove(header::CONTENT_ENCODING);
308            response
309        }
310
311        // Not a range request
312        None => {
313            let body = if let Some(file) = maybe_file {
314                ResponseBody::new(UnsyncBoxBody::from_inner(
315                    AsyncReadBody::with_capacity(file, output.chunk_size).boxed_unsync(),
316                ))
317            } else {
318                empty_body()
319            };
320
321            builder
322                .header(header::CONTENT_LENGTH, size)
323                .body(body)
324                .unwrap()
325        }
326    }
327}
328
329fn body_from_bytes(bytes: Bytes) -> ResponseBody {
330    let body = Full::from(bytes).map_err(|err| match err {}).boxed_unsync();
331    ResponseBody::new(UnsyncBoxBody::from_inner(body))
332}
333
334fn empty_body() -> ResponseBody {
335    let body = Empty::new().map_err(|err| match err {}).boxed_unsync();
336    ResponseBody::new(UnsyncBoxBody::from_inner(body))
337}