rama_http/service/fs/serve_dir/mod.rs
1use crate::dep::http_body::{self, Body as HttpBody};
2use crate::layer::set_status::SetStatus;
3use crate::{Body, HeaderValue, Method, Request, Response, StatusCode, header};
4use bytes::Bytes;
5use percent_encoding::percent_decode;
6use rama_core::error::{BoxError, OpaqueError};
7use rama_core::{Context, Service};
8use rama_http_types::headers::encoding::{SupportedEncodings, parse_accept_encoding_headers};
9use std::fmt;
10use std::str::FromStr;
11use std::{
12 convert::Infallible,
13 path::{Component, Path, PathBuf},
14};
15
16pub(crate) mod future;
17mod headers;
18mod open_file;
19
20#[cfg(test)]
21mod tests;
22
23// default capacity 64KiB
24const DEFAULT_CAPACITY: usize = 65536;
25
26/// Service that serves files from a given directory and all its sub directories.
27///
28/// The `Content-Type` will be guessed from the file extension.
29///
30/// An empty response with status `404 Not Found` will be returned if:
31///
32/// - The file doesn't exist
33/// - Any segment of the path contains `..`
34/// - Any segment of the path contains a backslash
35/// - On unix, any segment of the path referenced as directory is actually an
36/// existing file (`/file.html/something`)
37/// - We don't have necessary permissions to read the file
38///
39/// # Example
40///
41/// ```rust,no_run
42/// use rama_http_backend::server::HttpServer;
43/// use rama_http::service::fs::{ServeDir, ServeFile};
44/// use rama_core::{
45/// rt::Executor,
46/// Layer, layer::TraceErrLayer,
47/// };
48/// use rama_tcp::server::TcpListener;
49///
50/// #[tokio::main]
51/// async fn main() {
52/// let exec = Executor::default();
53///
54/// let listener = TcpListener::bind("127.0.0.1:8080")
55/// .await
56/// .expect("bind TCP Listener");
57///
58/// // This will serve files in the "assets" directory and
59/// // its subdirectories
60/// let http_fs_server = HttpServer::auto(exec).service(ServeDir::new("assets"));
61///
62/// // Serve the HTTP server over TCP
63/// listener
64/// .serve(TraceErrLayer::new().into_layer(http_fs_server))
65/// .await;
66/// }
67/// ```
68#[derive(Clone, Debug)]
69pub struct ServeDir<F = DefaultServeDirFallback> {
70 base: PathBuf,
71 buf_chunk_size: usize,
72 precompressed_variants: Option<PrecompressedVariants>,
73 // This is used to specialise implementation for
74 // single files
75 variant: ServeVariant,
76 fallback: Option<F>,
77 call_fallback_on_method_not_allowed: bool,
78}
79
80impl ServeDir<DefaultServeDirFallback> {
81 /// Create a new [`ServeDir`].
82 pub fn new<P>(path: P) -> Self
83 where
84 P: AsRef<Path>,
85 {
86 let mut base = PathBuf::from(".");
87 base.push(path.as_ref());
88
89 Self {
90 base,
91 buf_chunk_size: DEFAULT_CAPACITY,
92 precompressed_variants: None,
93 variant: ServeVariant::Directory {
94 serve_mode: Default::default(),
95 },
96 fallback: None,
97 call_fallback_on_method_not_allowed: false,
98 }
99 }
100
101 pub(crate) fn new_single_file<P>(path: P, mime: HeaderValue) -> Self
102 where
103 P: AsRef<Path>,
104 {
105 Self {
106 base: path.as_ref().to_owned(),
107 buf_chunk_size: DEFAULT_CAPACITY,
108 precompressed_variants: None,
109 variant: ServeVariant::SingleFile { mime },
110 fallback: None,
111 call_fallback_on_method_not_allowed: false,
112 }
113 }
114}
115
116impl<F> ServeDir<F> {
117 /// Set the [`DirectoryServeMode`].
118 pub fn with_directory_serve_mode(mut self, mode: DirectoryServeMode) -> Self {
119 match &mut self.variant {
120 ServeVariant::Directory { serve_mode } => {
121 *serve_mode = mode;
122 self
123 }
124 ServeVariant::SingleFile { mime: _ } => self,
125 }
126 }
127
128 /// Set the [`DirectoryServeMode`].
129 pub fn set_directory_serve_mode(&mut self, mode: DirectoryServeMode) -> &mut Self {
130 match &mut self.variant {
131 ServeVariant::Directory { serve_mode } => {
132 *serve_mode = mode;
133 self
134 }
135 ServeVariant::SingleFile { mime: _ } => self,
136 }
137 }
138
139 /// Set a specific read buffer chunk size.
140 ///
141 /// The default capacity is 64kb.
142 pub fn with_buf_chunk_size(mut self, chunk_size: usize) -> Self {
143 self.buf_chunk_size = chunk_size;
144 self
145 }
146
147 /// Set a specific read buffer chunk size.
148 ///
149 /// The default capacity is 64kb.
150 pub fn set_buf_chunk_size(&mut self, chunk_size: usize) -> &mut Self {
151 self.buf_chunk_size = chunk_size;
152 self
153 }
154
155 /// Informs the service that it should also look for a precompressed gzip
156 /// version of _any_ file in the directory.
157 ///
158 /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
159 /// a client with an `Accept-Encoding` header that allows the gzip encoding
160 /// will receive the file `dir/foo.txt.gz` instead of `dir/foo.txt`.
161 /// If the precompressed file is not available, or the client doesn't support it,
162 /// the uncompressed version will be served instead.
163 /// Both the precompressed version and the uncompressed version are expected
164 /// to be present in the directory. Different precompressed variants can be combined.
165 pub fn precompressed_gzip(mut self) -> Self {
166 self.precompressed_variants
167 .get_or_insert(Default::default())
168 .gzip = true;
169 self
170 }
171
172 /// Informs the service that it should also look for a precompressed gzip
173 /// version of _any_ file in the directory.
174 ///
175 /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
176 /// a client with an `Accept-Encoding` header that allows the gzip encoding
177 /// will receive the file `dir/foo.txt.gz` instead of `dir/foo.txt`.
178 /// If the precompressed file is not available, or the client doesn't support it,
179 /// the uncompressed version will be served instead.
180 /// Both the precompressed version and the uncompressed version are expected
181 /// to be present in the directory. Different precompressed variants can be combined.
182 pub fn set_precompressed_gzip(&mut self) -> &mut Self {
183 self.precompressed_variants
184 .get_or_insert(Default::default())
185 .gzip = true;
186 self
187 }
188
189 /// Informs the service that it should also look for a precompressed brotli
190 /// version of _any_ file in the directory.
191 ///
192 /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
193 /// a client with an `Accept-Encoding` header that allows the brotli encoding
194 /// will receive the file `dir/foo.txt.br` instead of `dir/foo.txt`.
195 /// If the precompressed file is not available, or the client doesn't support it,
196 /// the uncompressed version will be served instead.
197 /// Both the precompressed version and the uncompressed version are expected
198 /// to be present in the directory. Different precompressed variants can be combined.
199 pub fn precompressed_br(mut self) -> Self {
200 self.precompressed_variants
201 .get_or_insert(Default::default())
202 .br = true;
203 self
204 }
205
206 /// Informs the service that it should also look for a precompressed brotli
207 /// version of _any_ file in the directory.
208 ///
209 /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
210 /// a client with an `Accept-Encoding` header that allows the brotli encoding
211 /// will receive the file `dir/foo.txt.br` instead of `dir/foo.txt`.
212 /// If the precompressed file is not available, or the client doesn't support it,
213 /// the uncompressed version will be served instead.
214 /// Both the precompressed version and the uncompressed version are expected
215 /// to be present in the directory. Different precompressed variants can be combined.
216 pub fn set_precompressed_br(&mut self) -> &mut Self {
217 self.precompressed_variants
218 .get_or_insert(Default::default())
219 .br = true;
220 self
221 }
222
223 /// Informs the service that it should also look for a precompressed deflate
224 /// version of _any_ file in the directory.
225 ///
226 /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
227 /// a client with an `Accept-Encoding` header that allows the deflate encoding
228 /// will receive the file `dir/foo.txt.zz` instead of `dir/foo.txt`.
229 /// If the precompressed file is not available, or the client doesn't support it,
230 /// the uncompressed version will be served instead.
231 /// Both the precompressed version and the uncompressed version are expected
232 /// to be present in the directory. Different precompressed variants can be combined.
233 pub fn precompressed_deflate(mut self) -> Self {
234 self.precompressed_variants
235 .get_or_insert(Default::default())
236 .deflate = true;
237 self
238 }
239
240 /// Informs the service that it should also look for a precompressed deflate
241 /// version of _any_ file in the directory.
242 ///
243 /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
244 /// a client with an `Accept-Encoding` header that allows the deflate encoding
245 /// will receive the file `dir/foo.txt.zz` instead of `dir/foo.txt`.
246 /// If the precompressed file is not available, or the client doesn't support it,
247 /// the uncompressed version will be served instead.
248 /// Both the precompressed version and the uncompressed version are expected
249 /// to be present in the directory. Different precompressed variants can be combined.
250 pub fn set_precompressed_deflate(&mut self) -> &mut Self {
251 self.precompressed_variants
252 .get_or_insert(Default::default())
253 .deflate = true;
254 self
255 }
256
257 /// Informs the service that it should also look for a precompressed zstd
258 /// version of _any_ file in the directory.
259 ///
260 /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
261 /// a client with an `Accept-Encoding` header that allows the zstd encoding
262 /// will receive the file `dir/foo.txt.zst` instead of `dir/foo.txt`.
263 /// If the precompressed file is not available, or the client doesn't support it,
264 /// the uncompressed version will be served instead.
265 /// Both the precompressed version and the uncompressed version are expected
266 /// to be present in the directory. Different precompressed variants can be combined.
267 pub fn precompressed_zstd(mut self) -> Self {
268 self.precompressed_variants
269 .get_or_insert(Default::default())
270 .zstd = true;
271 self
272 }
273
274 /// Informs the service that it should also look for a precompressed zstd
275 /// version of _any_ file in the directory.
276 ///
277 /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
278 /// a client with an `Accept-Encoding` header that allows the zstd encoding
279 /// will receive the file `dir/foo.txt.zst` instead of `dir/foo.txt`.
280 /// If the precompressed file is not available, or the client doesn't support it,
281 /// the uncompressed version will be served instead.
282 /// Both the precompressed version and the uncompressed version are expected
283 /// to be present in the directory. Different precompressed variants can be combined.
284 pub fn set_precompressed_zstd(&mut self) -> &mut Self {
285 self.precompressed_variants
286 .get_or_insert(Default::default())
287 .zstd = true;
288 self
289 }
290
291 /// Set the fallback service.
292 ///
293 /// This service will be called if there is no file at the path of the request.
294 ///
295 /// The status code returned by the fallback will not be altered. Use
296 /// [`ServeDir::not_found_service`] to set a fallback and always respond with `404 Not Found`.
297 ///
298 /// # Example
299 ///
300 /// This can be used to respond with a different file:
301 ///
302 /// ```rust,no_run
303 /// use rama_core::{
304 /// rt::Executor,
305 /// Layer, layer::TraceErrLayer,
306 /// };
307 /// use rama_tcp::server::TcpListener;
308 /// use rama_http_backend::server::HttpServer;
309 /// use rama_http::service::fs::{ServeDir, ServeFile};
310 ///
311 /// #[tokio::main]
312 /// async fn main() {
313 /// let exec = Executor::default();
314 ///
315 /// let listener = TcpListener::bind("127.0.0.1:8080")
316 /// .await
317 /// .expect("bind TCP Listener");
318 ///
319 /// // This will serve files in the "assets" directory and
320 /// // its subdirectories, and use assets/not_found.html as the fallback page
321 /// let fs_server = ServeDir::new("assets").fallback(ServeFile::new("assets/not_found.html"));
322 /// let http_fs_server = HttpServer::auto(exec).service(fs_server);
323 ///
324 /// // Serve the HTTP server over TCP
325 /// listener
326 /// .serve(TraceErrLayer::new().into_layer(http_fs_server))
327 /// .await;
328 /// }
329 /// ```
330 pub fn fallback<F2>(self, new_fallback: F2) -> ServeDir<F2> {
331 ServeDir {
332 base: self.base,
333 buf_chunk_size: self.buf_chunk_size,
334 precompressed_variants: self.precompressed_variants,
335 variant: self.variant,
336 fallback: Some(new_fallback),
337 call_fallback_on_method_not_allowed: self.call_fallback_on_method_not_allowed,
338 }
339 }
340
341 /// Set the fallback service and override the fallback's status code to `404 Not Found`.
342 ///
343 /// This service will be called if there is no file at the path of the request.
344 ///
345 /// # Example
346 ///
347 /// This can be used to respond with a different file:
348 ///
349 /// ```rust,no_run
350 /// use rama_core::{
351 /// rt::Executor,
352 /// layer::TraceErrLayer,
353 /// Layer,
354 /// };
355 /// use rama_tcp::server::TcpListener;
356 /// use rama_http_backend::server::HttpServer;
357 /// use rama_http::service::fs::{ServeDir, ServeFile};
358 ///
359 /// #[tokio::main]
360 /// async fn main() {
361 /// let exec = Executor::default();
362 ///
363 /// let listener = TcpListener::bind("127.0.0.1:8080")
364 /// .await
365 /// .expect("bind TCP Listener");
366 ///
367 /// // This will serve files in the "assets" directory and
368 /// // its subdirectories, and use assets/not_found.html as the not_found page
369 /// let fs_server = ServeDir::new("assets").not_found_service(ServeFile::new("assets/not_found.html"));
370 /// let http_fs_server = HttpServer::auto(exec).service(fs_server);
371 ///
372 /// // Serve the HTTP server over TCP
373 /// listener
374 /// .serve(TraceErrLayer::new().into_layer(http_fs_server))
375 /// .await;
376 /// }
377 /// ```
378 ///
379 /// Setups like this are often found in single page applications.
380 pub fn not_found_service<F2>(self, new_fallback: F2) -> ServeDir<SetStatus<F2>> {
381 self.fallback(SetStatus::new(new_fallback, StatusCode::NOT_FOUND))
382 }
383
384 /// Customize whether or not to call the fallback for requests that aren't `GET` or `HEAD`.
385 ///
386 /// Defaults to not calling the fallback and instead returning `405 Method Not Allowed`.
387 pub fn call_fallback_on_method_not_allowed(mut self, call_fallback: bool) -> Self {
388 self.call_fallback_on_method_not_allowed = call_fallback;
389 self
390 }
391
392 /// Customize whether or not to call the fallback for requests that aren't `GET` or `HEAD`.
393 ///
394 /// Defaults to not calling the fallback and instead returning `405 Method Not Allowed`.
395 pub fn set_call_fallback_on_method_not_allowed(&mut self, call_fallback: bool) -> &mut Self {
396 self.call_fallback_on_method_not_allowed = call_fallback;
397 self
398 }
399
400 /// Call the service and get a future that contains any `std::io::Error` that might have
401 /// happened.
402 ///
403 /// By default `<ServeDir as Service<_>>::call` will handle IO errors and convert them into
404 /// responses. It does that by converting [`std::io::ErrorKind::NotFound`] and
405 /// [`std::io::ErrorKind::PermissionDenied`] to `404 Not Found` and any other error to `500
406 /// Internal Server Error`. The error will also be logged with `tracing`.
407 ///
408 /// If you want to manually control how the error response is generated you can make a new
409 /// service that wraps a `ServeDir` and calls `try_call` instead of `call`.
410 ///
411 /// # Example
412 ///
413 /// ```rust,no_run
414 /// use rama_core::{
415 /// rt::Executor,
416 /// service::service_fn,
417 /// layer::TraceErrLayer,
418 /// Context, Layer,
419 /// };
420 /// use rama_tcp::server::TcpListener;
421 /// use rama_http_backend::server::HttpServer;
422 /// use rama_http::service::fs::ServeDir;
423 /// use rama_http::{Body, Request, Response, StatusCode};
424 /// use std::convert::Infallible;
425 ///
426 /// #[tokio::main]
427 /// async fn main() {
428 /// let exec = Executor::default();
429 ///
430 /// let listener = TcpListener::bind("127.0.0.1:8080")
431 /// .await
432 /// .expect("bind TCP Listener");
433 ///
434 /// // This will serve files in the "assets" directory and
435 /// // its subdirectories, and use assets/not_found.html as the fallback page
436 /// let http_fs_server = HttpServer::auto(exec).service(service_fn(serve_dir));
437 ///
438 /// // Serve the HTTP server over TCP
439 /// listener
440 /// .serve(TraceErrLayer::new().into_layer(http_fs_server))
441 /// .await;
442 /// }
443 ///
444 /// async fn serve_dir<State>(
445 /// ctx: Context<State>,
446 /// request: Request,
447 /// ) -> Result<Response<Body>, Infallible>
448 /// where
449 /// State: Clone + Send + Sync + 'static,
450 /// {
451 /// let service = ServeDir::new("assets");
452 ///
453 /// match service.try_call(ctx, request).await {
454 /// Ok(response) => Ok(response),
455 /// Err(_) => {
456 /// let body = Body::from("Something went wrong...");
457 /// let response = Response::builder()
458 /// .status(StatusCode::INTERNAL_SERVER_ERROR)
459 /// .body(body)
460 /// .unwrap();
461 /// Ok(response)
462 /// }
463 /// }
464 /// }
465 /// ```
466 pub async fn try_call<State, ReqBody, FResBody>(
467 &self,
468 ctx: Context<State>,
469 req: Request<ReqBody>,
470 ) -> Result<Response, std::io::Error>
471 where
472 State: Clone + Send + Sync + 'static,
473 F: Service<State, Request<ReqBody>, Response = Response<FResBody>, Error = Infallible>
474 + Clone,
475 FResBody: http_body::Body<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
476 {
477 if req.method() != Method::GET && req.method() != Method::HEAD {
478 if self.call_fallback_on_method_not_allowed {
479 if let Some(fallback) = self.fallback.as_ref() {
480 return future::serve_fallback(fallback, ctx, req).await;
481 }
482 } else {
483 return Ok(future::method_not_allowed());
484 }
485 }
486
487 // `ServeDir` doesn't care about the request body but the fallback might. So move out the
488 // body and pass it to the fallback, leaving an empty body in its place
489 //
490 // this is necessary because we cannot clone bodies
491 let (mut parts, body) = req.into_parts();
492 // same goes for extensions
493 let extensions = std::mem::take(&mut parts.extensions);
494 let req = Request::from_parts(parts, Body::empty());
495
496 let fallback_and_request = self.fallback.as_ref().map(|fallback| {
497 let mut fallback_req = Request::new(body);
498 *fallback_req.method_mut() = req.method().clone();
499 *fallback_req.uri_mut() = req.uri().clone();
500 *fallback_req.headers_mut() = req.headers().clone();
501 *fallback_req.extensions_mut() = extensions;
502
503 (fallback, ctx, fallback_req)
504 });
505
506 let path_to_file = match self
507 .variant
508 .build_and_validate_path(&self.base, req.uri().path())
509 {
510 Some(path_to_file) => path_to_file,
511 None => {
512 return if let Some((fallback, ctx, request)) = fallback_and_request {
513 future::serve_fallback(fallback, ctx, request).await
514 } else {
515 Ok(future::not_found())
516 };
517 }
518 };
519
520 let buf_chunk_size = self.buf_chunk_size;
521 let range_header = req
522 .headers()
523 .get(header::RANGE)
524 .and_then(|value| value.to_str().ok())
525 .map(|s| s.to_owned());
526
527 let negotiated_encodings: Vec<_> = parse_accept_encoding_headers(
528 req.headers(),
529 self.precompressed_variants.unwrap_or_default(),
530 )
531 .collect();
532
533 let variant = self.variant.clone();
534
535 let open_file_result = open_file::open_file(
536 variant,
537 path_to_file,
538 req,
539 negotiated_encodings,
540 range_header,
541 buf_chunk_size,
542 )
543 .await;
544
545 future::consume_open_file_result(open_file_result, fallback_and_request).await
546 }
547}
548
549impl<State, ReqBody, F, FResBody> Service<State, Request<ReqBody>> for ServeDir<F>
550where
551 State: Clone + Send + Sync + 'static,
552 ReqBody: Send + 'static,
553 F: Service<State, Request<ReqBody>, Response = Response<FResBody>, Error = Infallible> + Clone,
554 FResBody: HttpBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
555{
556 type Response = Response;
557 type Error = Infallible;
558
559 async fn serve(
560 &self,
561 ctx: Context<State>,
562 req: Request<ReqBody>,
563 ) -> Result<Self::Response, Self::Error> {
564 let result = self.try_call(ctx, req).await;
565 Ok(result.unwrap_or_else(|err| {
566 tracing::error!(error = %err, "Failed to read file");
567
568 let body = Body::empty();
569 Response::builder()
570 .status(StatusCode::INTERNAL_SERVER_ERROR)
571 .body(body)
572 .unwrap()
573 }))
574 }
575}
576
577#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
578pub enum DirectoryServeMode {
579 #[default]
580 /// If the requested path is a directory append `index.html`.
581 ///
582 /// This is useful for static sites
583 AppendIndexHtml,
584 /// If the requested path is a directory
585 /// handle it as resource "not found" (404).
586 NotFound,
587 /// Show the file tree of the directory as file tree
588 /// which can be navigated.
589 HtmlFileList,
590}
591
592impl fmt::Display for DirectoryServeMode {
593 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
594 write!(
595 f,
596 "{}",
597 match self {
598 DirectoryServeMode::AppendIndexHtml => "append-index",
599 DirectoryServeMode::NotFound => "not-found",
600 DirectoryServeMode::HtmlFileList => "html-file-list",
601 }
602 )
603 }
604}
605
606impl FromStr for DirectoryServeMode {
607 type Err = OpaqueError;
608
609 fn from_str(s: &str) -> Result<Self, Self::Err> {
610 rama_utils::macros::match_ignore_ascii_case_str! {
611 match(s) {
612 "append-index" | "append_index" => Ok(Self::AppendIndexHtml),
613 "not-found" | "not_found" => Ok(Self::NotFound),
614 "html-file-list" | "html_file_list" => Ok(Self::HtmlFileList),
615 _ => Err(OpaqueError::from_display("invalid DirectoryServeMode str")),
616 }
617 }
618 }
619}
620
621impl TryFrom<&str> for DirectoryServeMode {
622 type Error = OpaqueError;
623
624 #[inline]
625 fn try_from(value: &str) -> Result<Self, Self::Error> {
626 value.parse()
627 }
628}
629
630// Allow the ServeDir service to be used in the ServeFile service
631// with almost no overhead
632#[derive(Clone, Debug)]
633enum ServeVariant {
634 Directory { serve_mode: DirectoryServeMode },
635 SingleFile { mime: HeaderValue },
636}
637
638impl ServeVariant {
639 fn build_and_validate_path(&self, base_path: &Path, requested_path: &str) -> Option<PathBuf> {
640 match self {
641 ServeVariant::Directory { serve_mode: _ } => {
642 let path = requested_path.trim_start_matches('/');
643
644 let path_decoded = percent_decode(path.as_ref()).decode_utf8().ok()?;
645 let path_decoded = Path::new(&*path_decoded);
646
647 let mut path_to_file = base_path.to_path_buf();
648 for component in path_decoded.components() {
649 match component {
650 Component::Normal(comp) => {
651 // protect against paths like `/foo/c:/bar/baz` (#204)
652 if Path::new(&comp)
653 .components()
654 .all(|c| matches!(c, Component::Normal(_)))
655 {
656 path_to_file.push(comp)
657 } else {
658 return None;
659 }
660 }
661 Component::CurDir => {}
662 Component::Prefix(_) | Component::RootDir | Component::ParentDir => {
663 return None;
664 }
665 }
666 }
667 Some(path_to_file)
668 }
669 ServeVariant::SingleFile { mime: _ } => Some(base_path.to_path_buf()),
670 }
671 }
672}
673
674/// The default fallback service used with [`ServeDir`].
675#[derive(Debug, Clone, Copy)]
676pub struct DefaultServeDirFallback(Infallible);
677
678impl<State, ReqBody> Service<State, Request<ReqBody>> for DefaultServeDirFallback
679where
680 State: Clone + Send + Sync + 'static,
681 ReqBody: Send + 'static,
682{
683 type Response = Response;
684 type Error = Infallible;
685
686 async fn serve(
687 &self,
688 _ctx: Context<State>,
689 _req: Request<ReqBody>,
690 ) -> Result<Self::Response, Self::Error> {
691 match self.0 {}
692 }
693}
694
695#[derive(Clone, Copy, Debug, Default)]
696struct PrecompressedVariants {
697 gzip: bool,
698 deflate: bool,
699 br: bool,
700 zstd: bool,
701}
702
703impl SupportedEncodings for PrecompressedVariants {
704 fn gzip(&self) -> bool {
705 self.gzip
706 }
707
708 fn deflate(&self) -> bool {
709 self.deflate
710 }
711
712 fn br(&self) -> bool {
713 self.br
714 }
715
716 fn zstd(&self) -> bool {
717 self.zstd
718 }
719}