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

Add Cache-Control to rustdoc pages #1569

Merged
merged 2 commits into from
Sep 7, 2022
Merged
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
11 changes: 11 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ pub struct Config {
// Content Security Policy
pub(crate) csp_report_only: bool,

// Cache-Control header
// If both are absent, don't generate the header. If only one is present,
// generate just that directive. Values are in seconds.
pub(crate) cache_control_stale_while_revalidate: Option<u32>,
pub(crate) cache_control_max_age: Option<u32>,

// Build params
pub(crate) build_attempts: u16,
pub(crate) rustwide_workspace: PathBuf,
Expand Down Expand Up @@ -130,6 +136,11 @@ impl Config {

csp_report_only: env("DOCSRS_CSP_REPORT_ONLY", false)?,

cache_control_stale_while_revalidate: maybe_env(
"CACHE_CONTROL_STALE_WHILE_REVALIDATE",
)?,
cache_control_max_age: maybe_env("CACHE_CONTROL_MAX_AGE")?,

local_archive_cache_path: env(
"DOCSRS_ARCHIVE_INDEX_CACHE_PATH",
prefix.join("archive_cache"),
Expand Down
59 changes: 57 additions & 2 deletions src/web/rustdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use crate::{
Config, Metrics, Storage,
};
use anyhow::{anyhow, Context};
use iron::url::percent_encoding::percent_decode;
use iron::{
headers::{CacheControl, CacheDirective},
url::percent_encoding::percent_decode,
};
use iron::{
headers::{Expires, HttpDate},
modifiers::Redirect,
Expand Down Expand Up @@ -199,7 +202,11 @@ struct RustdocPage {
latest_version: String,
target: String,
inner_path: String,
// true if we are displaying the latest version of the crate, regardless
// of whether the URL specifies a version number or the string "latest."
is_latest_version: bool,
// true if the URL specifies a version using the string "latest."
is_latest_url: bool,
is_prerelease: bool,
krate: CrateDetails,
metadata: MetaData,
Expand All @@ -225,15 +232,16 @@ impl RustdocPage {
.get::<crate::Metrics>()
.expect("missing Metrics from the request extensions");

let is_latest_url = self.is_latest_url;
// Build the page of documentation
let ctx = ctry!(req, tera::Context::from_serialize(self));
let config = extension!(req, Config);
// Extract the head and body of the rustdoc file so that we can insert it into our own html
// while logging OOM errors from html rewriting
let html = match utils::rewrite_lol(rustdoc_html, max_parse_memory, ctx, templates) {
Err(RewritingError::MemoryLimitExceeded(..)) => {
metrics.html_rewrite_ooms.inc();

let config = extension!(req, Config);
let err = anyhow!(
"Failed to serve the rustdoc file '{}' because rewriting it surpassed the memory limit of {} bytes",
file_path, config.max_parse_memory,
Expand All @@ -246,7 +254,27 @@ impl RustdocPage {

let mut response = Response::with((Status::Ok, html));
response.headers.set(ContentType::html());
if is_latest_url {
response
.headers
.set(CacheControl(vec![CacheDirective::MaxAge(0)]));
} else {
let mut directives = vec![];
if let Some(seconds) = config.cache_control_stale_while_revalidate {
directives.push(CacheDirective::Extension(
"stale-while-revalidate".to_string(),
Some(format!("{}", seconds)),
));
}

if let Some(seconds) = config.cache_control_max_age {
directives.push(CacheDirective::MaxAge(seconds));
}

if !directives.is_empty() {
response.headers.set(CacheControl(directives));
}
}
Ok(response)
}
}
Expand Down Expand Up @@ -501,6 +529,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
target,
inner_path,
is_latest_version,
is_latest_url: version_or_latest == "latest",
is_prerelease,
metadata: krate.metadata.clone(),
krate,
Expand Down Expand Up @@ -840,6 +869,32 @@ mod test {
})
}

#[test]
fn cache_headers() {
wrapper(|env| {
env.override_config(|config| {
config.cache_control_max_age = Some(600);
config.cache_control_stale_while_revalidate = Some(2592000);
});

env.fake_release()
.name("dummy")
.version("0.1.0")
.archive_storage(true)
.rustdoc_file("dummy/index.html")
.create()?;
let resp = env.frontend().get("/dummy/latest/dummy/").send()?;
assert_eq!(resp.headers().get("Cache-Control").unwrap(), &"max-age=0");

let resp = env.frontend().get("/dummy/0.1.0/dummy/").send()?;
assert_eq!(
resp.headers().get("Cache-Control").unwrap(),
&"stale-while-revalidate=2592000, max-age=600"
);
Ok(())
})
}

#[test_case(true)]
#[test_case(false)]
fn go_to_latest_version(archive_storage: bool) {
Expand Down