Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Allow specifying a cache-control header for serving static files. #2264

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions core/lib/src/fs/named_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<CacheControlOptions>);

impl NamedFile {
/// Attempts to open a file in read-only mode.
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
35 changes: 30 additions & 5 deletions core/lib/src/fs/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -64,6 +64,7 @@ use crate::fs::NamedFile;
pub struct FileServer {
root: PathBuf,
options: Options,
cache_control: Option<CacheControlOptions>,
rank: isize,
}

Expand Down Expand Up @@ -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`.
Expand All @@ -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<FileServer> for Vec<Route> {
Expand All @@ -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);
Expand Down Expand Up @@ -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),
}
}
Expand Down