Skip to content

Commit b2c112c

Browse files
trflynn89gmta
authored andcommitted
LibWebView+RequestServer: Add a simple test mode for the HTTP disk cache
This mode allows us to test the HTTP disk cache with two mechanisms: 1. If RequestServer is launched with --http-disk-cache-mode=testing, it will cache requests with a X-Ladybird-Enable-Disk-Cache header. 2. In test mode, RS will include a X-Ladybird-Disk-Cache-Status response header indicating how the response was handled by the cache. There is no standard way for a web request to know what happened with respect to the disk cache, so this fills that hole for testing. This mode is not exposed to users.
1 parent a853bb4 commit b2c112c

File tree

11 files changed

+107
-28
lines changed

11 files changed

+107
-28
lines changed

Libraries/LibWebView/Application.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
262262

263263
m_request_server_options = {
264264
.certificates = move(certificates),
265-
.enable_http_disk_cache = enable_http_disk_cache ? EnableHTTPDiskCache::Yes : EnableHTTPDiskCache::No,
265+
.http_disk_cache_mode = enable_http_disk_cache ? HTTPDiskCacheMode::Enabled : HTTPDiskCacheMode::Disabled,
266266
};
267267

268268
m_web_content_options = {

Libraries/LibWebView/HelperProcess.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,19 @@ ErrorOr<NonnullRefPtr<Requests::RequestClient>> launch_request_server_process()
213213
for (auto const& certificate : request_server_options.certificates)
214214
arguments.append(ByteString::formatted("--certificate={}", certificate));
215215

216-
if (request_server_options.enable_http_disk_cache == EnableHTTPDiskCache::Yes)
217-
arguments.append("--enable-http-disk-cache"sv);
216+
arguments.append("--http-disk-cache-mode"sv);
217+
218+
switch (request_server_options.http_disk_cache_mode) {
219+
case HTTPDiskCacheMode::Disabled:
220+
arguments.append("disabled"sv);
221+
break;
222+
case HTTPDiskCacheMode::Enabled:
223+
arguments.append("enabled"sv);
224+
break;
225+
case HTTPDiskCacheMode::Testing:
226+
arguments.append("testing"sv);
227+
break;
228+
}
218229

219230
if (auto server = mach_server_name(); server.has_value()) {
220231
arguments.append("--mach-server-name"sv);

Libraries/LibWebView/Options.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,15 @@ struct BrowserOptions {
9393
EnableContentFilter enable_content_filter { EnableContentFilter::Yes };
9494
};
9595

96-
enum class EnableHTTPDiskCache {
97-
No,
98-
Yes,
96+
enum class HTTPDiskCacheMode {
97+
Disabled,
98+
Enabled,
99+
Testing,
99100
};
100101

101102
struct RequestServerOptions {
102103
Vector<ByteString> certificates;
103-
EnableHTTPDiskCache enable_http_disk_cache { EnableHTTPDiskCache::No };
104+
HTTPDiskCacheMode http_disk_cache_mode { HTTPDiskCacheMode::Disabled };
104105
};
105106

106107
enum class IsLayoutTestMode {

Services/RequestServer/Cache/DiskCache.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,38 @@ namespace RequestServer {
1515

1616
static constexpr auto INDEX_DATABASE = "INDEX"sv;
1717

18-
ErrorOr<DiskCache> DiskCache::create()
18+
ErrorOr<DiskCache> DiskCache::create(Mode mode)
1919
{
20-
auto cache_directory = LexicalPath::join(Core::StandardPaths::cache_directory(), "Ladybird"sv, "Cache"sv);
20+
auto cache_name = mode == Mode::Normal ? "Cache"sv : "TestCache"sv;
21+
auto cache_directory = LexicalPath::join(Core::StandardPaths::cache_directory(), "Ladybird"sv, cache_name);
2122

2223
auto database = TRY(Database::Database::create(cache_directory.string(), INDEX_DATABASE));
2324
auto index = TRY(CacheIndex::create(database));
2425

25-
return DiskCache { move(database), move(cache_directory), move(index) };
26+
return DiskCache { mode, move(database), move(cache_directory), move(index) };
2627
}
2728

28-
DiskCache::DiskCache(NonnullRefPtr<Database::Database> database, LexicalPath cache_directory, CacheIndex index)
29-
: m_database(move(database))
29+
DiskCache::DiskCache(Mode mode, NonnullRefPtr<Database::Database> database, LexicalPath cache_directory, CacheIndex index)
30+
: m_mode(mode)
31+
, m_database(move(database))
3032
, m_cache_directory(move(cache_directory))
3133
, m_index(move(index))
3234
{
35+
// Start with a clean slate in test mode.
36+
if (m_mode == Mode::Testing)
37+
remove_entries_accessed_since(UnixDateTime::earliest());
3338
}
3439

3540
Variant<Optional<CacheEntryWriter&>, DiskCache::CacheHasOpenEntry> DiskCache::create_entry(Request& request)
3641
{
3742
if (!is_cacheable(request.method()))
3843
return Optional<CacheEntryWriter&> {};
3944

45+
if (m_mode == Mode::Testing) {
46+
if (!request.request_headers().contains(TEST_CACHE_ENABLED_HEADER))
47+
return Optional<CacheEntryWriter&> {};
48+
}
49+
4050
auto serialized_url = serialize_url_for_cache_storage(request.url());
4151
auto cache_key = create_cache_key(serialized_url, request.method());
4252

Services/RequestServer/Cache/DiskCache.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,16 @@ namespace RequestServer {
2121

2222
class DiskCache {
2323
public:
24-
static ErrorOr<DiskCache> create();
24+
enum class Mode {
25+
Normal,
26+
27+
// In test mode, we only enable caching of responses on a per-request basis, signified by a request header. The
28+
// response headers will include some status on how the request was handled.
29+
Testing,
30+
};
31+
static ErrorOr<DiskCache> create(Mode);
32+
33+
Mode mode() const { return m_mode; }
2534

2635
struct CacheHasOpenEntry { };
2736
Variant<Optional<CacheEntryWriter&>, CacheHasOpenEntry> create_entry(Request&);
@@ -35,14 +44,16 @@ class DiskCache {
3544
void cache_entry_closed(Badge<CacheEntry>, CacheEntry const&);
3645

3746
private:
38-
DiskCache(NonnullRefPtr<Database::Database>, LexicalPath cache_directory, CacheIndex);
47+
DiskCache(Mode, NonnullRefPtr<Database::Database>, LexicalPath cache_directory, CacheIndex);
3948

4049
enum class CheckReaderEntries {
4150
No,
4251
Yes,
4352
};
4453
bool check_if_cache_has_open_entry(Request&, u64 cache_key, CheckReaderEntries);
4554

55+
Mode m_mode;
56+
4657
NonnullRefPtr<Database::Database> m_database;
4758

4859
HashMap<u64, Vector<NonnullOwnPtr<CacheEntry>, 1>> m_open_cache_entries;

Services/RequestServer/Cache/Utilities.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ bool is_header_exempted_from_storage(StringView name)
192192
"Proxy-Connection"sv,
193193
"TE"sv,
194194
"Transfer-Encoding"sv,
195-
"Upgrade"sv
195+
"Upgrade"sv,
196196

197197
// * Likewise, some fields' semantics require them to be removed before forwarding the message, and this MAY be
198198
// implemented by doing so before storage; see Section 7.6.1 of [HTTP] for some examples.
@@ -204,7 +204,10 @@ bool is_header_exempted_from_storage(StringView name)
204204
// unless the cache incorporates the identity of the proxy into the cache key. Effectively, this is limited to
205205
// Proxy-Authenticate (Section 11.7.1 of [HTTP]), Proxy-Authentication-Info (Section 11.7.3 of [HTTP]), and
206206
// Proxy-Authorization (Section 11.7.2 of [HTTP]).
207-
);
207+
208+
// AD-HOC: Exclude headers used only for testing.
209+
TEST_CACHE_ENABLED_HEADER,
210+
TEST_CACHE_STATUS_HEADER);
208211
}
209212

210213
// https://httpwg.org/specs/rfc9111.html#heuristic.freshness

Services/RequestServer/Cache/Utilities.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
namespace RequestServer {
1717

18+
constexpr inline auto TEST_CACHE_ENABLED_HEADER = "X-Ladybird-Enable-Disk-Cache"sv;
19+
constexpr inline auto TEST_CACHE_STATUS_HEADER = "X-Ladybird-Disk-Cache-Status"sv;
20+
1821
String serialize_url_for_cache_storage(URL::URL const&);
1922
u64 create_cache_key(StringView url, StringView method);
2023
LexicalPath path_for_cache_key(LexicalPath const& cache_directory, u64 cache_key);

Services/RequestServer/Request.cpp

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ void Request::handle_initial_state()
211211
m_disk_cache->create_entry(*this).visit(
212212
[&](Optional<CacheEntryWriter&> cache_entry_writer) {
213213
m_cache_entry_writer = cache_entry_writer;
214+
215+
if (!m_cache_entry_writer.has_value())
216+
m_cache_status = CacheStatus::NotCached;
214217
},
215218
[&](DiskCache::CacheHasOpenEntry) {
216219
// If an existing entry is open for reading or writing, we must wait for it to complete. An entry being
@@ -228,15 +231,13 @@ void Request::handle_initial_state()
228231

229232
void Request::handle_read_cache_state()
230233
{
231-
m_status_code = m_cache_entry_reader->status_code();
232234
m_reason_phrase = m_cache_entry_reader->reason_phrase();
233235
m_response_headers = m_cache_entry_reader->response_headers();
236+
m_cache_status = CacheStatus::ReadFromCache;
234237

235238
if (inform_client_request_started().is_error())
236239
return;
237-
238-
m_client.async_headers_became_available(m_request_id, m_response_headers, m_status_code, m_reason_phrase);
239-
m_sent_response_headers_to_client = true;
240+
transfer_headers_to_client_if_needed();
240241

241242
m_cache_entry_reader->pipe_to(
242243
m_client_request_pipe->writer_fd(),
@@ -556,13 +557,37 @@ void Request::transfer_headers_to_client_if_needed()
556557
if (exchange(m_sent_response_headers_to_client, true))
557558
return;
558559

559-
m_status_code = acquire_status_code();
560-
m_client.async_headers_became_available(m_request_id, m_response_headers, m_status_code, m_reason_phrase);
560+
if (m_cache_entry_reader.has_value())
561+
m_status_code = m_cache_entry_reader->status_code();
562+
else
563+
m_status_code = acquire_status_code();
561564

562565
if (m_cache_entry_writer.has_value()) {
563-
if (m_cache_entry_writer->write_status_and_reason(m_status_code, m_reason_phrase, m_response_headers).is_error())
566+
if (m_cache_entry_writer->write_status_and_reason(m_status_code, m_reason_phrase, m_response_headers).is_error()) {
567+
m_cache_status = CacheStatus::NotCached;
564568
m_cache_entry_writer.clear();
569+
} else {
570+
m_cache_status = CacheStatus::WrittenToCache;
571+
}
572+
}
573+
574+
if (m_disk_cache.has_value() && m_disk_cache->mode() == DiskCache::Mode::Testing) {
575+
switch (m_cache_status) {
576+
case CacheStatus::Unknown:
577+
break;
578+
case CacheStatus::NotCached:
579+
m_response_headers.set(TEST_CACHE_STATUS_HEADER, "not-cached"sv);
580+
break;
581+
case CacheStatus::WrittenToCache:
582+
m_response_headers.set(TEST_CACHE_STATUS_HEADER, "written-to-cache"sv);
583+
break;
584+
case CacheStatus::ReadFromCache:
585+
m_response_headers.set(TEST_CACHE_STATUS_HEADER, "read-from-cache"sv);
586+
break;
587+
}
565588
}
589+
590+
m_client.async_headers_became_available(m_request_id, m_response_headers, m_status_code, m_reason_phrase);
566591
}
567592

568593
ErrorOr<void> Request::write_queued_bytes_without_blocking()

Services/RequestServer/Request.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class Request : public Weakable<Request> {
5353

5454
URL::URL const& url() const { return m_url; }
5555
ByteString const& method() const { return m_method; }
56+
HTTP::HeaderMap const& request_headers() const { return m_request_headers; }
5657
UnixDateTime request_start_time() const { return m_request_start_time; }
5758

5859
void notify_request_unblocked(Badge<DiskCache>);
@@ -75,6 +76,13 @@ class Request : public Weakable<Request> {
7576
Error, // Any error occured during the request's lifetime.
7677
};
7778

79+
enum class CacheStatus : u8 {
80+
Unknown,
81+
NotCached,
82+
WrittenToCache,
83+
ReadFromCache,
84+
};
85+
7886
Request(
7987
i32 request_id,
8088
Optional<DiskCache&> disk_cache,
@@ -159,6 +167,7 @@ class Request : public Weakable<Request> {
159167

160168
Optional<CacheEntryReader&> m_cache_entry_reader;
161169
Optional<CacheEntryWriter&> m_cache_entry_writer;
170+
CacheStatus m_cache_status { CacheStatus::Unknown };
162171

163172
Optional<Requests::NetworkError> m_network_error;
164173
};

Services/RequestServer/main.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
3434

3535
Vector<ByteString> certificates;
3636
StringView mach_server_name;
37-
bool enable_http_disk_cache = false;
37+
StringView http_disk_cache_mode;
3838
bool wait_for_debugger = false;
3939

4040
Core::ArgsParser args_parser;
4141
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
4242
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
43-
args_parser.add_option(enable_http_disk_cache, "Enable HTTP disk cache", "enable-http-disk-cache");
43+
args_parser.add_option(http_disk_cache_mode, "HTTP disk cache mode", "http-disk-cache-mode", 0, "mode");
4444
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
4545
args_parser.parse(arguments);
4646

@@ -58,8 +58,12 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
5858
Core::Platform::register_with_mach_server(mach_server_name);
5959
#endif
6060

61-
if (enable_http_disk_cache) {
62-
if (auto cache = RequestServer::DiskCache::create(); cache.is_error())
61+
if (http_disk_cache_mode.is_one_of("enabled"sv, "testing"sv)) {
62+
auto mode = http_disk_cache_mode == "enabled"sv
63+
? RequestServer::DiskCache::Mode::Normal
64+
: RequestServer::DiskCache::Mode::Testing;
65+
66+
if (auto cache = RequestServer::DiskCache::create(mode); cache.is_error())
6367
warnln("Unable to create disk cache: {}", cache.error());
6468
else
6569
RequestServer::g_disk_cache = cache.release_value();

0 commit comments

Comments
 (0)