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