-
Notifications
You must be signed in to change notification settings - Fork 209
new cache-policy & cache middleware structure to support full page caching #1856
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
Merged
+561
−246
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
b0c1f9a
new cache-policy & cache middleware structure to support full page ca…
syphar 2f39aff
fix doc comment for assert_redirect_cached
syphar 92a393b
change NoCache policy to just `max-age=0` to allow back-forward cache…
syphar 570e74b
set assert! message when misusing cache control
syphar af124e6
update comment wording
syphar 5853ffa
fix rustfmt error
syphar 3e3c257
rename CachePolicy::ForeverOnlyInCdn into ForeverInCdn
syphar cdedb65
revert permanent redirect to match current logic in rustdoc redirects
syphar a9a1802
Revert "revert permanent redirect to match current logic in rustdoc r…
syphar e5cdb8c
remove 'public' from cache-control headers
syphar 050b967
revert cache settings for invocation specific rustdoc assets
syphar e40a8f0
omit cache-control header when it would be empty
syphar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
use super::STATIC_FILE_CACHE_DURATION; | ||
use crate::config::Config; | ||
use iron::{ | ||
headers::{CacheControl, CacheDirective}, | ||
AfterMiddleware, IronResult, Request, Response, | ||
}; | ||
|
||
#[cfg(test)] | ||
pub const NO_CACHE: &str = "max-age=0"; | ||
|
||
/// defines the wanted caching behaviour for a web response. | ||
pub enum CachePolicy { | ||
/// no browser or CDN caching. | ||
/// In some cases the browser might still use cached content, | ||
/// for example when using the "back" button or when it can't | ||
/// connect to the server. | ||
NoCaching, | ||
/// don't cache, plus | ||
/// * enforce revalidation | ||
/// * never store | ||
NoStoreMustRevalidate, | ||
/// cache forever in browser & CDN. | ||
/// Valid when you have hashed / versioned filenames and every rebuild would | ||
/// change the filename. | ||
ForeverInCdnAndBrowser, | ||
/// cache forever in CDN, but not in the browser. | ||
/// Since we control the CDN we can actively purge content that is cached like | ||
/// this, for example after building a crate. | ||
/// Example usage: `/latest/` rustdoc pages and their redirects. | ||
jsha marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ForeverInCdn, | ||
/// cache forver in the CDN, but allow stale content in the browser. | ||
/// Example: rustdoc pages with the version in their URL. | ||
/// A browser will show the stale content while getting the up-to-date | ||
/// version from the origin server in the background. | ||
/// This helps building a PWA. | ||
ForeverInCdnAndStaleInBrowser, | ||
} | ||
|
||
impl CachePolicy { | ||
pub fn render(&self, config: &Config) -> Vec<CacheDirective> { | ||
match *self { | ||
CachePolicy::NoCaching => { | ||
vec![CacheDirective::MaxAge(0)] | ||
} | ||
CachePolicy::NoStoreMustRevalidate => { | ||
vec![ | ||
CacheDirective::NoCache, | ||
CacheDirective::NoStore, | ||
CacheDirective::MustRevalidate, | ||
CacheDirective::MaxAge(0), | ||
] | ||
} | ||
CachePolicy::ForeverInCdnAndBrowser => { | ||
vec![CacheDirective::MaxAge(STATIC_FILE_CACHE_DURATION as u32)] | ||
} | ||
CachePolicy::ForeverInCdn => { | ||
// A missing `max-age` or `s-maxage` in the Cache-Control header will lead to | ||
// CloudFront using the default TTL, while the browser not seeing any caching header. | ||
// This means we can have the CDN caching the documentation while just | ||
// issuing a purge after a build. | ||
// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#ExpirationDownloadDist | ||
vec![] | ||
} | ||
CachePolicy::ForeverInCdnAndStaleInBrowser => { | ||
let mut directives = CachePolicy::ForeverInCdn.render(config); | ||
if let Some(seconds) = config.cache_control_stale_while_revalidate { | ||
directives.push(CacheDirective::Extension( | ||
"stale-while-revalidate".to_string(), | ||
Some(seconds.to_string()), | ||
)); | ||
} | ||
directives | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl iron::typemap::Key for CachePolicy { | ||
type Value = CachePolicy; | ||
} | ||
|
||
/// Middleware to ensure a correct cache-control header. | ||
/// The default is an explicit "never cache" header, which | ||
/// can be adapted via: | ||
/// ```ignore | ||
/// resp.extensions.insert::<CachePolicy>(CachePolicy::ForeverInCdn); | ||
/// # change Cache::ForeverInCdn into the cache polity you want to have | ||
/// ``` | ||
/// in a handler function. | ||
pub(super) struct CacheMiddleware; | ||
|
||
impl AfterMiddleware for CacheMiddleware { | ||
fn after(&self, req: &mut Request, mut res: Response) -> IronResult<Response> { | ||
let config = req.extensions.get::<Config>().expect("missing config"); | ||
let cache = res | ||
.extensions | ||
.get::<CachePolicy>() | ||
.unwrap_or(&CachePolicy::NoCaching); | ||
|
||
if cfg!(test) { | ||
assert!( | ||
!res.headers.has::<CacheControl>(), | ||
"handlers should never set their own caching headers and only use CachePolicy to control caching." | ||
); | ||
} | ||
|
||
let directives = cache.render(config); | ||
if !directives.is_empty() { | ||
res.headers.set(CacheControl(directives)) | ||
} | ||
Ok(res) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::test::wrapper; | ||
use iron::headers::CacheControl; | ||
use test_case::test_case; | ||
|
||
#[test_case(CachePolicy::NoCaching, "max-age=0")] | ||
#[test_case( | ||
CachePolicy::NoStoreMustRevalidate, | ||
"no-cache, no-store, must-revalidate, max-age=0" | ||
)] | ||
#[test_case(CachePolicy::ForeverInCdnAndBrowser, "max-age=31104000")] | ||
#[test_case(CachePolicy::ForeverInCdn, "")] | ||
#[test_case( | ||
CachePolicy::ForeverInCdnAndStaleInBrowser, | ||
"stale-while-revalidate=86400" | ||
)] | ||
fn render(cache: CachePolicy, expected: &str) { | ||
wrapper(|env| { | ||
assert_eq!( | ||
CacheControl(cache.render(&env.config())).to_string(), | ||
expected | ||
); | ||
Ok(()) | ||
}); | ||
} | ||
|
||
#[test] | ||
fn render_stale_without_config() { | ||
wrapper(|env| { | ||
env.override_config(|config| config.cache_control_stale_while_revalidate = None); | ||
|
||
assert_eq!( | ||
CacheControl(CachePolicy::ForeverInCdnAndStaleInBrowser.render(&env.config())) | ||
.to_string(), | ||
"" | ||
); | ||
Ok(()) | ||
}); | ||
} | ||
#[test] | ||
fn render_stale_with_config() { | ||
wrapper(|env| { | ||
env.override_config(|config| { | ||
config.cache_control_stale_while_revalidate = Some(666); | ||
}); | ||
|
||
assert_eq!( | ||
CacheControl(CachePolicy::ForeverInCdnAndStaleInBrowser.render(&env.config())) | ||
.to_string(), | ||
"stale-while-revalidate=666" | ||
); | ||
Ok(()) | ||
}); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.