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