tower_http/services/fs/serve_dir/
future.rs1use super::{
2 open_file::{FileOpened, FileRequestExtent, OpenFileOutput},
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 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 #[cfg(unix)]
148 let error_is_not_a_directory = err.raw_os_error() == Some(20);
153 #[cfg(not(unix))]
154 let error_is_not_a_directory = false;
155
156 if matches!(
157 err.kind(),
158 io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied
159 ) || error_is_not_a_directory
160 {
161 if let Some((mut fallback, request)) = fallback_and_request.take() {
162 call_fallback(&mut fallback, request)
163 } else {
164 break Poll::Ready(Ok(not_found()));
165 }
166 } else {
167 break Poll::Ready(Err(err));
168 }
169 }
170 },
171
172 ResponseFutureInnerProj::FallbackFuture { future } => {
173 break Pin::new(future).poll(cx).map_err(|err| match err {})
174 }
175
176 ResponseFutureInnerProj::InvalidPath {
177 fallback_and_request,
178 } => {
179 if let Some((mut fallback, request)) = fallback_and_request.take() {
180 call_fallback(&mut fallback, request)
181 } else {
182 break Poll::Ready(Ok(not_found()));
183 }
184 }
185
186 ResponseFutureInnerProj::MethodNotAllowed => {
187 let mut res = response_with_status(StatusCode::METHOD_NOT_ALLOWED);
188 res.headers_mut()
189 .insert(ALLOW, HeaderValue::from_static("GET,HEAD"));
190 break Poll::Ready(Ok(res));
191 }
192 };
193
194 this.inner.set(new_state);
195 }
196 }
197}
198
199fn response_with_status(status: StatusCode) -> Response<ResponseBody> {
200 Response::builder()
201 .status(status)
202 .body(empty_body())
203 .unwrap()
204}
205
206fn not_found() -> Response<ResponseBody> {
207 response_with_status(StatusCode::NOT_FOUND)
208}
209
210pub(super) fn call_fallback<F, B, FResBody>(
211 fallback: &mut F,
212 req: Request<B>,
213) -> ResponseFutureInner<B, F>
214where
215 F: Service<Request<B>, Response = Response<FResBody>, Error = Infallible> + Clone,
216 F::Future: Send + 'static,
217 FResBody: http_body::Body<Data = Bytes> + Send + 'static,
218 FResBody::Error: Into<BoxError>,
219{
220 let future = fallback
221 .call(req)
222 .map_ok(|response| {
223 response
224 .map(|body| {
225 UnsyncBoxBody::from_inner(
226 body.map_err(|err| match err.into().downcast::<io::Error>() {
227 Ok(err) => *err,
228 Err(err) => io::Error::new(io::ErrorKind::Other, err),
229 })
230 .boxed_unsync(),
231 )
232 })
233 .map(ResponseBody::new)
234 })
235 .boxed();
236
237 ResponseFutureInner::FallbackFuture { future }
238}
239
240fn build_response(output: FileOpened) -> Response<ResponseBody> {
241 let (maybe_file, size) = match output.extent {
242 FileRequestExtent::Full(file, size) => (Some(file), size),
243 FileRequestExtent::Head(size) => (None, size),
244 };
245
246 let mut builder = Response::builder()
247 .header(header::CONTENT_TYPE, output.mime_header_value)
248 .header(header::ACCEPT_RANGES, "bytes");
249
250 if let Some(encoding) = output
251 .maybe_encoding
252 .filter(|encoding| *encoding != Encoding::Identity)
253 {
254 builder = builder.header(header::CONTENT_ENCODING, encoding.into_header_value());
255 }
256
257 if output.precompression_configured {
260 builder = builder.header(header::VARY, "accept-encoding");
261 }
262
263 if let Some(last_modified) = output.last_modified {
264 builder = builder.header(header::LAST_MODIFIED, last_modified.0.to_string());
265 }
266
267 if let Some(etag) = output.etag {
268 builder = builder.header(header::ETAG, etag.into_header_value());
269 }
270
271 match output.maybe_range {
272 Some(Ok(ranges)) => {
273 if let Some(range) = ranges.first() {
274 if ranges.len() > 1 {
275 builder
276 .header(header::CONTENT_RANGE, format!("bytes */{}", size))
277 .status(StatusCode::RANGE_NOT_SATISFIABLE)
278 .body(body_from_bytes(Bytes::from(
279 "Cannot serve multipart range requests",
280 )))
281 .unwrap()
282 } else {
283 let body = if let Some(file) = maybe_file {
284 let range_size = range.end() - range.start() + 1;
285 ResponseBody::new(UnsyncBoxBody::from_inner(
286 AsyncReadBody::with_capacity_limited(
287 file,
288 output.chunk_size,
289 range_size,
290 )
291 .boxed_unsync(),
292 ))
293 } else {
294 empty_body()
295 };
296
297 let content_length = if size == 0 {
298 0
299 } else {
300 range.end() - range.start() + 1
301 };
302
303 builder
304 .header(
305 header::CONTENT_RANGE,
306 format!("bytes {}-{}/{}", range.start(), range.end(), size),
307 )
308 .header(header::CONTENT_LENGTH, content_length)
309 .status(StatusCode::PARTIAL_CONTENT)
310 .body(body)
311 .unwrap()
312 }
313 } else {
314 builder
315 .header(header::CONTENT_RANGE, format!("bytes */{}", size))
316 .status(StatusCode::RANGE_NOT_SATISFIABLE)
317 .body(body_from_bytes(Bytes::from(
318 "No range found after parsing range header, please file an issue",
319 )))
320 .unwrap()
321 }
322 }
323
324 Some(Err(_)) => builder
325 .header(header::CONTENT_RANGE, format!("bytes */{}", size))
326 .status(StatusCode::RANGE_NOT_SATISFIABLE)
327 .body(empty_body())
328 .unwrap(),
329
330 None => {
332 let body = if let Some(file) = maybe_file {
333 ResponseBody::new(UnsyncBoxBody::from_inner(
334 AsyncReadBody::with_capacity(file, output.chunk_size).boxed_unsync(),
335 ))
336 } else {
337 empty_body()
338 };
339
340 builder
341 .header(header::CONTENT_LENGTH, size)
342 .body(body)
343 .unwrap()
344 }
345 }
346}
347
348fn body_from_bytes(bytes: Bytes) -> ResponseBody {
349 let body = Full::from(bytes).map_err(|err| match err {}).boxed_unsync();
350 ResponseBody::new(UnsyncBoxBody::from_inner(body))
351}
352
353fn empty_body() -> ResponseBody {
354 let body = Empty::new().map_err(|err| match err {}).boxed_unsync();
355 ResponseBody::new(UnsyncBoxBody::from_inner(body))
356}