Skip to content

Commit a854363

Browse files
committed
Add Cache-Control to rustdoc pages
For /latest/ URLs, set max-age=0. For versioned URLs max-age=10 minutes and stale-while-revalidate=2 months. The idea behind this is that versioned URLs change mostly in minor ways - the "Go to latest" link at the top, and the list of versions in the crate menu. And setting a long cache time (either via max-age or via stale-while-revalidate) allows pages to be loaded even while offline. We could probably apply a long stale-while-revalidate to /latest/ URLs as well, but this is more likely to have a user-noticeable impact, and the /latest/ URLs are relatively new so we don't want to create any confusing interactions.
1 parent 1ce3fd8 commit a854363

File tree

1 file changed

+45
-2
lines changed

1 file changed

+45
-2
lines changed

src/web/rustdoc.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ use crate::{
1111
Config, Metrics, Storage,
1212
};
1313
use anyhow::{anyhow, Context};
14-
use iron::url::percent_encoding::percent_decode;
14+
use iron::{
15+
headers::{CacheControl, CacheDirective},
16+
url::percent_encoding::percent_decode,
17+
};
1518
use iron::{
1619
headers::{Expires, HttpDate},
1720
modifiers::Redirect,
@@ -199,7 +202,11 @@ struct RustdocPage {
199202
latest_version: String,
200203
target: String,
201204
inner_path: String,
205+
// true if we are displaying the latest version of the crate, regardless
206+
// of whether the URL specifies a version number or the string "latest."
202207
is_latest_version: bool,
208+
// true if the URL specifies a version using the string "latest."
209+
is_latest_url: bool,
203210
is_prerelease: bool,
204211
krate: CrateDetails,
205212
metadata: MetaData,
@@ -225,6 +232,7 @@ impl RustdocPage {
225232
.get::<crate::Metrics>()
226233
.expect("missing Metrics from the request extensions");
227234

235+
let is_latest_url = self.is_latest_url;
228236
// Build the page of documentation
229237
let ctx = ctry!(req, tera::Context::from_serialize(self));
230238
// Extract the head and body of the rustdoc file so that we can insert it into our own html
@@ -246,7 +254,19 @@ impl RustdocPage {
246254

247255
let mut response = Response::with((Status::Ok, html));
248256
response.headers.set(ContentType::html());
249-
257+
if is_latest_url {
258+
response
259+
.headers
260+
.set(CacheControl(vec![CacheDirective::MaxAge(0)]));
261+
} else {
262+
response.headers.set(CacheControl(vec![
263+
CacheDirective::Extension(
264+
"stale-while-revalidate".to_string(),
265+
Some("2592000".to_string()), // sixty days
266+
),
267+
CacheDirective::MaxAge(600u32), // ten minutes
268+
]));
269+
}
250270
Ok(response)
251271
}
252272
}
@@ -501,6 +521,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
501521
target,
502522
inner_path,
503523
is_latest_version,
524+
is_latest_url: version_or_latest == "latest",
504525
is_prerelease,
505526
metadata: krate.metadata.clone(),
506527
krate,
@@ -840,6 +861,28 @@ mod test {
840861
})
841862
}
842863

864+
#[test]
865+
fn cache_headers() {
866+
wrapper(|env| {
867+
env.fake_release()
868+
.name("dummy")
869+
.version("0.1.0")
870+
.archive_storage(true)
871+
.rustdoc_file("dummy/index.html")
872+
.create()?;
873+
874+
let resp = env.frontend().get("/dummy/latest/dummy/").send()?;
875+
assert_eq!(resp.headers().get("Cache-Control").unwrap(), &"max-age=0");
876+
877+
let resp = env.frontend().get("/dummy/0.1.0/dummy/").send()?;
878+
assert_eq!(
879+
resp.headers().get("Cache-Control").unwrap(),
880+
&"stale-while-revalidate=2592000, max-age=600"
881+
);
882+
Ok(())
883+
})
884+
}
885+
843886
#[test_case(true)]
844887
#[test_case(false)]
845888
fn go_to_latest_version(archive_storage: bool) {

0 commit comments

Comments
 (0)