tower_http/services/fs/serve_dir/
future.rs1use 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 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 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 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}