feat: implements remaining http cache conditionalss (#407)
* implements remaining http conditionals * computed etag is not optional
This commit is contained in:
parent
f1e90686dc
commit
632f7a41bf
2 changed files with 104 additions and 13 deletions
|
@ -15,8 +15,8 @@ use chrono::{LocalResult, TimeZone, Utc};
|
||||||
use futures_util::{pin_mut, TryStreamExt};
|
use futures_util::{pin_mut, TryStreamExt};
|
||||||
use headers::{
|
use headers::{
|
||||||
AcceptRanges, AccessControlAllowCredentials, AccessControlAllowOrigin, CacheControl,
|
AcceptRanges, AccessControlAllowCredentials, AccessControlAllowOrigin, CacheControl,
|
||||||
ContentLength, ContentType, ETag, HeaderMap, HeaderMapExt, IfModifiedSince, IfNoneMatch,
|
ContentLength, ContentType, ETag, HeaderMap, HeaderMapExt, IfMatch, IfModifiedSince,
|
||||||
IfRange, LastModified, Range,
|
IfNoneMatch, IfRange, IfUnmodifiedSince, LastModified, Range,
|
||||||
};
|
};
|
||||||
use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
|
use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
|
||||||
use hyper::body::Frame;
|
use hyper::body::Frame;
|
||||||
|
@ -796,18 +796,29 @@ impl Server {
|
||||||
let size = meta.len();
|
let size = meta.len();
|
||||||
let mut use_range = true;
|
let mut use_range = true;
|
||||||
if let Some((etag, last_modified)) = extract_cache_headers(&meta) {
|
if let Some((etag, last_modified)) = extract_cache_headers(&meta) {
|
||||||
let cached = {
|
if let Some(if_unmodified_since) = headers.typed_get::<IfUnmodifiedSince>() {
|
||||||
if let Some(if_none_match) = headers.typed_get::<IfNoneMatch>() {
|
if !if_unmodified_since.precondition_passes(last_modified.into()) {
|
||||||
!if_none_match.precondition_passes(&etag)
|
*res.status_mut() = StatusCode::PRECONDITION_FAILED;
|
||||||
} else if let Some(if_modified_since) = headers.typed_get::<IfModifiedSince>() {
|
return Ok(());
|
||||||
!if_modified_since.is_modified(last_modified.into())
|
}
|
||||||
} else {
|
}
|
||||||
false
|
if let Some(if_match) = headers.typed_get::<IfMatch>() {
|
||||||
|
if !if_match.precondition_passes(&etag) {
|
||||||
|
*res.status_mut() = StatusCode::PRECONDITION_FAILED;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(if_modified_since) = headers.typed_get::<IfModifiedSince>() {
|
||||||
|
if !if_modified_since.is_modified(last_modified.into()) {
|
||||||
|
*res.status_mut() = StatusCode::NOT_MODIFIED;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(if_none_match) = headers.typed_get::<IfNoneMatch>() {
|
||||||
|
if !if_none_match.precondition_passes(&etag) {
|
||||||
|
*res.status_mut() = StatusCode::NOT_MODIFIED;
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if cached {
|
|
||||||
*res.status_mut() = StatusCode::NOT_MODIFIED;
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.headers_mut().typed_insert(last_modified);
|
res.headers_mut().typed_insert(last_modified);
|
||||||
|
|
80
tests/cache.rs
Normal file
80
tests/cache.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
mod fixtures;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Duration};
|
||||||
|
use fixtures::{server, Error, TestServer};
|
||||||
|
use reqwest::header::{
|
||||||
|
HeaderName, ETAG, IF_MATCH, IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_UNMODIFIED_SINCE,
|
||||||
|
LAST_MODIFIED,
|
||||||
|
};
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(IF_UNMODIFIED_SINCE, Duration::days(1), StatusCode::OK)]
|
||||||
|
#[case(IF_UNMODIFIED_SINCE, Duration::days(0), StatusCode::OK)]
|
||||||
|
#[case(IF_UNMODIFIED_SINCE, Duration::days(-1), StatusCode::PRECONDITION_FAILED)]
|
||||||
|
#[case(IF_MODIFIED_SINCE, Duration::days(1), StatusCode::NOT_MODIFIED)]
|
||||||
|
#[case(IF_MODIFIED_SINCE, Duration::days(0), StatusCode::NOT_MODIFIED)]
|
||||||
|
#[case(IF_MODIFIED_SINCE, Duration::days(-1), StatusCode::OK)]
|
||||||
|
fn get_file_with_if_modified_since_condition(
|
||||||
|
#[case] header_condition: HeaderName,
|
||||||
|
#[case] duration_after_file_modified: Duration,
|
||||||
|
#[case] expected_code: StatusCode,
|
||||||
|
server: TestServer,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let resp = fetch!(b"HEAD", format!("{}index.html", server.url())).send()?;
|
||||||
|
|
||||||
|
let last_modified = resp
|
||||||
|
.headers()
|
||||||
|
.get(LAST_MODIFIED)
|
||||||
|
.and_then(|h| h.to_str().ok())
|
||||||
|
.and_then(|s| DateTime::parse_from_rfc2822(s).ok())
|
||||||
|
.expect("Recieved no valid last modified header");
|
||||||
|
|
||||||
|
let req_modified_time = (last_modified + duration_after_file_modified)
|
||||||
|
.format("%a, %e %b %Y %T GMT")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let resp = fetch!(b"GET", format!("{}index.html", server.url()))
|
||||||
|
.header(header_condition, req_modified_time)
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
assert_eq!(resp.status(), expected_code);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn same_etag(etag: &str) -> String {
|
||||||
|
etag.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn different_etag(etag: &str) -> String {
|
||||||
|
format!("{}1234", etag)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(IF_MATCH, same_etag, StatusCode::OK)]
|
||||||
|
#[case(IF_MATCH, different_etag, StatusCode::PRECONDITION_FAILED)]
|
||||||
|
#[case(IF_NONE_MATCH, same_etag, StatusCode::NOT_MODIFIED)]
|
||||||
|
#[case(IF_NONE_MATCH, different_etag, StatusCode::OK)]
|
||||||
|
fn get_file_with_etag_match(
|
||||||
|
#[case] header_condition: HeaderName,
|
||||||
|
#[case] etag_modifier: fn(&str) -> String,
|
||||||
|
#[case] expected_code: StatusCode,
|
||||||
|
server: TestServer,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let resp = fetch!(b"HEAD", format!("{}index.html", server.url())).send()?;
|
||||||
|
|
||||||
|
let etag = resp
|
||||||
|
.headers()
|
||||||
|
.get(ETAG)
|
||||||
|
.and_then(|h| h.to_str().ok())
|
||||||
|
.expect("Recieved no valid etag header");
|
||||||
|
|
||||||
|
let resp = fetch!(b"GET", format!("{}index.html", server.url()))
|
||||||
|
.header(header_condition, etag_modifier(etag))
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
assert_eq!(resp.status(), expected_code);
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue