Skip to content

Commit 2453f0b

Browse files
committed
LibHTTP+LibWeb: Use LibHTTP's cache implementation in LibWeb
There are a couple of remaining RFC 9111 methods in LibWeb's Fetch, but these are currently directly tied to the way we store GC-allocated HTTP response objects. So de-coupling that is left as a future exercise.
1 parent 21bbbac commit 2453f0b

File tree

6 files changed

+43
-257
lines changed

6 files changed

+43
-257
lines changed

Libraries/LibHTTP/Cache/Utilities.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,15 @@ RevalidationAttributes RevalidationAttributes::create(HeaderList const& headers)
374374
return attributes;
375375
}
376376

377+
// https://httpwg.org/specs/rfc9111.html#storing.fields
378+
void store_header_and_trailer_fields(HeaderList& stored_headers, HeaderList const& response_headers)
379+
{
380+
for (auto const& header : response_headers) {
381+
if (!is_header_exempted_from_storage(header.name))
382+
stored_headers.append(header);
383+
}
384+
}
385+
377386
// https://httpwg.org/specs/rfc9111.html#update
378387
void update_header_fields(HeaderList& stored_headers, HeaderList const& updated_headers)
379388
{

Libraries/LibHTTP/Cache/Utilities.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ bool is_cacheable(StringView method);
2828
bool is_cacheable(u32 status_code, HeaderList const&);
2929
bool is_header_exempted_from_storage(StringView name);
3030

31-
AK::Duration calculate_freshness_lifetime(u32 status_code, HeaderList const&, AK::Duration current_time_offset_for_testing);
32-
AK::Duration calculate_age(HeaderList const&, UnixDateTime request_time, UnixDateTime response_time, AK::Duration current_time_offset_for_testing);
31+
AK::Duration calculate_freshness_lifetime(u32 status_code, HeaderList const&, AK::Duration current_time_offset_for_testing = {});
32+
AK::Duration calculate_age(HeaderList const&, UnixDateTime request_time, UnixDateTime response_time, AK::Duration current_time_offset_for_testing = {});
3333

3434
enum class CacheLifetimeStatus {
3535
Fresh,
@@ -45,6 +45,7 @@ struct RevalidationAttributes {
4545
Optional<UnixDateTime> last_modified;
4646
};
4747

48+
void store_header_and_trailer_fields(HeaderList&, HeaderList const&);
4849
void update_header_fields(HeaderList&, HeaderList const&);
4950

5051
AK::Duration compute_current_time_offset_for_testing(Optional<DiskCache&>, HeaderList const& request_headers);

Libraries/LibWeb/DOM/Document.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ WebIDL::ExceptionOr<GC::Ref<Document>> Document::create_and_initialize(Type type
350350
DOM::DocumentLoadTimingInfo load_timing_info;
351351
// AD-HOC: The response object no longer has an associated timing info object. For now, we use response's non-standard response time property,
352352
// which represents the time that the time that the response object was created.
353-
auto response_creation_time = navigation_params.response->response_time().nanoseconds() / 1e6;
353+
auto response_creation_time = navigation_params.response->monotonic_response_time().nanoseconds() / 1e6;
354354
load_timing_info.navigation_start_time = HighResolutionTime::coarsen_time(response_creation_time, HTML::relevant_settings_object(*window).cross_origin_isolated_capability() == HTML::CanUseCrossOriginIsolatedAPIs::Yes);
355355

356356
// 9. Let document be a new Document, with

Libraries/LibWeb/Fetch/Fetching/Fetching.cpp

Lines changed: 10 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <AK/Base64.h>
1313
#include <AK/Debug.h>
1414
#include <AK/ScopeGuard.h>
15+
#include <LibHTTP/Cache/Utilities.h>
1516
#include <LibHTTP/Method.h>
1617
#include <LibJS/Runtime/Completion.h>
1718
#include <LibRequests/RequestTimingInfo.h>
@@ -1416,7 +1417,7 @@ GC::Ptr<PendingResponse> http_redirect_fetch(JS::Realm& realm, Infrastructure::F
14161417
class CachePartition : public RefCounted<CachePartition> {
14171418
public:
14181419
// https://httpwg.org/specs/rfc9111.html#constructing.responses.from.caches
1419-
GC::Ptr<Infrastructure::Response> select_response(JS::Realm& realm, URL::URL const& url, StringView method, HTTP::HeaderList const& headers, Vector<GC::Ptr<Infrastructure::Response>>& initial_set_of_stored_responses) const
1420+
GC::Ptr<Infrastructure::Response> select_response(JS::Realm& realm, URL::URL const& url, StringView method, HTTP::HeaderList const& headers) const
14201421
{
14211422
// When presented with a request, a cache MUST NOT reuse a stored response unless:
14221423

@@ -1439,8 +1440,6 @@ class CachePartition : public RefCounted<CachePartition> {
14391440

14401441
// FIXME: - the stored response does not contain the no-cache directive (Section 5.2.2.4), unless it is successfully validated (Section 4.3), and
14411442

1442-
initial_set_of_stored_responses.append(*cached_response);
1443-
14441443
// FIXME: - the stored response is one of the following:
14451444
// + fresh (see Section 4.2), or
14461445
// + allowed to be served stale (see Section 4.2.4), or
@@ -1452,190 +1451,23 @@ class CachePartition : public RefCounted<CachePartition> {
14521451

14531452
void store_response(JS::Realm& realm, Infrastructure::Request const& http_request, Infrastructure::Response const& response)
14541453
{
1455-
if (!is_cacheable(http_request, response))
1454+
if (!HTTP::is_cacheable(http_request.method()))
1455+
return;
1456+
if (!HTTP::is_cacheable(response.status(), response.header_list()))
14561457
return;
14571458

14581459
auto cached_response = Infrastructure::Response::create(realm.vm());
14591460

1460-
store_header_and_trailer_fields(response, *cached_response->header_list());
1461+
HTTP::store_header_and_trailer_fields(cached_response->header_list(), response.header_list());
14611462
cached_response->set_body(response.body()->clone(realm));
14621463
cached_response->set_body_info(response.body_info());
14631464
cached_response->set_method(http_request.method());
14641465
cached_response->set_status(response.status());
14651466
cached_response->url_list().append(http_request.current_url());
1466-
m_cache.set(http_request.current_url(), move(cached_response));
1467-
}
1468-
1469-
// https://httpwg.org/specs/rfc9111.html#freshening.responses
1470-
void freshen_stored_responses_upon_validation(Infrastructure::Response const& response, Vector<GC::Ptr<Infrastructure::Response>>& initial_set_of_stored_responses)
1471-
{
1472-
// When a cache receives a 304 (Not Modified) response, it needs to identify stored
1473-
// responses that are suitable for updating with the new information provided, and then do so.
1474-
1475-
// The initial set of stored responses to update are those that could have been
1476-
// chosen for that request — i.e., those that meet the requirements in Section 4,
1477-
// except the last requirement to be fresh, able to be served stale, or just validated.
1478-
for (auto stored_response : initial_set_of_stored_responses) {
1479-
// Then, that initial set of stored responses is further filtered by the first match of:
1480-
1481-
// - FIXME: If the new response contains one or more strong validators (see Section 8.8.1 of [HTTP]),
1482-
// then each of those strong validators identifies a selected representation for update.
1483-
// All the stored responses in the initial set with one of those same strong validators
1484-
// are identified for update.
1485-
// If none of the initial set contains at least one of the same strong validators,
1486-
// then the cache MUST NOT use the new response to update any stored responses.
1487-
// - FIXME: If the new response contains no strong validators but does contain one or more weak validators,
1488-
// and those validators correspond to one of the initial set's stored responses,
1489-
// then the most recent of those matching stored responses is identified for update.
1490-
// - FIXME: If the new response does not include any form of validator (such as where a client generates an
1491-
// `If-Modified-Since` request from a source other than the `Last-Modified` response header field),
1492-
// and there is only one stored response in the initial set, and that stored response also lacks a validator,
1493-
// then that stored response is identified for update.
1494-
1495-
// For each stored response identified, the cache MUST update its header fields
1496-
// with the header fields provided in the 304 (Not Modified) response, as per Section 3.2.
1497-
update_stored_header_fields(response, stored_response->header_list());
1498-
}
1467+
m_cache.set(http_request.current_url(), cached_response);
14991468
}
15001469

15011470
private:
1502-
// https://httpwg.org/specs/rfc9111.html#storing.fields
1503-
bool is_exempted_for_storage(StringView header_name)
1504-
{
1505-
// Caches MUST include all received response header fields — including unrecognized ones — when storing a response;
1506-
// this assures that new HTTP header fields can be successfully deployed. However, the following exceptions are made:
1507-
1508-
// - The Connection header field and fields whose names are listed in it are required by Section 7.6.1 of [HTTP]
1509-
// to be removed before forwarding the message. This MAY be implemented by doing so before storage.
1510-
1511-
// - Likewise, some fields' semantics require them to be removed before forwarding the message, and this MAY be
1512-
// implemented by doing so before storage; see Section 7.6.1 of [HTTP] for some examples.
1513-
1514-
// FIXME: - The no-cache (Section 5.2.2.4) and private (Section 5.2.2.7) cache directives can have arguments that
1515-
// prevent storage of header fields by all caches and shared caches, respectively.
1516-
1517-
// FIXME: - Header fields that are specific to the proxy that a cache uses when forwarding a request MUST NOT be stored,
1518-
// unless the cache incorporates the identity of the proxy into the cache key.
1519-
// Effectively, this is limited to Proxy-Authenticate (Section 11.7.1 of [HTTP]), Proxy-Authentication-Info (Section 11.7.3 of [HTTP]), and Proxy-Authorization (Section 11.7.2 of [HTTP]).
1520-
1521-
return header_name.is_one_of_ignoring_ascii_case(
1522-
"Connection"sv,
1523-
"Proxy-Connection"sv,
1524-
"Keep-Alive"sv,
1525-
"TE"sv,
1526-
"Transfer-Encoding"sv,
1527-
"Upgrade"sv);
1528-
}
1529-
1530-
// https://httpwg.org/specs/rfc9111.html#update
1531-
bool is_exempted_for_updating(StringView header_name)
1532-
{
1533-
// Caches are required to update a stored response's header fields from another
1534-
// (typically newer) response in several situations; for example, see Sections 3.4, 4.3.4, and 4.3.5.
1535-
1536-
// When doing so, the cache MUST add each header field in the provided response to the stored response,
1537-
// replacing field values that are already present, with the following exceptions:
1538-
1539-
// - Header fields excepted from storage in Section 3.1,
1540-
return is_exempted_for_storage(header_name)
1541-
// - Header fields that the cache's stored response depends upon, as described below,
1542-
|| false
1543-
// - Header fields that are automatically processed and removed by the recipient, as described below, and
1544-
|| false
1545-
// - The Content-Length header field.
1546-
|| header_name.equals_ignoring_ascii_case("Content-Length"sv);
1547-
1548-
// In some cases, caches (especially in user agents) store the results of processing
1549-
// the received response, rather than the response itself, and updating header fields
1550-
// that affect that processing can result in inconsistent behavior and security issues.
1551-
// Caches in this situation MAY omit these header fields from updating stored responses
1552-
// on an exceptional basis but SHOULD limit such omission to those fields necessary to
1553-
// assure integrity of the stored response.
1554-
1555-
// For example, a browser might decode the content coding of a response while it is being received,
1556-
// creating a disconnect between the data it has stored and the response's original metadata.
1557-
// Updating that stored metadata with a different Content-Encoding header field would be problematic.
1558-
// Likewise, a browser might store a post-parse HTML tree rather than the content received in the response;
1559-
// updating the Content-Type header field would not be workable in this case because any assumptions about
1560-
// the format made in parsing would now be invalid.
1561-
1562-
// Furthermore, some fields are automatically processed and removed by the HTTP implementation,
1563-
// such as the Content-Range header field. Implementations MAY automatically omit such header fields from updates,
1564-
// even when the processing does not actually occur.
1565-
1566-
// Note that the Content-* prefix is not a signal that a header field is omitted from update; it is a convention for MIME header fields, not HTTP.
1567-
}
1568-
1569-
// https://httpwg.org/specs/rfc9111.html#update
1570-
void update_stored_header_fields(Infrastructure::Response const& response, HTTP::HeaderList& headers)
1571-
{
1572-
for (auto const& header : *response.header_list()) {
1573-
if (!is_exempted_for_updating(header.name))
1574-
headers.delete_(header.name);
1575-
}
1576-
1577-
for (auto const& header : *response.header_list()) {
1578-
if (!is_exempted_for_updating(header.name))
1579-
headers.append(header);
1580-
}
1581-
}
1582-
1583-
// https://httpwg.org/specs/rfc9111.html#storing.fields
1584-
void store_header_and_trailer_fields(Infrastructure::Response const& response, HTTP::HeaderList& headers)
1585-
{
1586-
for (auto const& header : *response.header_list()) {
1587-
if (!is_exempted_for_storage(header.name))
1588-
headers.append(header);
1589-
}
1590-
}
1591-
1592-
// https://httpwg.org/specs/rfc9111.html#response.cacheability
1593-
static bool is_cacheable(Infrastructure::Request const& request, Infrastructure::Response const& response)
1594-
{
1595-
// A cache MUST NOT store a response to a request unless:
1596-
1597-
// - AD-HOC: For now, we simply don't cache responses without a simple ByteBuffer body.
1598-
if (!response.body() || !response.body()->source().has<ByteBuffer>())
1599-
return false;
1600-
1601-
// - the request method is understood by the cache;
1602-
if (request.method() != "GET"sv && request.method() != "HEAD"sv)
1603-
return false;
1604-
1605-
// - the response status code is final (see Section 15 of [HTTP]);
1606-
if (response.status() < 200)
1607-
return false;
1608-
1609-
// - if the response status code is 206 or 304,
1610-
// or the must-understand cache directive (see Section 5.2.2.3) is present:
1611-
// the cache understands the response status code;
1612-
if (response.status() == 206 || response.status() == 304) {
1613-
// FIXME: Implement must-understand cache directive
1614-
}
1615-
1616-
// - the no-store cache directive is not present in the response (see Section 5.2.2.5);
1617-
if (request.cache_mode() == Infrastructure::Request::CacheMode::NoStore)
1618-
return false;
1619-
1620-
// FIXME: - if the cache is shared: the private response directive is either not present
1621-
// or allows a shared cache to store a modified response; see Section 5.2.2.7);
1622-
1623-
// FIXME: - if the cache is shared: the Authorization header field is not present in the
1624-
// request (see Section 11.6.2 of [HTTP]) or a response directive is present
1625-
// that explicitly allows shared caching (see Section 3.5); and
1626-
1627-
// FIXME: - the response contains at least one of the following:
1628-
// + a public response directive (see Section 5.2.2.9);
1629-
// + a private response directive, if the cache is not shared (see Section 5.2.2.7);
1630-
// + an Expires header field (see Section 5.3);
1631-
// + a max-age response directive (see Section 5.2.2.1);
1632-
// + if the cache is shared: an s-maxage response directive (see Section 5.2.2.10);
1633-
// + a cache extension that allows it to be cached (see Section 5.2.3); or
1634-
// + a status code that is defined as heuristically cacheable (see Section 4.2.2).
1635-
1636-
return true;
1637-
}
1638-
16391471
HashMap<URL::URL, GC::Root<Infrastructure::Response>> m_cache;
16401472
};
16411473

@@ -1702,7 +1534,6 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
17021534

17031535
// 5. Let storedResponse be null.
17041536
GC::Ptr<Infrastructure::Response> stored_response;
1705-
GC::RootVector<GC::Ptr<Infrastructure::Response>> initial_set_of_stored_responses(realm.heap());
17061537

17071538
// 6. Let httpCache be null.
17081539
// (Typeless until we actually implement it, needed for checks below)
@@ -1971,7 +1802,7 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
19711802
// validation, as per the "Constructing Responses from Caches" chapter of HTTP Caching [HTTP-CACHING],
19721803
// if any.
19731804
// NOTE: As mandated by HTTP, this still takes the `Vary` header into account.
1974-
stored_response = http_cache->select_response(realm, http_request->current_url(), http_request->method(), *http_request->header_list(), initial_set_of_stored_responses);
1805+
stored_response = http_cache->select_response(realm, http_request->current_url(), http_request->method(), *http_request->header_list());
19751806

19761807
// 2. If storedResponse is non-null, then:
19771808
if (stored_response) {
@@ -2056,7 +1887,7 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
20561887

20571888
auto returned_pending_response = PendingResponse::create(vm, request);
20581889

2059-
pending_forward_response->when_loaded([&realm, &vm, &fetch_params, request, response, stored_response, initial_set_of_stored_responses, http_request, returned_pending_response, is_authentication_fetch, is_new_connection_fetch, revalidating_flag, include_credentials, response_was_null = !response, http_cache](GC::Ref<Infrastructure::Response> resolved_forward_response) mutable {
1890+
pending_forward_response->when_loaded([&realm, &vm, &fetch_params, request, response, stored_response, http_request, returned_pending_response, is_authentication_fetch, is_new_connection_fetch, revalidating_flag, include_credentials, response_was_null = !response, http_cache](GC::Ref<Infrastructure::Response> resolved_forward_response) mutable {
20601891
dbgln_if(WEB_FETCH_DEBUG, "Fetch: Running 'HTTP-network-or-cache fetch' pending_forward_response load callback");
20611892
if (response_was_null) {
20621893
auto forward_response = resolved_forward_response;
@@ -2079,7 +1910,7 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
20791910
// 1. Update storedResponse’s header list using forwardResponse’s header list, as per the "Freshening
20801911
// Stored Responses upon Validation" chapter of HTTP Caching.
20811912
// NOTE: This updates the stored response in cache as well.
2082-
http_cache->freshen_stored_responses_upon_validation(*forward_response, initial_set_of_stored_responses);
1913+
HTTP::update_header_fields(stored_response->header_list(), forward_response->header_list());
20831914

20841915
// 2. Set response to storedResponse.
20851916
response = stored_response;

0 commit comments

Comments
 (0)