LibWeb/Fetch: Add freshness notion to HTTP::Response

Defined in RFC9111 "HTTP Caching".

Co-Authored-By: Andrew Kaster <akaster@serenityos.org>
This commit is contained in:
Lucas CHOLLET 2023-03-23 22:16:15 -04:00 committed by Tim Flynn
parent c05f296014
commit fcbc6123bb
Notes: sideshowbarker 2024-07-17 20:22:04 +09:00
2 changed files with 113 additions and 0 deletions

View file

@ -25,6 +25,7 @@ JS_DEFINE_ALLOCATOR(OpaqueRedirectFilteredResponse);
Response::Response(JS::NonnullGCPtr<HeaderList> header_list)
: m_header_list(header_list)
, m_response_time(UnixDateTime::now())
{
}
@ -207,6 +208,106 @@ bool Response::is_cors_cross_origin() const
return type() == Type::Opaque || type() == Type::OpaqueRedirect;
}
// https://fetch.spec.whatwg.org/#concept-fresh-response
bool Response::is_fresh() const
{
// A fresh response is a response whose current age is within its freshness lifetime.
return current_age() < freshness_lifetime();
}
// https://fetch.spec.whatwg.org/#concept-stale-while-revalidate-response
bool Response::is_stale_while_revalidate() const
{
// A stale-while-revalidate response is a response that is not a fresh response and whose current age is within the stale-while-revalidate lifetime.
return !is_fresh() && current_age() < stale_while_revalidate_lifetime();
}
// https://fetch.spec.whatwg.org/#concept-stale-response
bool Response::is_stale() const
{
// A stale response is a response that is not a fresh response or a stale-while-revalidate response.
return !is_fresh() && !is_stale_while_revalidate();
}
// https://httpwg.org/specs/rfc9111.html#age.calculations
u64 Response::current_age() const
{
// The term "age_value" denotes the value of the Age header field (Section 5.1), in a form appropriate for arithmetic operation; or 0, if not available.
Optional<Duration> age;
if (auto const age_header = header_list()->get("Age"sv.bytes()); age_header.has_value()) {
if (auto converted_age = StringView { *age_header }.to_number<u64>(); converted_age.has_value())
age = Duration::from_seconds(converted_age.value());
}
auto const age_value = age.value_or(Duration::from_seconds(0));
// The term "date_value" denotes the value of the Date header field, in a form appropriate for arithmetic operations. See Section 6.6.1 of [HTTP] for the definition of the Date header field and for requirements regarding responses without it.
// FIXME: Do we have a parser for HTTP-date?
auto const date_value = UnixDateTime::now() - Duration::from_seconds(5);
// The term "now" means the current value of this implementation's clock (Section 5.6.7 of [HTTP]).
auto const now = UnixDateTime::now();
// The value of the clock at the time of the request that resulted in the stored response.
// FIXME: Let's get the correct time.
auto const request_time = UnixDateTime::now() - Duration::from_seconds(5);
// The value of the clock at the time the response was received.
auto const response_time = m_response_time;
auto const apparent_age = max(0, (response_time - date_value).to_seconds());
auto const response_delay = response_time - request_time;
auto const corrected_age_value = age_value + response_delay;
auto const corrected_initial_age = max(apparent_age, corrected_age_value.to_seconds());
auto const resident_time = (now - response_time).to_seconds();
return corrected_initial_age + resident_time;
}
// https://httpwg.org/specs/rfc9111.html#calculating.freshness.lifetime
u64 Response::freshness_lifetime() const
{
auto const elem = header_list()->get_decode_and_split("Cache-Control"sv.bytes());
if (!elem.has_value())
return 0;
// FIXME: If the cache is shared and the s-maxage response directive (Section 5.2.2.10) is present, use its value
// If the max-age response directive (Section 5.2.2.1) is present, use its value, or
for (auto const& directive : *elem) {
if (directive.starts_with_bytes("max-age"sv)) {
auto equal_offset = directive.find_byte_offset('=').value();
auto const value = directive.bytes_as_string_view().substring_view(equal_offset);
return value.to_number<u64>().value();
}
}
// FIXME: If the Expires response header field (Section 5.3) is present, use its value minus the value of the Date response header field (using the time the message was received if it is not present, as per Section 6.6.1 of [HTTP]), or
// FIXME: Otherwise, no explicit expiration time is present in the response. A heuristic freshness lifetime might be applicable; see Section 4.2.2.
return 0;
}
// https://httpwg.org/specs/rfc5861.html#n-the-stale-while-revalidate-cache-control-extension
u64 Response::stale_while_revalidate_lifetime() const
{
auto const elem = header_list()->get_decode_and_split("Cache-Control"sv.bytes());
if (!elem.has_value())
return 0;
for (auto const& directive : *elem) {
if (directive.starts_with_bytes("stale-while-revalidate"sv)) {
auto equal_offset = directive.find_byte_offset('=').value();
auto const value = directive.bytes_as_string_view().substring_view(equal_offset);
return value.to_number<u64>().value();
}
}
return 0;
}
// Non-standard
Optional<StringView> Response::network_error_message() const
{