hyper_staticfile/
service.rs

1use std::{future::Future, io::Error as IoError, path::PathBuf, pin::Pin};
2
3use http::{Request, Response};
4use hyper::service::Service;
5
6use crate::{
7    vfs::{FileOpener, IntoFileAccess, TokioFileOpener},
8    AcceptEncoding, Body, Resolver, ResponseBuilder,
9};
10
11/// High-level interface for serving static files.
12///
13/// This struct serves files based on the request path. The path is first sanitized, then mapped
14/// to a file on the filesystem. If the path corresponds to a directory, it will try to look for a
15/// directory index.
16///
17/// This struct allows direct access to its fields, but these fields are typically initialized by
18/// the accessors, using the builder pattern. The fields are basically a bunch of settings that
19/// determine the response details.
20///
21/// This struct also implements the `hyper::Service` trait, which simply wraps `Static::serve`.
22/// Note that using the trait currently involves an extra `Box`.
23///
24/// Cloning this struct is a cheap operation.
25pub struct Static<O = TokioFileOpener> {
26    /// The resolver instance used to open files.
27    pub resolver: Resolver<O>,
28    /// Whether to send cache headers, and what lifespan to indicate.
29    pub cache_headers: Option<u32>,
30}
31
32impl Static<TokioFileOpener> {
33    /// Create a new instance of `Static` with a given root path.
34    ///
35    /// The path may be absolute or relative. If `Path::new("")` is used, files will be served from
36    /// the current directory.
37    pub fn new(root: impl Into<PathBuf>) -> Self {
38        Self {
39            resolver: Resolver::new(root),
40            cache_headers: None,
41        }
42    }
43}
44
45impl<O: FileOpener> Static<O> {
46    /// Create a new instance of `Static` with a custom file opener.
47    pub fn with_opener(opener: O) -> Self {
48        Self {
49            resolver: Resolver::with_opener(opener),
50            cache_headers: None,
51        }
52    }
53
54    /// Add cache headers to responses for the given lifespan.
55    pub fn cache_headers(&mut self, value: Option<u32>) -> &mut Self {
56        self.cache_headers = value;
57        self
58    }
59
60    /// Set the encodings the client is allowed to request via the `Accept-Encoding` header.
61    pub fn allowed_encodings(&mut self, allowed_encodings: AcceptEncoding) -> &mut Self {
62        self.resolver.allowed_encodings = allowed_encodings;
63        self
64    }
65
66    /// Serve a request.
67    pub async fn serve<B>(
68        self,
69        request: Request<B>,
70    ) -> Result<Response<Body<<O::File as IntoFileAccess>::Output>>, IoError> {
71        let Self {
72            resolver,
73            cache_headers,
74        } = self;
75        resolver.resolve_request(&request).await.map(|result| {
76            ResponseBuilder::new()
77                .request(&request)
78                .cache_headers(cache_headers)
79                .build(result)
80                .expect("unable to build response")
81        })
82    }
83}
84
85impl<O> Clone for Static<O> {
86    fn clone(&self) -> Self {
87        Self {
88            resolver: self.resolver.clone(),
89            cache_headers: self.cache_headers,
90        }
91    }
92}
93
94impl<O, B> Service<Request<B>> for Static<O>
95where
96    O: FileOpener,
97    B: Send + Sync + 'static,
98{
99    type Response = Response<Body<<O::File as IntoFileAccess>::Output>>;
100    type Error = IoError;
101    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
102
103    fn call(&self, request: Request<B>) -> Self::Future {
104        Box::pin(self.clone().serve(request))
105    }
106}