From a8b0af6295d0b966ff58c6e1ec68e8cd0ece0211 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Sun, 9 May 2021 16:37:54 +0200 Subject: [PATCH] feat: default cache-control headers for web assets --- src/control_headers.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/handler.rs | 13 +++++++++++-- src/lib.rs | 1 + 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/control_headers.rs diff --git a/src/control_headers.rs b/src/control_headers.rs new file mode 100644 index 0000000..4641583 --- /dev/null +++ b/src/control_headers.rs @@ -0,0 +1,41 @@ +// An arbitrary `Cache-Control` headers functionality for incoming requests based on a set of file types. +// Note: Since it's a ad-hoc feature it could be subject to change. +// See https://github.com/joseluisq/static-web-server/issues/30 + +use headers::{CacheControl, HeaderMapExt}; +use hyper::{Body, Response}; + +const CACHE_EXT_ONE_HOUR: [&str; 4] = ["atom", "json", "rss", "xml"]; +const CACHE_EXT_ONE_YEAR: [&str; 30] = [ + "bmp", "bz2", "css", "doc", "gif", "gz", "htc", "ico", "jpeg", "jpg", "js", "map", "mjs", + "mp3", "mp4", "ogg", "ogv", "pdf", "png", "rar", "rtf", "tar", "tgz", "wav", "weba", "webm", + "webp", "woff", "woff2", "zip", +]; + +/// It appends a `Cache-Control` header to a response if that one is part of a set of file types. +pub fn with_cache_control(ext: &str, resp: &mut Response) { + // Default max-age value in seconds (one day) + let mut max_age = 60 * 60 * 24_u64; + + if CACHE_EXT_ONE_HOUR + .iter() + .any(|x| ext.ends_with(&[".", *x].concat())) + { + max_age = 60 * 60; + } else if CACHE_EXT_ONE_YEAR + .iter() + .any(|x| ext.ends_with(&[".", *x].concat())) + { + max_age = 60 * 60 * 24 * 365; + } + + let cache_control = CacheControl::new() + .with_public() + .with_max_age(duration_from_secs(max_age)); + resp.headers_mut().typed_insert(cache_control); +} + +/// It caps a duration value at ~136 years. +fn duration_from_secs(secs: u64) -> std::time::Duration { + std::time::Duration::from_secs(std::cmp::min(secs, u32::MAX as u64)) +} diff --git a/src/handler.rs b/src/handler.rs index 7ab7c2d..ad65b19 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,7 +1,7 @@ use hyper::{Body, Request, Response}; use std::path::Path; -use crate::{compression, static_files}; +use crate::{compression, control_headers, static_files}; use crate::{error::Result, error_page}; /// Main server request handler. @@ -10,7 +10,16 @@ pub async fn handle_request(base: &Path, req: &Request) -> Result compression::auto(method, headers, resp), + Ok(resp) => { + // Compression on demand based on`Accept-Encoding` header + let mut resp = compression::auto(method, headers, resp)?; + + // Append `Cache-Control` headers for web assets + let ext = req.uri().path().to_lowercase(); + control_headers::with_cache_control(&ext, &mut resp); + + Ok(resp) + } Err(status) => error_page::get_error_response(method, &status), } } diff --git a/src/lib.rs b/src/lib.rs index 3ccd4e5..fa591ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ extern crate anyhow; pub mod compression; pub mod config; +pub mod control_headers; pub mod error_page; pub mod handler; pub mod helpers; -- libgit2 1.7.2