Skip to content

Commit c55705b

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 0607ed2 commit c55705b

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,
@@ -198,7 +201,11 @@ struct RustdocPage {
198201
latest_version: String,
199202
target: String,
200203
inner_path: String,
204+
// true if we are displaying the latest version of the crate, regardless
205+
// of whether the URL specifies a version number or the string "latest."
201206
is_latest_version: bool,
207+
// true if the URL specifies a version using the string "latest."
208+
is_latest_url: bool,
202209
is_prerelease: bool,
203210
krate: CrateDetails,
204211
metadata: MetaData,
@@ -224,6 +231,7 @@ impl RustdocPage {
224231
.get::<crate::Metrics>()
225232
.expect("missing Metrics from the request extensions");
226233

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

246254
let mut response = Response::with((Status::Ok, html));
247255
response.headers.set(ContentType::html());
248-
256+
if is_latest_url {
257+
response
258+
.headers
259+
.set(CacheControl(vec![CacheDirective::MaxAge(0)]));
260+
} else {
261+
response.headers.set(CacheControl(vec![
262+
CacheDirective::Extension(
263+
"stale-while-revalidate".to_string(),
264+
Some("2592000".to_string()), // sixty days
265+
),
266+
CacheDirective::MaxAge(600u32), // ten minutes
267+
]));
268+
}
249269
Ok(response)
250270
}
251271
}
@@ -492,6 +512,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
492512
target,
493513
inner_path,
494514
is_latest_version,
515+
is_latest_url: version_or_latest == "latest",
495516
is_prerelease,
496517
metadata: krate.metadata.clone(),
497518
krate,
@@ -831,6 +852,28 @@ mod test {
831852
})
832853
}
833854

855+
#[test]
856+
fn cache_headers() {
857+
wrapper(|env| {
858+
env.fake_release()
859+
.name("dummy")
860+
.version("0.1.0")
861+
.archive_storage(true)
862+
.rustdoc_file("dummy/index.html")
863+
.create()?;
864+
865+
let resp = env.frontend().get("/dummy/latest/dummy/").send()?;
866+
assert_eq!(resp.headers().get("Cache-Control").unwrap(), &"max-age=0");
867+
868+
let resp = env.frontend().get("/dummy/0.1.0/dummy/").send()?;
869+
assert_eq!(
870+
resp.headers().get("Cache-Control").unwrap(),
871+
&"stale-while-revalidate=2592000, max-age=600"
872+
);
873+
Ok(())
874+
})
875+
}
876+
834877
#[test_case(true)]
835878
#[test_case(false)]
836879
fn go_to_latest_version(archive_storage: bool) {

0 commit comments

Comments
 (0)