mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 02:10:26 +00:00
We need to store request headers in order to handle Vary mismatches. (Note we should also be using BLOB for header storage in sqlite, as they are not necessarily UTF-8.)
115 lines
5.1 KiB
C++
115 lines
5.1 KiB
C++
/*
|
|
* Copyright (c) 2025-2026, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Debug.h>
|
|
#include <LibHTTP/Cache/MemoryCache.h>
|
|
#include <LibHTTP/Cache/Utilities.h>
|
|
|
|
namespace HTTP {
|
|
|
|
NonnullRefPtr<MemoryCache> MemoryCache::create()
|
|
{
|
|
return adopt_ref(*new MemoryCache());
|
|
}
|
|
|
|
// https://httpwg.org/specs/rfc9111.html#constructing.responses.from.caches
|
|
Optional<MemoryCache::Entry const&> MemoryCache::open_entry(URL::URL const& url, StringView method, HeaderList const& request_headers, CacheMode cache_mode)
|
|
{
|
|
if (cache_mode == CacheMode::Reload || cache_mode == CacheMode::NoCache)
|
|
return {};
|
|
|
|
// When presented with a request, a cache MUST NOT reuse a stored response unless:
|
|
// - the presented target URI (Section 7.1 of [HTTP]) and that of the stored response match, and
|
|
// - the request method associated with the stored response allows it to be used for the presented request, and
|
|
if (!is_cacheable(method, request_headers))
|
|
return {};
|
|
|
|
auto serialized_url = serialize_url_for_cache_storage(url);
|
|
auto cache_key = create_cache_key(serialized_url, method);
|
|
|
|
auto cache_entry = m_complete_entries.get(cache_key);
|
|
if (!cache_entry.has_value()) {
|
|
dbgln_if(HTTP_MEMORY_CACHE_DEBUG, "\033[37m[memory]\033[0m \033[35;1mNo cache entry for\033[0m {}", url);
|
|
return {};
|
|
}
|
|
|
|
// FIXME: - request header fields nominated by the stored response (if any) match those presented (see Section 4.1), and
|
|
(void)request_headers;
|
|
|
|
// - the stored response does not contain the no-cache directive (Section 5.2.2.4), unless it is successfully
|
|
// validated (Section 4.3), and
|
|
// - the stored response is one of the following:
|
|
// * fresh (see Section 4.2), or
|
|
// * allowed to be served stale (see Section 4.2.4), or
|
|
// * successfully validated (see Section 4.3).
|
|
auto freshness_lifetime = calculate_freshness_lifetime(cache_entry->status_code, cache_entry->response_headers);
|
|
auto current_age = calculate_age(cache_entry->response_headers, cache_entry->request_time, cache_entry->response_time);
|
|
|
|
switch (cache_lifetime_status(cache_entry->response_headers, freshness_lifetime, current_age)) {
|
|
case CacheLifetimeStatus::Fresh:
|
|
dbgln_if(HTTP_MEMORY_CACHE_DEBUG, "\033[37m[memory]\033[0m \033[32;1mOpened cache entry for\033[0m {} (lifetime={}s age={}s) ({} bytes)", url, freshness_lifetime.to_seconds(), current_age.to_seconds(), cache_entry->response_body.size());
|
|
return cache_entry;
|
|
|
|
case CacheLifetimeStatus::Expired:
|
|
case CacheLifetimeStatus::MustRevalidate:
|
|
case CacheLifetimeStatus::StaleWhileRevalidate:
|
|
if (cache_mode_permits_stale_responses(cache_mode)) {
|
|
dbgln_if(HTTP_MEMORY_CACHE_DEBUG, "\033[37m[memory]\033[0m \033[32;1mOpened expired cache entry for\033[0m {} (lifetime={}s age={}s) ({} bytes)", url, freshness_lifetime.to_seconds(), current_age.to_seconds(), cache_entry->response_body.size());
|
|
return cache_entry;
|
|
}
|
|
|
|
dbgln_if(HTTP_MEMORY_CACHE_DEBUG, "\033[37m[memory]\033[0m \033[33;1mCache entry expired for\033[0m {} (lifetime={}s age={}s)", url, freshness_lifetime.to_seconds(), current_age.to_seconds());
|
|
m_complete_entries.remove(cache_key);
|
|
return {};
|
|
}
|
|
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
void MemoryCache::create_entry(URL::URL const& url, StringView method, HeaderList const& request_headers, UnixDateTime request_time, u32 status_code, ByteString reason_phrase, HeaderList const& response_headers)
|
|
{
|
|
if (!is_cacheable(method, request_headers))
|
|
return;
|
|
if (!is_cacheable(status_code, response_headers))
|
|
return;
|
|
|
|
auto serialized_url = serialize_url_for_cache_storage(url);
|
|
auto cache_key = create_cache_key(serialized_url, method);
|
|
|
|
auto request_headers_copy = HeaderList::create();
|
|
store_header_and_trailer_fields(request_headers_copy, request_headers);
|
|
|
|
auto response_headers_copy = HeaderList::create();
|
|
store_header_and_trailer_fields(response_headers_copy, response_headers);
|
|
|
|
Entry cache_entry {
|
|
.status_code = status_code,
|
|
.reason_phrase = move(reason_phrase),
|
|
.request_headers = move(request_headers_copy),
|
|
.response_headers = move(response_headers_copy),
|
|
.response_body = {},
|
|
.request_time = request_time,
|
|
.response_time = UnixDateTime::now(),
|
|
};
|
|
|
|
dbgln_if(HTTP_MEMORY_CACHE_DEBUG, "\033[37m[memory]\033[0m \033[32;1mCreated cache entry for\033[0m {}", url);
|
|
m_pending_entries.set(cache_key, move(cache_entry));
|
|
}
|
|
|
|
void MemoryCache::finalize_entry(URL::URL const& url, StringView method, ByteBuffer response_body)
|
|
{
|
|
auto serialized_url = serialize_url_for_cache_storage(url);
|
|
auto cache_key = create_cache_key(serialized_url, method);
|
|
|
|
if (auto cache_entry = m_pending_entries.take(cache_key); cache_entry.has_value()) {
|
|
dbgln_if(HTTP_MEMORY_CACHE_DEBUG, "\033[37m[memory]\033[0m \033[34;1mFinished caching\033[0m {} ({} bytes)", url, response_body.size());
|
|
|
|
cache_entry->response_body = move(response_body);
|
|
m_complete_entries.set(cache_key, cache_entry.release_value());
|
|
}
|
|
}
|
|
|
|
}
|