diff --git a/core/lib/src/fs/named_file.rs b/core/lib/src/fs/named_file.rs index 8fd165f209..43a3f7cdc5 100644 --- a/core/lib/src/fs/named_file.rs +++ b/core/lib/src/fs/named_file.rs @@ -6,7 +6,11 @@ use tokio::fs::File; use crate::request::Request; use crate::response::{self, Responder}; -use crate::http::ContentType; +use crate::http::{self, ContentType, Header}; + +/// A Cache-Control header value. Check e.g. [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives) +/// for more information. +pub type CacheControlOptions = String; /// A [`Responder`] that sends file data with a Content-Type based on its /// file extension. @@ -37,7 +41,7 @@ use crate::http::ContentType; /// /// [`FileServer`]: crate::fs::FileServer #[derive(Debug)] -pub struct NamedFile(PathBuf, File); +pub struct NamedFile(PathBuf, File, Option); impl NamedFile { /// Attempts to open a file in read-only mode. @@ -65,7 +69,7 @@ impl NamedFile { // all of those `seek`s to determine the file size. But, what happens if // the file gets changed between now and then? let file = File::open(path.as_ref()).await?; - Ok(NamedFile(path.as_ref().to_path_buf(), file)) + Ok(NamedFile(path.as_ref().to_path_buf(), file, None)) } /// Retrieve the underlying `File`. @@ -139,6 +143,12 @@ impl NamedFile { pub fn path(&self) -> &Path { self.0.as_path() } + + /// Set cache-control information. + pub fn cache_control(mut self, cc: CacheControlOptions) -> Self { + self.2 = Some(cc); + self + } } /// Streams the named file to the client. Sets or overrides the Content-Type in @@ -153,6 +163,10 @@ impl<'r> Responder<'r, 'static> for NamedFile { if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) { response.set_header(ct); } + if let Some(cc) = self.2 { + let cch = Header::new(http::hyper::header::CACHE_CONTROL.as_str(), cc); + response.set_header(cch); + } } Ok(response) diff --git a/core/lib/src/fs/server.rs b/core/lib/src/fs/server.rs index e806a932a7..09b39de421 100644 --- a/core/lib/src/fs/server.rs +++ b/core/lib/src/fs/server.rs @@ -4,7 +4,7 @@ use crate::{Request, Data}; use crate::http::{Method, uri::Segments, ext::IntoOwned}; use crate::route::{Route, Handler, Outcome}; use crate::response::Redirect; -use crate::fs::NamedFile; +use crate::fs::{CacheControlOptions, NamedFile}; /// Custom handler for serving static files. /// @@ -64,6 +64,7 @@ use crate::fs::NamedFile; pub struct FileServer { root: PathBuf, options: Options, + cache_control: Option, rank: isize, } @@ -159,7 +160,7 @@ impl FileServer { } } - FileServer { root: path.into(), options, rank: Self::DEFAULT_RANK } + FileServer { root: path.into(), options, rank: Self::DEFAULT_RANK, cache_control: None } } /// Sets the rank for generated routes to `rank`. @@ -179,6 +180,18 @@ impl FileServer { self.rank = rank; self } + + /// Set cache-control header value to serve files with. + /// + /// ```rust,no_run + /// use rocket::fs::{FileServer, CacheControlOptions}; + /// + /// FileServer::new("/public").cache_control("max-age=86400".to_string()); + /// ``` + pub fn cache_control(mut self, cc: CacheControlOptions) -> Self { + self.cache_control = Some(cc); + self + } } impl From for Vec { @@ -204,7 +217,10 @@ impl Handler for FileServer { }; if segments.is_empty() { - let file = NamedFile::open(&self.root).await.ok(); + let mut file = NamedFile::open(&self.root).await.ok(); + if let Some(ref cc) = self.cache_control { + file = file.map(|f| f.cache_control(cc.clone())); + } return Outcome::from_or_forward(req, data, file); } else { return Outcome::forward(data); @@ -232,10 +248,19 @@ impl Handler for FileServer { return Outcome::forward(data); } - let index = NamedFile::open(p.join("index.html")).await.ok(); + let mut index = NamedFile::open(p.join("index.html")).await.ok(); + if let Some(ref cc) = self.cache_control { + index = index.map(|f| f.cache_control(cc.clone())); + } Outcome::from_or_forward(req, data, index) }, - Some(p) => Outcome::from_or_forward(req, data, NamedFile::open(p).await.ok()), + Some(p) => { + let mut file = NamedFile::open(p).await.ok(); + if let Some(ref cc) = self.cache_control { + file = file.map(|f| f.cache_control(cc.clone())); + } + Outcome::from_or_forward(req, data, file) + }, None => Outcome::forward(data), } }