ladybird/Libraries/LibRequests/Request.cpp
Andreas Kling c0b19ff981 RequestServer: Send bytecode cache sidecars as files
Map JavaScript bytecode cache sidecars from the HTTP disk cache instead
of copying them into anonymous shared buffers while handing response
headers to WebContent. Store the mapped data as ImmutableBytes on the
fetch response so script fetching can decode directly from the mapped
sidecar bytes.

Add LibHTTP coverage for retrieving associated cache data as a mappable
file, alongside the existing byte-buffer retrieval API.
2026-05-16 08:13:35 +02:00

276 lines
10 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/ScopeGuard.h>
#include <LibCore/File.h>
#include <LibCore/System.h>
#include <LibRequests/Request.h>
#include <LibRequests/RequestClient.h>
namespace Requests {
static Optional<Core::ImmutableBytes> map_body_file(int fd, u64 offset, u64 size)
{
ArmedScopeGuard close_fd = [fd] {
(void)Core::System::close(fd);
};
if (!AK::is_within_range<off_t>(offset) || !AK::is_within_range<size_t>(size)) {
dbgln("Request: Received body file outside mappable range");
return {};
}
close_fd.disarm();
auto payload = Core::ImmutableBytes::map_from_fd_range_and_close(fd, "request response"sv, static_cast<off_t>(offset), static_cast<size_t>(size));
if (payload.is_error()) {
dbgln("Request: Failed to map body file: {}", payload.error());
return {};
}
return payload.release_value();
}
ErrorOr<NonnullOwnPtr<ReadStream>> ReadStream::create(int reader_fd)
{
#if defined(AK_OS_WINDOWS)
auto local_socket = TRY(Core::LocalSocket::adopt_fd(reader_fd));
auto notifier = local_socket->notifier();
VERIFY(notifier);
return adopt_own(*new ReadStream(move(local_socket), notifier.release_nonnull()));
#else
auto file = TRY(Core::File::adopt_fd(reader_fd, Core::File::OpenMode::Read));
auto notifier = Core::Notifier::construct(reader_fd, Core::Notifier::Type::Read);
return adopt_own(*new ReadStream(move(file), move(notifier)));
#endif
}
Request::Request(RequestClient& client, u64 request_id)
: m_client(client)
, m_request_id(request_id)
{
}
bool Request::stop()
{
on_headers_received = nullptr;
on_finish = nullptr;
on_certificate_requested = nullptr;
m_internal_buffered_data = nullptr;
m_internal_stream_data = nullptr;
m_mode = Mode::Unknown;
return m_client->stop_request({}, *this);
}
void Request::set_request_fd(Badge<Requests::RequestClient>, int fd)
{
// If the request was stopped while this IPC was in-flight, just bail.
if (!m_internal_stream_data)
return;
VERIFY(m_fd == -1);
m_fd = fd;
auto read_stream = MUST(ReadStream::create(fd));
auto notifier = read_stream->notifier();
notifier->on_activation = move(m_internal_stream_data->read_notifier->on_activation);
m_internal_stream_data->read_notifier = notifier;
m_internal_stream_data->read_stream = move(read_stream);
}
void Request::set_request_body_file(Badge<Requests::RequestClient>, int fd, u64 offset, u64 size)
{
auto payload = map_body_file(fd, offset, size);
if (!payload.has_value()) {
if (m_internal_stream_data)
m_body_delivery_error = NetworkError::CacheReadFailed;
return;
}
// If the request was stopped while this IPC was in-flight, just bail.
if (!m_internal_stream_data)
return;
if (m_mode == Mode::Buffered) {
m_internal_buffered_data->payload = payload.release_value();
return;
}
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(ResponseData::from_immutable_bytes(*m_internal_stream_data->file_backed_payload));
}
void Request::set_request_cached_body_file(Badge<Requests::RequestClient>, int fd, u64 offset, u64 size)
{
auto payload = map_body_file(fd, offset, size);
if (!payload.has_value())
return;
if (m_mode == Mode::Buffered) {
m_internal_buffered_data->payload = payload.release_value();
return;
}
// If the request was stopped while this IPC was in-flight, just bail.
if (!m_internal_stream_data)
return;
m_internal_stream_data->cached_payload = payload.release_value();
if (m_internal_stream_data->on_cached_body_available)
m_internal_stream_data->on_cached_body_available(*m_internal_stream_data->cached_payload);
}
void Request::set_buffered_request_finished_callback(BufferedRequestFinished on_buffered_request_finished)
{
VERIFY(m_mode == Mode::Unknown);
m_mode = Mode::Buffered;
m_internal_buffered_data = make<InternalBufferedData>();
on_headers_received = [this](auto headers, auto response_code, auto const& reason_phrase, auto javascript_bytecode, auto javascript_bytecode_cache_vary_key) {
m_internal_buffered_data->response_headers = move(headers);
m_internal_buffered_data->response_code = move(response_code);
m_internal_buffered_data->reason_phrase = reason_phrase;
m_internal_buffered_data->javascript_bytecode = move(javascript_bytecode);
m_internal_buffered_data->javascript_bytecode_cache_vary_key = javascript_bytecode_cache_vary_key;
};
on_finish = [this, on_buffered_request_finished = move(on_buffered_request_finished)](auto total_size, auto& timing_info, auto network_error) {
auto payload = [&] {
if (m_internal_buffered_data->payload.has_value())
return m_internal_buffered_data->payload.release_value();
auto output_buffer = ByteBuffer::create_uninitialized(m_internal_buffered_data->payload_stream.used_buffer_size()).release_value_but_fixme_should_propagate_errors();
m_internal_buffered_data->payload_stream.read_until_filled(output_buffer).release_value_but_fixme_should_propagate_errors();
return Core::ImmutableBytes::adopt(move(output_buffer));
}();
on_buffered_request_finished(
total_size,
timing_info,
network_error,
m_internal_buffered_data->response_headers,
m_internal_buffered_data->response_code,
m_internal_buffered_data->reason_phrase,
move(m_internal_buffered_data->javascript_bytecode),
m_internal_buffered_data->javascript_bytecode_cache_vary_key,
move(payload));
};
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(data.bytes()).release_value_but_fixme_should_propagate_errors();
});
}
void Request::set_unbuffered_request_callbacks(HeadersReceived on_headers_received, DataReceived on_data_received, CachedBodyAvailable on_cached_body_available, RequestFinished on_finish)
{
VERIFY(m_mode == Mode::Unknown);
m_mode = Mode::Unbuffered;
this->on_headers_received = move(on_headers_received);
this->on_finish = move(on_finish);
set_up_internal_stream_data(move(on_data_received));
m_internal_stream_data->on_cached_body_available = move(on_cached_body_available);
}
void Request::did_finish(Badge<RequestClient>, u64 total_size, RequestTimingInfo const& timing_info, Optional<NetworkError> const& network_error)
{
auto effective_network_error = m_body_delivery_error.has_value() ? m_body_delivery_error : network_error;
if (on_finish)
on_finish(total_size, timing_info, effective_network_error);
}
void Request::did_receive_headers(Badge<RequestClient>, NonnullRefPtr<HTTP::HeaderList> response_headers, Optional<u32> response_code, Optional<String> const& reason_phrase, Optional<Core::ImmutableBytes> javascript_bytecode, Optional<u64> javascript_bytecode_cache_vary_key)
{
if (on_headers_received)
on_headers_received(move(response_headers), response_code, reason_phrase, move(javascript_bytecode), javascript_bytecode_cache_vary_key);
}
void Request::did_request_certificates(Badge<RequestClient>)
{
if (on_certificate_requested) {
auto result = on_certificate_requested();
if (!m_client->set_certificate({}, *this, result.certificate, result.key)) {
dbgln("Request: set_certificate failed");
}
}
}
void Request::set_up_internal_stream_data(DataReceived on_data_available)
{
VERIFY(!m_internal_stream_data);
m_internal_stream_data = make<InternalStreamData>();
m_internal_stream_data->on_data_available = move(on_data_available);
m_internal_stream_data->read_notifier = Core::Notifier::construct(fd(), Core::Notifier::Type::Read);
if (fd() != -1)
m_internal_stream_data->read_stream = MUST(ReadStream::create(fd()));
auto user_on_finish = move(on_finish);
on_finish = [this](auto total_size, auto const& timing_info, auto network_error) {
// If the request was stopped while this IPC was in-flight, just bail.
if (!m_internal_stream_data)
return;
m_internal_stream_data->total_size = total_size;
m_internal_stream_data->network_error = network_error;
m_internal_stream_data->timing_info = timing_info;
m_internal_stream_data->request_done = true;
m_internal_stream_data->on_finish();
};
m_internal_stream_data->on_finish = [this, user_on_finish = move(user_on_finish)]() {
// If the request was stopped while this IPC was in-flight, just bail.
if (!m_internal_stream_data)
return;
if (!m_internal_stream_data->user_finish_called && (!m_internal_stream_data->read_stream || m_internal_stream_data->read_stream->is_eof())) {
m_internal_stream_data->user_finish_called = true;
user_on_finish(m_internal_stream_data->total_size, m_internal_stream_data->timing_info, m_internal_stream_data->network_error);
}
};
m_internal_stream_data->read_notifier->on_activation = [this]() {
static constexpr size_t buffer_size = 256 * KiB;
static char buffer[buffer_size];
// If the request was stopped while this IPC was in-flight, just bail.
if (!m_internal_stream_data)
return;
do {
auto result = m_internal_stream_data->read_stream->read_some({ buffer, buffer_size });
if (result.is_error() && (!result.error().is_errno() || (result.error().is_errno() && result.error().code() != EINTR)))
break;
if (result.is_error())
continue;
auto read_bytes = result.release_value();
if (read_bytes.is_empty())
break;
m_internal_stream_data->on_data_available(ResponseData::from_bytes(read_bytes));
} while (true);
if (m_internal_stream_data->read_stream->is_eof())
m_internal_stream_data->read_notifier->close();
if (m_internal_stream_data->request_done)
m_internal_stream_data->on_finish();
};
}
Request::InternalBufferedData::InternalBufferedData()
: response_headers(HTTP::HeaderList::create())
{
}
}