use super::{with_file, with_partial_file};
use super::{Caching, IntoPathBuf, Range};
use crate::error::ClientErrorKind;
use crate::header::{Method, StatusCode};
use crate::into::{IntoResponse, IntoRoute};
use crate::routes::{ParamsNames, PathParams, Route, RoutePath};
use crate::util::PinnedFuture;
use crate::{Error, Request, Resources, Response};
use std::borrow::Cow;
use std::io;
use std::path::Path;
use std::time::Duration;
pub async fn serve_file(
path: impl AsRef<Path>,
req: &Request,
caching: Option<Caching>,
) -> io::Result<Response> {
if matches!(&caching, Some(c) if c.if_none_match(req.header())) {
return Ok(caching.unwrap().into_response());
}
let range = Range::parse(req.header());
let mut res = match range {
Some(range) => with_partial_file(path, range).await?.into_response(),
None => with_file(path).await?.into_response(),
};
if let Some(caching) = caching {
if matches!(
res.header.status_code,
StatusCode::OK | StatusCode::NOT_FOUND
) {
caching.complete_header(&mut res.header);
}
}
Ok(res)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum CachingBuilder {
None,
Default,
MaxAge(Duration),
}
impl From<CachingBuilder> for Option<Caching> {
fn from(b: CachingBuilder) -> Self {
match b {
CachingBuilder::None => None,
CachingBuilder::Default => Some(Caching::default()),
CachingBuilder::MaxAge(age) => Some(Caching::new(age)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StaticFiles {
uri: &'static str,
path: &'static str,
caching: CachingBuilder,
}
impl StaticFiles {
pub const fn new(uri: &'static str, path: &'static str) -> Self {
Self {
uri,
path,
caching: CachingBuilder::Default,
}
}
pub const fn no_cache(uri: &'static str, path: &'static str) -> Self {
Self {
uri,
path,
caching: CachingBuilder::None,
}
}
pub const fn cache_with_age(
uri: &'static str,
path: &'static str,
max_age: Duration,
) -> Self {
Self {
uri,
path,
caching: CachingBuilder::MaxAge(max_age),
}
}
}
impl IntoRoute for StaticFiles {
type IntoRoute = StaticFilesRoute;
fn into_route(self) -> StaticFilesRoute {
StaticFilesRoute {
uri: self.uri.into(),
path: self.path.into(),
caching: self.caching.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticFilesOwned {
uri: String,
path: String,
caching: CachingBuilder,
}
impl StaticFilesOwned {
pub fn new(uri: String, path: String) -> Self {
Self {
uri,
path,
caching: CachingBuilder::Default,
}
}
pub fn no_cache(uri: String, path: String) -> Self {
Self {
uri,
path,
caching: CachingBuilder::None,
}
}
pub fn cache_with_age(
uri: String,
path: String,
max_age: Duration,
) -> Self {
Self {
uri,
path,
caching: CachingBuilder::MaxAge(max_age),
}
}
}
impl IntoRoute for StaticFilesOwned {
type IntoRoute = StaticFilesRoute;
fn into_route(self) -> StaticFilesRoute {
StaticFilesRoute {
uri: self.uri.trim_end_matches('/').to_string().into(),
path: self.path.into(),
caching: self.caching.into(),
}
}
}
#[doc(hidden)]
pub struct StaticFilesRoute {
uri: Cow<'static, str>,
path: Cow<'static, str>,
caching: Option<Caching>,
}
impl Route for StaticFilesRoute {
fn validate_requirements(&self, _params: &ParamsNames, _data: &Resources) {}
fn path(&self) -> RoutePath {
RoutePath {
method: Some(Method::GET),
path: format!("{}/{{*rem}}", self.uri).into(),
}
}
fn call<'a>(
&'a self,
req: &'a mut Request,
_params: &'a PathParams,
_: &'a Resources,
) -> PinnedFuture<'a, crate::Result<Response>> {
let uri = &self.uri;
let caching = self.caching.clone();
PinnedFuture::new(async move {
let res_path_buf =
req.header().uri().path()[uri.len()..].into_path_buf();
let path_buf = res_path_buf
.map_err(|e| Error::new(ClientErrorKind::BadRequest, e))?;
let path_buf = Path::new(&*self.path).join(path_buf);
serve_file(path_buf, &req, caching)
.await
.map_err(Error::from_client_io)
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StaticFile {
uri: &'static str,
path: &'static str,
caching: CachingBuilder,
}
impl StaticFile {
pub const fn new(uri: &'static str, path: &'static str) -> Self {
Self {
uri,
path,
caching: CachingBuilder::Default,
}
}
pub const fn no_cache(uri: &'static str, path: &'static str) -> Self {
Self {
uri,
path,
caching: CachingBuilder::None,
}
}
pub const fn cache_with_age(
uri: &'static str,
path: &'static str,
max_age: Duration,
) -> Self {
Self {
uri,
path,
caching: CachingBuilder::MaxAge(max_age),
}
}
}
impl IntoRoute for StaticFile {
type IntoRoute = StaticFileRoute;
fn into_route(self) -> StaticFileRoute {
StaticFileRoute {
uri: self.uri.trim_end_matches('/').into(),
path: self.path.into(),
caching: self.caching.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticFileOwned {
uri: String,
path: String,
caching: CachingBuilder,
}
impl StaticFileOwned {
pub const fn new(uri: String, path: String) -> Self {
Self {
uri,
path,
caching: CachingBuilder::Default,
}
}
pub const fn no_cache(uri: String, path: String) -> Self {
Self {
uri,
path,
caching: CachingBuilder::None,
}
}
pub const fn cache_with_age(
uri: String,
path: String,
max_age: Duration,
) -> Self {
Self {
uri,
path,
caching: CachingBuilder::MaxAge(max_age),
}
}
}
impl IntoRoute for StaticFileOwned {
type IntoRoute = StaticFileRoute;
fn into_route(self) -> StaticFileRoute {
StaticFileRoute {
uri: self.uri.into(),
path: self.path.into(),
caching: self.caching.into(),
}
}
}
#[doc(hidden)]
pub struct StaticFileRoute {
uri: Cow<'static, str>,
path: Cow<'static, str>,
caching: Option<Caching>,
}
impl Route for StaticFileRoute {
fn validate_requirements(&self, _params: &ParamsNames, _data: &Resources) {}
fn path(&self) -> RoutePath {
RoutePath {
method: Some(Method::GET),
path: self.uri.clone(),
}
}
fn call<'a>(
&'a self,
req: &'a mut Request,
_params: &'a PathParams,
_data: &'a Resources,
) -> PinnedFuture<'a, crate::Result<Response>> {
PinnedFuture::new(async move {
serve_file(&*self.path, &req, self.caching.clone())
.await
.map_err(Error::from_client_io)
})
}
}