api: GetObject: implement if-match and if-unmodified-since
This commit is contained in:
parent
2191620af5
commit
1a8f74fc94
1 changed files with 41 additions and 16 deletions
|
@ -9,8 +9,8 @@ use futures::future;
|
||||||
use futures::stream::{self, Stream, StreamExt};
|
use futures::stream::{self, Stream, StreamExt};
|
||||||
use http::header::{
|
use http::header::{
|
||||||
ACCEPT_RANGES, CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE,
|
ACCEPT_RANGES, CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE,
|
||||||
CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE, ETAG, EXPIRES, IF_MODIFIED_SINCE, IF_NONE_MATCH,
|
CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE, ETAG, EXPIRES, IF_MATCH, IF_MODIFIED_SINCE,
|
||||||
LAST_MODIFIED, RANGE,
|
IF_NONE_MATCH, IF_UNMODIFIED_SINCE, LAST_MODIFIED, RANGE,
|
||||||
};
|
};
|
||||||
use hyper::{Request, Response, StatusCode};
|
use hyper::{Request, Response, StatusCode};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
@ -115,42 +115,67 @@ fn getobject_override_headers(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_answer_cached(
|
fn handle_http_precondition(
|
||||||
version: &ObjectVersion,
|
version: &ObjectVersion,
|
||||||
version_meta: &ObjectVersionMeta,
|
version_meta: &ObjectVersionMeta,
|
||||||
req: &Request<()>,
|
req: &Request<()>,
|
||||||
) -> Option<Response<ResBody>> {
|
) -> Result<Option<Response<ResBody>>, Error> {
|
||||||
|
if let Some(if_match) = req.headers().get(IF_MATCH) {
|
||||||
|
let if_match = if_match.to_str()?;
|
||||||
|
let expected = format!("\"{}\"", version_meta.etag);
|
||||||
|
let found = if_match
|
||||||
|
.split(',')
|
||||||
|
.map(str::trim)
|
||||||
|
.any(|etag| etag == expected || etag == "\"*\"");
|
||||||
|
if !found {
|
||||||
|
return Ok(Some(
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::PRECONDITION_FAILED)
|
||||||
|
.body(empty_body())
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// <trinity> It is possible, and is even usually the case, [that both If-None-Match and
|
// <trinity> It is possible, and is even usually the case, [that both If-None-Match and
|
||||||
// If-Modified-Since] are present in a request. In this situation If-None-Match takes
|
// If-Modified-Since] are present in a request. In this situation If-None-Match takes
|
||||||
// precedence and If-Modified-Since is ignored (as per 6.Precedence from rfc7232). The rational
|
// precedence and If-Modified-Since is ignored (as per 6.Precedence from rfc7232). The rational
|
||||||
// being that etag based matching is more accurate, it has no issue with sub-second precision
|
// being that etag based matching is more accurate, it has no issue with sub-second precision
|
||||||
// for instance (in case of very fast updates)
|
// for instance (in case of very fast updates)
|
||||||
|
let object_date = UNIX_EPOCH + Duration::from_millis(version.timestamp);
|
||||||
|
|
||||||
let cached = if let Some(none_match) = req.headers().get(IF_NONE_MATCH) {
|
let cached = if let Some(none_match) = req.headers().get(IF_NONE_MATCH) {
|
||||||
let none_match = none_match.to_str().ok()?;
|
let none_match = none_match.to_str()?;
|
||||||
let expected = format!("\"{}\"", version_meta.etag);
|
let expected = format!("\"{}\"", version_meta.etag);
|
||||||
let found = none_match
|
let found = none_match
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
.any(|etag| etag == expected || etag == "\"*\"");
|
.any(|etag| etag == expected || etag == "\"*\"");
|
||||||
found
|
found
|
||||||
|
} else if let Some(unmodified_since) = req.headers().get(IF_UNMODIFIED_SINCE) {
|
||||||
|
let unmodified_since = unmodified_since.to_str()?;
|
||||||
|
let unmodified_since =
|
||||||
|
httpdate::parse_http_date(unmodified_since).ok_or_bad_request("invalid http date")?;
|
||||||
|
object_date <= unmodified_since
|
||||||
} else if let Some(modified_since) = req.headers().get(IF_MODIFIED_SINCE) {
|
} else if let Some(modified_since) = req.headers().get(IF_MODIFIED_SINCE) {
|
||||||
let modified_since = modified_since.to_str().ok()?;
|
let modified_since = modified_since.to_str()?;
|
||||||
let client_date = httpdate::parse_http_date(modified_since).ok()?;
|
let modified_since =
|
||||||
let server_date = UNIX_EPOCH + Duration::from_millis(version.timestamp);
|
httpdate::parse_http_date(modified_since).ok_or_bad_request("invalid http date")?;
|
||||||
client_date >= server_date
|
let object_date = UNIX_EPOCH + Duration::from_millis(version.timestamp);
|
||||||
|
object_date > modified_since
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
if cached {
|
if cached {
|
||||||
Some(
|
Ok(Some(
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::NOT_MODIFIED)
|
.status(StatusCode::NOT_MODIFIED)
|
||||||
.body(empty_body())
|
.body(empty_body())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,8 +221,8 @@ pub async fn handle_head_without_ctx(
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(cached) = try_answer_cached(object_version, version_meta, req) {
|
if let Some(res) = handle_http_precondition(object_version, version_meta, req)? {
|
||||||
return Ok(cached);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (encryption, headers) =
|
let (encryption, headers) =
|
||||||
|
@ -318,8 +343,8 @@ pub async fn handle_get_without_ctx(
|
||||||
ObjectVersionData::FirstBlock(meta, _) => meta,
|
ObjectVersionData::FirstBlock(meta, _) => meta,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(cached) = try_answer_cached(last_v, last_v_meta, req) {
|
if let Some(res) = handle_http_precondition(last_v, last_v_meta, req)? {
|
||||||
return Ok(cached);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (enc, headers) =
|
let (enc, headers) =
|
||||||
|
|
Loading…
Add table
Reference in a new issue