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.
This commit is contained in:
parent
b165bdb874
commit
54d2395a03
10 changed files with 72 additions and 24 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/ImmutableBytes.h>
|
||||
#include <LibHTTP/Cache/CacheMode.h>
|
||||
#include <LibHTTP/Forward.h>
|
||||
#include <LibURL/URL.h>
|
||||
|
|
@ -26,7 +27,7 @@ public:
|
|||
ByteString reason_phrase;
|
||||
NonnullRefPtr<HeaderList> request_headers;
|
||||
NonnullRefPtr<HeaderList> response_headers;
|
||||
ByteBuffer response_body;
|
||||
Core::ImmutableBytes response_body;
|
||||
|
||||
UnixDateTime request_time;
|
||||
UnixDateTime response_time;
|
||||
|
|
@ -37,7 +38,7 @@ public:
|
|||
Optional<Entry const&> 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<u64, Vector<Entry>, IdentityHashTraits<u64>> m_pending_entries;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Requests {
|
|||
|
||||
class Request;
|
||||
class RequestClient;
|
||||
class ResponseData;
|
||||
class WebSocket;
|
||||
struct RequestTimingInfo;
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ void Request::set_request_body_file(Badge<Requests::RequestClient>, 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())
|
||||
|
|
|
|||
|
|
@ -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<Core::ImmutableBytes> 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<Core::ImmutableBytes> m_immutable_bytes;
|
||||
};
|
||||
|
||||
class ReadStream {
|
||||
public:
|
||||
static ErrorOr<NonnullOwnPtr<ReadStream>> create(int reader_fd);
|
||||
|
|
@ -69,7 +97,7 @@ public:
|
|||
void set_buffered_request_finished_callback(BufferedRequestFinished);
|
||||
|
||||
using HeadersReceived = Function<void(NonnullRefPtr<HTTP::HeaderList> response_headers, Optional<u32> response_code, Optional<String> const& reason_phrase, Optional<Core::AnonymousBuffer> javascript_bytecode, Optional<u64> javascript_bytecode_cache_vary_key)>;
|
||||
using DataReceived = Function<void(ReadonlyBytes data)>;
|
||||
using DataReceived = Function<void(ResponseData data)>;
|
||||
using RequestFinished = Function<void(u64 total_size, RequestTimingInfo const& timing_info, Optional<NetworkError> network_error)>;
|
||||
|
||||
// Configure the request such that the response data is provided unbuffered as it is received. Using this method is
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ void FetchedDataReceiver::set_body(GC::Ref<Fetch::Infrastructure::Body> 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())
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <LibCore/ImmutableBytes.h>
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibHTTP/Forward.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibRequests/Request.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
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<Infrastructure::FetchParams const>, GC::Ref<Streams::ReadableStream>, RefPtr<HTTP::MemoryCache>);
|
||||
|
|
@ -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<Core::ImmutableBytes> m_cache_body;
|
||||
|
||||
bool m_network_complete { false };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2229,8 +2229,8 @@ GC::Ref<PendingResponse> 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<StringView> error_message) {
|
||||
|
|
@ -2238,7 +2238,7 @@ GC::Ref<PendingResponse> 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)));
|
||||
|
|
|
|||
|
|
@ -369,7 +369,7 @@ RefPtr<Requests::Request> ResourceLoader::load(LoadRequest& request, GC::Root<On
|
|||
[on_headers_received = move(on_headers_received), on_data_received = move(on_data_received), on_complete = move(on_complete), request](ReadonlyBytes data, Requests::RequestTimingInfo const& timing_info, HTTP::HeaderList const& response_headers) {
|
||||
log_success(request);
|
||||
on_headers_received->function()(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<Requests::Request> ResourceLoader::load(LoadRequest& request, GC::Root<On
|
|||
request,
|
||||
[on_headers_received = move(on_headers_received), on_data_received = move(on_data_received), on_complete](FileLoadResult const& load_result) {
|
||||
on_headers_received->function()(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<Requests::Request> ResourceLoader::load(LoadRequest& request, GC::Root<On
|
|||
[request, on_headers_received = move(on_headers_received), on_data_received = move(on_data_received), on_complete](FileLoadResult const& load_result) {
|
||||
log_success(request);
|
||||
on_headers_received->function()(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<Requests::Request> ResourceLoader::load(LoadRequest& request, GC::Root<On
|
|||
|
||||
auto protocol_data_received = [on_data_received = move(on_data_received), request, request_id = protocol_request->id()](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<Requests::NetworkError> const& network_error) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <LibGC/Function.h>
|
||||
#include <LibHTTP/HeaderList.h>
|
||||
#include <LibRequests/Forward.h>
|
||||
#include <LibRequests/Request.h>
|
||||
#include <LibRequests/RequestTimingInfo.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
|
@ -33,7 +34,7 @@ public:
|
|||
void set_client(NonnullRefPtr<Requests::RequestClient>);
|
||||
|
||||
using OnHeadersReceived = GC::Function<void(HTTP::HeaderList const& response_headers, Optional<u32> status_code, Optional<String> const& reason_phrase, Optional<Core::AnonymousBuffer> javascript_bytecode, Optional<u64> javascript_bytecode_cache_vary_key)>;
|
||||
using OnDataReceived = GC::Function<void(ReadonlyBytes data)>;
|
||||
using OnDataReceived = GC::Function<void(Requests::ResponseData data)>;
|
||||
using OnComplete = GC::Function<void(bool success, Requests::RequestTimingInfo const& timing_info, Optional<StringView> error_message)>;
|
||||
|
||||
RefPtr<Requests::Request> load(LoadRequest&, GC::Root<OnHeadersReceived>, GC::Root<OnDataReceived>, GC::Root<OnComplete>);
|
||||
|
|
|
|||
Loading…
Reference in a new issue