RequestServer: Add a hook to advance a request's clock time for testing

For example, we will want to be able to test that a cached object was
expired after N seconds. Rather than waiting that time during testing,
this adds a testing-only request header to internally advance the clock
for a single HTTP request.
This commit is contained in:
Timothy Flynn 2025-11-18 10:36:55 -05:00 committed by Jelle Raaijmakers
parent b2c112c41a
commit 4de3f77d37
Notes: github-actions[bot] 2025-11-20 08:35:34 +00:00
7 changed files with 50 additions and 22 deletions

View file

@ -6,6 +6,7 @@
#include <LibCrypto/Hash/SHA1.h>
#include <LibURL/URL.h>
#include <RequestServer/Cache/DiskCache.h>
#include <RequestServer/Cache/Utilities.h>
namespace RequestServer {
@ -207,11 +208,12 @@ bool is_header_exempted_from_storage(StringView name)
// AD-HOC: Exclude headers used only for testing.
TEST_CACHE_ENABLED_HEADER,
TEST_CACHE_STATUS_HEADER);
TEST_CACHE_STATUS_HEADER,
TEST_CACHE_REQUEST_TIME_OFFSET);
}
// https://httpwg.org/specs/rfc9111.html#heuristic.freshness
static AK::Duration calculate_heuristic_freshness_lifetime(HTTP::HeaderMap const& headers)
static AK::Duration calculate_heuristic_freshness_lifetime(HTTP::HeaderMap const& headers, AK::Duration current_time_offset_for_testing)
{
// Since origin servers do not always provide explicit expiration times, a cache MAY assign a heuristic expiration
// time when an explicit time is not specified, employing algorithms that use other field values (such as the
@ -230,7 +232,7 @@ static AK::Duration calculate_heuristic_freshness_lifetime(HTTP::HeaderMap const
if (!last_modified.has_value())
return {};
auto now = UnixDateTime::now();
auto now = UnixDateTime::now() + current_time_offset_for_testing;
auto since_last_modified = now - *last_modified;
auto seconds = since_last_modified.to_seconds();
@ -243,7 +245,7 @@ static AK::Duration calculate_heuristic_freshness_lifetime(HTTP::HeaderMap const
}
// https://httpwg.org/specs/rfc9111.html#calculating.freshness.lifetime
AK::Duration calculate_freshness_lifetime(u32 status_code, HTTP::HeaderMap const& headers)
AK::Duration calculate_freshness_lifetime(u32 status_code, HTTP::HeaderMap const& headers, AK::Duration current_time_offset_for_testing)
{
// A cache can calculate the freshness lifetime (denoted as freshness_lifetime) of a response by evaluating the
// following rules and using the first match:
@ -265,8 +267,8 @@ AK::Duration calculate_freshness_lifetime(u32 status_code, HTTP::HeaderMap const
// * 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
if (auto expires = parse_http_date(headers.get("Expires"sv)); expires.has_value()) {
auto date = parse_http_date(headers.get("Date"sv)).value_or_lazy_evaluated([]() {
return UnixDateTime::now();
auto date = parse_http_date(headers.get("Date"sv)).value_or_lazy_evaluated([&]() {
return UnixDateTime::now() + current_time_offset_for_testing;
});
return *expires - date;
@ -287,14 +289,14 @@ AK::Duration calculate_freshness_lifetime(u32 status_code, HTTP::HeaderMap const
}
if (heuristics_allowed)
return calculate_heuristic_freshness_lifetime(headers);
return calculate_heuristic_freshness_lifetime(headers, current_time_offset_for_testing);
// No explicit expiration time, and heuristics not allowed or not applicable.
return {};
}
// https://httpwg.org/specs/rfc9111.html#age.calculations
AK::Duration calculate_age(HTTP::HeaderMap const& headers, UnixDateTime request_time, UnixDateTime response_time)
AK::Duration calculate_age(HTTP::HeaderMap const& headers, UnixDateTime request_time, UnixDateTime response_time, AK::Duration current_time_offset_for_testing)
{
// 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.
@ -306,7 +308,7 @@ AK::Duration calculate_age(HTTP::HeaderMap const& headers, UnixDateTime request_
}
// The term "now" means the current value of this implementation's clock (Section 5.6.7 of [HTTP]).
auto now = UnixDateTime::now();
auto now = UnixDateTime::now() + current_time_offset_for_testing;
// 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
@ -406,4 +408,16 @@ void update_header_fields(HTTP::HeaderMap& stored_headers, HTTP::HeaderMap const
}
}
AK::Duration compute_current_time_offset_for_testing(Optional<DiskCache&> disk_cache, HTTP::HeaderMap const& request_headers)
{
if (disk_cache.has_value() && disk_cache->mode() == DiskCache::Mode::Testing) {
if (auto header = request_headers.get(TEST_CACHE_REQUEST_TIME_OFFSET); header.has_value()) {
if (auto offset = header->to_number<i64>(); offset.has_value())
return AK::Duration::from_seconds(*offset);
}
}
return {};
}
}