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