From 54d2395a037ae0f2c098084ba6b622cd0161e73d Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 15 May 2026 14:49:12 +0200 Subject: [PATCH] LibWeb: Preserve file-backed HTTP cache bodies Carry response chunks through LibRequests and ResourceLoader as a ResponseData wrapper. A disk cache hit can retain its mapped storage, while ordinary streamed chunks still use borrowed bytes. Store completed memory cache entries as Core::ImmutableBytes. This lets mapped disk-cache responses stay file-backed while retained by the HTTP memory cache, instead of forcing another ByteBuffer copy. --- Libraries/LibHTTP/Cache/MemoryCache.cpp | 2 +- Libraries/LibHTTP/Cache/MemoryCache.h | 5 ++-- Libraries/LibRequests/Forward.h | 1 + Libraries/LibRequests/Request.cpp | 8 ++--- Libraries/LibRequests/Request.h | 30 ++++++++++++++++++- .../Fetch/Fetching/FetchedDataReceiver.cpp | 26 ++++++++++++---- .../Fetch/Fetching/FetchedDataReceiver.h | 5 +++- Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 6 ++-- Libraries/LibWeb/Loader/ResourceLoader.cpp | 10 +++---- Libraries/LibWeb/Loader/ResourceLoader.h | 3 +- 10 files changed, 72 insertions(+), 24 deletions(-) diff --git a/Libraries/LibHTTP/Cache/MemoryCache.cpp b/Libraries/LibHTTP/Cache/MemoryCache.cpp index 2207ee6d75..eb165a076a 100644 --- a/Libraries/LibHTTP/Cache/MemoryCache.cpp +++ b/Libraries/LibHTTP/Cache/MemoryCache.cpp @@ -109,7 +109,7 @@ void MemoryCache::create_entry(URL::URL const& url, StringView method, HeaderLis // FIXME: It would be nicer if create_entry just returned the cache and vary keys. But the call sites of create_entry and // finalize_entry are pretty far apart, so passing that information along is rather awkward in Fetch. -void MemoryCache::finalize_entry(URL::URL const& url, StringView method, HeaderList const& request_headers, u32 status_code, HeaderList const& response_headers, ByteBuffer response_body) +void MemoryCache::finalize_entry(URL::URL const& url, StringView method, HeaderList const& request_headers, u32 status_code, HeaderList const& response_headers, Core::ImmutableBytes response_body) { if (!is_cacheable(method, request_headers)) return; diff --git a/Libraries/LibHTTP/Cache/MemoryCache.h b/Libraries/LibHTTP/Cache/MemoryCache.h index 6f23c8e943..10f3446406 100644 --- a/Libraries/LibHTTP/Cache/MemoryCache.h +++ b/Libraries/LibHTTP/Cache/MemoryCache.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,7 @@ public: ByteString reason_phrase; NonnullRefPtr request_headers; NonnullRefPtr response_headers; - ByteBuffer response_body; + Core::ImmutableBytes response_body; UnixDateTime request_time; UnixDateTime response_time; @@ -37,7 +38,7 @@ public: Optional open_entry(URL::URL const&, StringView method, HeaderList const& request_headers, CacheMode); void create_entry(URL::URL const&, StringView method, HeaderList const& request_headers, UnixDateTime request_time, u32 status_code, ByteString reason_phrase, HeaderList const& response_headers); - void finalize_entry(URL::URL const&, StringView method, HeaderList const& request_headers, u32 status_code, HeaderList const& response_headers, ByteBuffer response_body); + void finalize_entry(URL::URL const&, StringView method, HeaderList const& request_headers, u32 status_code, HeaderList const& response_headers, Core::ImmutableBytes response_body); private: HashMap, IdentityHashTraits> m_pending_entries; diff --git a/Libraries/LibRequests/Forward.h b/Libraries/LibRequests/Forward.h index b9cbe703d3..a6db5a41dd 100644 --- a/Libraries/LibRequests/Forward.h +++ b/Libraries/LibRequests/Forward.h @@ -10,6 +10,7 @@ namespace Requests { class Request; class RequestClient; +class ResponseData; class WebSocket; struct RequestTimingInfo; diff --git a/Libraries/LibRequests/Request.cpp b/Libraries/LibRequests/Request.cpp index 6b7b2bdf6e..cf538f63c1 100644 --- a/Libraries/LibRequests/Request.cpp +++ b/Libraries/LibRequests/Request.cpp @@ -91,7 +91,7 @@ void Request::set_request_body_file(Badge, int fd, u64 m_internal_stream_data->file_backed_payload = payload.release_value(); if (m_internal_stream_data->on_data_available) - m_internal_stream_data->on_data_available(m_internal_stream_data->file_backed_payload->bytes()); + m_internal_stream_data->on_data_available(ResponseData::from_immutable_bytes(*m_internal_stream_data->file_backed_payload)); } void Request::set_buffered_request_finished_callback(BufferedRequestFinished on_buffered_request_finished) @@ -131,9 +131,9 @@ void Request::set_buffered_request_finished_callback(BufferedRequestFinished on_ move(payload)); }; - set_up_internal_stream_data([this](auto read_bytes) { + set_up_internal_stream_data([this](auto data) { // FIXME: What do we do if this fails? - m_internal_buffered_data->payload_stream.write_until_depleted(read_bytes).release_value_but_fixme_should_propagate_errors(); + m_internal_buffered_data->payload_stream.write_until_depleted(data.bytes()).release_value_but_fixme_should_propagate_errors(); }); } @@ -223,7 +223,7 @@ void Request::set_up_internal_stream_data(DataReceived on_data_available) if (read_bytes.is_empty()) break; - m_internal_stream_data->on_data_available(read_bytes); + m_internal_stream_data->on_data_available(ResponseData::from_bytes(read_bytes)); } while (true); if (m_internal_stream_data->read_stream->is_eof()) diff --git a/Libraries/LibRequests/Request.h b/Libraries/LibRequests/Request.h index c788a8d15e..d43964fccc 100644 --- a/Libraries/LibRequests/Request.h +++ b/Libraries/LibRequests/Request.h @@ -24,6 +24,34 @@ namespace Requests { class RequestClient; +class ResponseData { +public: + static ResponseData from_bytes(ReadonlyBytes bytes) { return ResponseData { bytes }; } + static ResponseData from_immutable_bytes(Core::ImmutableBytes bytes) { return ResponseData { move(bytes) }; } + + [[nodiscard]] ReadonlyBytes bytes() const + { + if (m_immutable_bytes.has_value()) + return m_immutable_bytes->bytes(); + return m_bytes; + } + [[nodiscard]] Optional const& immutable_bytes() const { return m_immutable_bytes; } + +private: + explicit ResponseData(ReadonlyBytes bytes) + : m_bytes(bytes) + { + } + + explicit ResponseData(Core::ImmutableBytes bytes) + : m_immutable_bytes(move(bytes)) + { + } + + ReadonlyBytes m_bytes; + Optional m_immutable_bytes; +}; + class ReadStream { public: static ErrorOr> create(int reader_fd); @@ -69,7 +97,7 @@ public: void set_buffered_request_finished_callback(BufferedRequestFinished); using HeadersReceived = Function response_headers, Optional response_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key)>; - using DataReceived = Function; + using DataReceived = Function; using RequestFinished = Function network_error)>; // Configure the request such that the response data is provided unbuffered as it is received. Using this method is diff --git a/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp b/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp index e82c29f598..cd3e2f6996 100644 --- a/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp @@ -40,7 +40,7 @@ void FetchedDataReceiver::set_body(GC::Ref body) m_pre_body_sniff_buffer.clear(); } // If the stream already completed before the body was set, - // we missed the set_sniff_bytes_complete() call in handle_network_bytes. + // we missed the set_sniff_bytes_complete() call in handle_network_data. if (m_network_complete) m_body->set_sniff_bytes_complete(); } @@ -56,10 +56,10 @@ void FetchedDataReceiver::visit_edges(Visitor& visitor) // This implements the parallel steps of the pullAlgorithm in HTTP-network-fetch. // https://fetch.spec.whatwg.org/#ref-for-in-parallel⑤ -void FetchedDataReceiver::handle_network_bytes(ReadonlyBytes bytes, NetworkState state) +void FetchedDataReceiver::handle_network_data(Requests::ResponseData data, NetworkState state) { if (state == NetworkState::Complete) { - VERIFY(bytes.is_empty()); + VERIFY(data.bytes().is_empty()); m_network_complete = true; // Mark sniff bytes as complete when the stream ends if (m_body) @@ -77,6 +77,7 @@ void FetchedDataReceiver::handle_network_bytes(ReadonlyBytes bytes, NetworkState return; // 1. If one or more bytes have been transmitted from response’s message body, then: + auto bytes = data.bytes(); if (bytes.is_empty()) return; @@ -96,8 +97,17 @@ void FetchedDataReceiver::handle_network_bytes(ReadonlyBytes bytes, NetworkState m_pre_body_sniff_buffer.append(bytes.slice(0, min(bytes.size(), space_remaining))); } - if (m_http_cache) - m_cache_buffer.append(bytes); + if (m_http_cache) { + if (auto const& immutable_bytes = data.immutable_bytes(); immutable_bytes.has_value() && immutable_bytes->is_file_backed() && m_cache_buffer.is_empty() && !m_cache_body.has_value()) { + m_cache_body = *immutable_bytes; + } else { + if (m_cache_body.has_value()) { + m_cache_buffer.append(m_cache_body->bytes()); + m_cache_body.clear(); + } + m_cache_buffer.append(bytes); + } + } // 7. Append bytes to buffer. enqueue_into_stream(bytes); @@ -138,10 +148,14 @@ void FetchedDataReceiver::close_stream() auto request = m_fetch_params->request(); if (m_stream->is_readable() && !m_fetch_params->is_canceled() && m_response && request->cache_mode() != HTTP::CacheMode::NoStore) { - m_http_cache->finalize_entry(request->current_url(), request->method(), request->header_list(), m_response->status(), m_response->header_list(), move(m_cache_buffer)); + auto response_body = m_cache_body.has_value() + ? m_cache_body.release_value() + : Core::ImmutableBytes::adopt(move(m_cache_buffer)); + m_http_cache->finalize_entry(request->current_url(), request->method(), request->header_list(), m_response->status(), m_response->header_list(), move(response_body)); } m_http_cache.clear(); + m_cache_body.clear(); } if (!m_stream->is_readable()) diff --git a/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.h b/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.h index 0cf07576cb..39ebbab4f8 100644 --- a/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.h +++ b/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.h @@ -8,9 +8,11 @@ #pragma once #include +#include #include #include #include +#include #include namespace Web::Fetch::Fetching { @@ -30,7 +32,7 @@ public: Complete, Error, }; - void handle_network_bytes(ReadonlyBytes, NetworkState); + void handle_network_data(Requests::ResponseData, NetworkState); private: FetchedDataReceiver(GC::Ref, GC::Ref, RefPtr); @@ -54,6 +56,7 @@ private: // Whole-response buffer retained only when m_http_cache is non-null, for finalize_entry(). ByteBuffer m_cache_buffer; + Optional m_cache_body; bool m_network_complete { false }; }; diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index de101b9faf..502f323764 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -2229,8 +2229,8 @@ GC::Ref nonstandard_resource_loader_file_or_http_network_fetch( // 16. Run these steps in parallel: // FIXME: 1. Run these steps, but abort when fetchParams is canceled: - auto on_data_received = GC::create_function(vm.heap(), [fetched_data_receiver](ReadonlyBytes bytes) { - fetched_data_receiver->handle_network_bytes(bytes, FetchedDataReceiver::NetworkState::Ongoing); + auto on_data_received = GC::create_function(vm.heap(), [fetched_data_receiver](Requests::ResponseData data) { + fetched_data_receiver->handle_network_data(move(data), FetchedDataReceiver::NetworkState::Ongoing); }); auto on_complete = GC::create_function(vm.heap(), [&vm, &realm, pending_response, stream, fetched_data_receiver](bool success, Requests::RequestTimingInfo const&, Optional error_message) { @@ -2238,7 +2238,7 @@ GC::Ref nonstandard_resource_loader_file_or_http_network_fetch( HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; if (success) { - fetched_data_receiver->handle_network_bytes({}, FetchedDataReceiver::NetworkState::Complete); + fetched_data_receiver->handle_network_data(Requests::ResponseData::from_bytes({}), FetchedDataReceiver::NetworkState::Complete); } else { // 16.1.2.2. Otherwise, if stream is readable, error stream with a TypeError. auto error = MUST(String::formatted("Load failed: {}", error_message.value_or("Unknown error"sv))); diff --git a/Libraries/LibWeb/Loader/ResourceLoader.cpp b/Libraries/LibWeb/Loader/ResourceLoader.cpp index ff155e9e58..bd7d3abd22 100644 --- a/Libraries/LibWeb/Loader/ResourceLoader.cpp +++ b/Libraries/LibWeb/Loader/ResourceLoader.cpp @@ -369,7 +369,7 @@ RefPtr ResourceLoader::load(LoadRequest& request, GC::Rootfunction()(response_headers, {}, {}, {}, {}); - on_data_received->function()(data); + on_data_received->function()(Requests::ResponseData::from_bytes(data)); on_complete->function()(true, timing_info, {}); }); return nullptr; @@ -380,7 +380,7 @@ RefPtr ResourceLoader::load(LoadRequest& request, GC::Rootfunction()(load_result.response_headers, {}, {}, {}, {}); - on_data_received->function()(load_result.data); + on_data_received->function()(Requests::ResponseData::from_bytes(load_result.data)); on_complete->function()(true, load_result.timing_info, {}); }, [on_complete](ByteString const& message) { @@ -396,7 +396,7 @@ RefPtr ResourceLoader::load(LoadRequest& request, GC::Rootfunction()(load_result.response_headers, {}, {}, {}, {}); - on_data_received->function()(load_result.data); + on_data_received->function()(Requests::ResponseData::from_bytes(load_result.data)); on_complete->function()(true, load_result.timing_info, {}); }, [on_complete, request](ByteString const& message) { @@ -431,8 +431,8 @@ RefPtr ResourceLoader::load(LoadRequest& request, GC::Rootid()](auto data) { if (auto page = request.page()) - page->client().page_did_receive_network_response_body(request_id, data); - on_data_received->function()(data); + page->client().page_did_receive_network_response_body(request_id, data.bytes()); + on_data_received->function()(move(data)); }; auto protocol_complete = [this, on_complete = move(on_complete), request, &protocol_request = *protocol_request](u64 total_size, Requests::RequestTimingInfo const& timing_info, Optional const& network_error) { diff --git a/Libraries/LibWeb/Loader/ResourceLoader.h b/Libraries/LibWeb/Loader/ResourceLoader.h index b2f10b5dcc..d06842d1fb 100644 --- a/Libraries/LibWeb/Loader/ResourceLoader.h +++ b/Libraries/LibWeb/Loader/ResourceLoader.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -33,7 +34,7 @@ public: void set_client(NonnullRefPtr); using OnHeadersReceived = GC::Function status_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key)>; - using OnDataReceived = GC::Function; + using OnDataReceived = GC::Function; using OnComplete = GC::Function error_message)>; RefPtr load(LoadRequest&, GC::Root, GC::Root, GC::Root);