Skip to content

Commit 20cd19b

Browse files
committed
RequestServer: Store HTTP response headers in the cache index
We currently store response headers in the cache entry file, before the response body. When we implement cache revalidation, we will need to update the stored response headers with whatever headers are received in a 304 response. It's not unlikely that those headers will have a size that differs from the stored headers. We would then have to rewrite the entire response body after the new headers. Instead of dealing with those inefficiencies, let's instead store the response headers in the cache index. This will allow us to update the headers with a simple SQL query.
1 parent bf7c5cd commit 20cd19b

File tree

7 files changed

+62
-72
lines changed

7 files changed

+62
-72
lines changed

Services/RequestServer/Cache/CacheEntry.cpp

Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
66

7-
#include <AK/JsonArray.h>
8-
#include <AK/JsonArraySerializer.h>
9-
#include <AK/JsonObject.h>
10-
#include <AK/JsonObjectSerializer.h>
11-
#include <AK/JsonValue.h>
127
#include <AK/ScopeGuard.h>
138
#include <LibCore/Notifier.h>
149
#include <LibCore/System.h>
@@ -35,8 +30,6 @@ ErrorOr<CacheHeader> CacheHeader::read_from_stream(Stream& stream)
3530
header.status_code = TRY(stream.read_value<u32>());
3631
header.reason_phrase_size = TRY(stream.read_value<u32>());
3732
header.reason_phrase_hash = TRY(stream.read_value<u32>());
38-
header.headers_size = TRY(stream.read_value<u32>());
39-
header.headers_hash = TRY(stream.read_value<u32>());
4033
return header;
4134
}
4235

@@ -49,8 +42,6 @@ ErrorOr<void> CacheHeader::write_to_stream(Stream& stream) const
4942
TRY(stream.write_value(status_code));
5043
TRY(stream.write_value(reason_phrase_size));
5144
TRY(stream.write_value(reason_phrase_hash));
52-
TRY(stream.write_value(headers_size));
53-
TRY(stream.write_value(headers_hash));
5445
return {};
5546
}
5647

@@ -112,7 +103,7 @@ CacheEntryWriter::CacheEntryWriter(DiskCache& disk_cache, CacheIndex& index, u64
112103
{
113104
}
114105

115-
ErrorOr<void> CacheEntryWriter::write_headers(u32 status_code, Optional<String> reason_phrase, HTTP::HeaderMap const& headers)
106+
ErrorOr<void> CacheEntryWriter::write_status_and_reason(u32 status_code, Optional<String> reason_phrase, HTTP::HeaderMap const& headers)
116107
{
117108
if (m_marked_for_deletion) {
118109
close_and_destroy_cache_entry();
@@ -133,35 +124,16 @@ ErrorOr<void> CacheEntryWriter::write_headers(u32 status_code, Optional<String>
133124
if (auto freshness = calculate_freshness_lifetime(headers); freshness.is_negative() || freshness.is_zero())
134125
return Error::from_string_literal("Response has already expired");
135126

136-
StringBuilder builder;
137-
auto headers_serializer = TRY(JsonArraySerializer<>::try_create(builder));
138-
139-
for (auto const& header : headers.headers()) {
140-
if (is_header_exempted_from_storage(header.name))
141-
continue;
142-
143-
auto header_serializer = TRY(headers_serializer.add_object());
144-
TRY(header_serializer.add("name"sv, header.name));
145-
TRY(header_serializer.add("value"sv, header.value));
146-
TRY(header_serializer.finish());
147-
}
148-
149-
TRY(headers_serializer.finish());
150-
auto serialized_headers = builder.string_view();
151-
m_cache_header.headers_size = serialized_headers.length();
152-
m_cache_header.headers_hash = serialized_headers.hash();
153-
154127
TRY(m_file->write_value(m_cache_header));
155128
TRY(m_file->write_until_depleted(m_url));
156129
if (reason_phrase.has_value())
157130
TRY(m_file->write_until_depleted(*reason_phrase));
158-
TRY(m_file->write_until_depleted(serialized_headers));
159131

160132
return {};
161133
}();
162134

163135
if (result.is_error()) {
164-
dbgln("\033[31;1mUnable to write headers to cache entry for\033[0m {}: {}", m_url, result.error());
136+
dbgln("\033[31;1mUnable to write status/reason to cache entry for\033[0m {}: {}", m_url, result.error());
165137

166138
remove();
167139
close_and_destroy_cache_entry();
@@ -194,7 +166,7 @@ ErrorOr<void> CacheEntryWriter::write_data(ReadonlyBytes data)
194166
return {};
195167
}
196168

197-
ErrorOr<void> CacheEntryWriter::flush()
169+
ErrorOr<void> CacheEntryWriter::flush(HTTP::HeaderMap headers)
198170
{
199171
ScopeGuard guard { [&]() { close_and_destroy_cache_entry(); } };
200172

@@ -208,13 +180,13 @@ ErrorOr<void> CacheEntryWriter::flush()
208180
return result.release_error();
209181
}
210182

211-
m_index.create_entry(m_cache_key, m_url, m_cache_footer.data_size, m_request_time, m_response_time);
183+
m_index.create_entry(m_cache_key, m_url, move(headers), m_cache_footer.data_size, m_request_time, m_response_time);
212184

213185
dbgln("\033[34;1mFinished caching\033[0m {} ({} bytes)", m_url, m_cache_footer.data_size);
214186
return {};
215187
}
216188

217-
ErrorOr<NonnullOwnPtr<CacheEntryReader>> CacheEntryReader::create(DiskCache& disk_cache, CacheIndex& index, u64 cache_key, u64 data_size)
189+
ErrorOr<NonnullOwnPtr<CacheEntryReader>> CacheEntryReader::create(DiskCache& disk_cache, CacheIndex& index, u64 cache_key, HTTP::HeaderMap headers, u64 data_size)
218190
{
219191
auto path = path_for_cache_key(disk_cache.cache_directory(), cache_key);
220192

@@ -225,7 +197,6 @@ ErrorOr<NonnullOwnPtr<CacheEntryReader>> CacheEntryReader::create(DiskCache& dis
225197

226198
String url;
227199
Optional<String> reason_phrase;
228-
HTTP::HeaderMap headers;
229200

230201
auto result = [&]() -> ErrorOr<void> {
231202
cache_header = TRY(file->read_value<CacheHeader>());
@@ -245,28 +216,6 @@ ErrorOr<NonnullOwnPtr<CacheEntryReader>> CacheEntryReader::create(DiskCache& dis
245216
return Error::from_string_literal("Reason phrase hash mismatch");
246217
}
247218

248-
auto serialized_headers = TRY(String::from_stream(*file, cache_header.headers_size));
249-
if (serialized_headers.hash() != cache_header.headers_hash)
250-
return Error::from_string_literal("HTTP headers hash mismatch");
251-
252-
auto json_headers = TRY(JsonValue::from_string(serialized_headers));
253-
if (!json_headers.is_array())
254-
return Error::from_string_literal("Expected HTTP headers to be a JSON array");
255-
256-
TRY(json_headers.as_array().try_for_each([&](JsonValue const& header) -> ErrorOr<void> {
257-
if (!header.is_object())
258-
return Error::from_string_literal("Expected headers entry to be a JSON object");
259-
260-
auto name = header.as_object().get_string("name"sv);
261-
auto value = header.as_object().get_string("value"sv);
262-
263-
if (!name.has_value() || !value.has_value())
264-
return Error::from_string_literal("Missing/invalid data in headers entry");
265-
266-
headers.set(name->to_byte_string(), value->to_byte_string());
267-
return {};
268-
}));
269-
270219
return {};
271220
}();
272221

@@ -275,7 +224,7 @@ ErrorOr<NonnullOwnPtr<CacheEntryReader>> CacheEntryReader::create(DiskCache& dis
275224
return result.release_error();
276225
}
277226

278-
auto data_offset = sizeof(CacheHeader) + cache_header.url_size + cache_header.reason_phrase_size + cache_header.headers_size;
227+
auto data_offset = sizeof(CacheHeader) + cache_header.url_size + cache_header.reason_phrase_size;
279228

280229
return adopt_own(*new CacheEntryReader { disk_cache, index, cache_key, move(url), move(path), move(file), fd, cache_header, move(reason_phrase), move(headers), data_offset, data_size });
281230
}

Services/RequestServer/Cache/CacheEntry.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@ struct [[gnu::packed]] CacheHeader {
3333
u32 status_code { 0 };
3434
u32 reason_phrase_size { 0 };
3535
u32 reason_phrase_hash { 0 };
36-
37-
u32 headers_size { 0 };
38-
u32 headers_hash { 0 };
3936
};
4037

4138
struct [[gnu::packed]] CacheFooter {
@@ -85,9 +82,9 @@ class CacheEntryWriter : public CacheEntry {
8582
static ErrorOr<NonnullOwnPtr<CacheEntryWriter>> create(DiskCache&, CacheIndex&, u64 cache_key, String url, UnixDateTime request_time);
8683
virtual ~CacheEntryWriter() override = default;
8784

88-
ErrorOr<void> write_headers(u32 status_code, Optional<String> reason_phrase, HTTP::HeaderMap const&);
85+
ErrorOr<void> write_status_and_reason(u32 status_code, Optional<String> reason_phrase, HTTP::HeaderMap const&);
8986
ErrorOr<void> write_data(ReadonlyBytes);
90-
ErrorOr<void> flush();
87+
ErrorOr<void> flush(HTTP::HeaderMap);
9188

9289
private:
9390
CacheEntryWriter(DiskCache&, CacheIndex&, u64 cache_key, String url, LexicalPath, NonnullOwnPtr<Core::OutputBufferedFile>, CacheHeader, UnixDateTime request_time);
@@ -100,7 +97,7 @@ class CacheEntryWriter : public CacheEntry {
10097

10198
class CacheEntryReader : public CacheEntry {
10299
public:
103-
static ErrorOr<NonnullOwnPtr<CacheEntryReader>> create(DiskCache&, CacheIndex&, u64 cache_key, u64 data_size);
100+
static ErrorOr<NonnullOwnPtr<CacheEntryReader>> create(DiskCache&, CacheIndex&, u64 cache_key, HTTP::HeaderMap, u64 data_size);
104101
virtual ~CacheEntryReader() override = default;
105102

106103
void pipe_to(int pipe_fd, Function<void(u64 bytes_piped)> on_complete, Function<void(u64 bytes_piped)> on_error);

Services/RequestServer/Cache/CacheIndex.cpp

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,52 @@
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
66

7+
#include <AK/StringBuilder.h>
78
#include <RequestServer/Cache/CacheIndex.h>
9+
#include <RequestServer/Cache/Utilities.h>
810
#include <RequestServer/Cache/Version.h>
911

1012
namespace RequestServer {
1113

1214
static constexpr u32 CACHE_METADATA_KEY = 12389u;
1315

16+
static ByteString serialize_headers(HTTP::HeaderMap const& headers)
17+
{
18+
StringBuilder builder;
19+
20+
for (auto const& header : headers.headers()) {
21+
if (is_header_exempted_from_storage(header.name))
22+
continue;
23+
24+
builder.append(header.name);
25+
builder.append(':');
26+
builder.append(header.value);
27+
builder.append('\n');
28+
}
29+
30+
return builder.to_byte_string();
31+
}
32+
33+
static HTTP::HeaderMap deserialize_headers(StringView serialized_headers)
34+
{
35+
HTTP::HeaderMap headers;
36+
37+
serialized_headers.for_each_split_view('\n', SplitBehavior::Nothing, [&](StringView serialized_header) {
38+
auto index = serialized_header.find(':');
39+
if (!index.has_value())
40+
return;
41+
42+
auto name = serialized_header.substring_view(0, *index).trim_whitespace();
43+
if (is_header_exempted_from_storage(name))
44+
return;
45+
46+
auto value = serialized_header.substring_view(*index + 1).trim_whitespace();
47+
headers.set(name, value);
48+
});
49+
50+
return headers;
51+
}
52+
1453
ErrorOr<CacheIndex> CacheIndex::create(Database::Database& database)
1554
{
1655
auto create_cache_metadata_table = TRY(database.prepare_statement(R"#(
@@ -45,6 +84,7 @@ ErrorOr<CacheIndex> CacheIndex::create(Database::Database& database)
4584
CREATE TABLE IF NOT EXISTS CacheIndex (
4685
cache_key INTEGER,
4786
url TEXT,
87+
response_headers TEXT,
4888
data_size INTEGER,
4989
request_time INTEGER,
5090
response_time INTEGER,
@@ -55,7 +95,7 @@ ErrorOr<CacheIndex> CacheIndex::create(Database::Database& database)
5595
database.execute_statement(create_cache_index_table, {});
5696

5797
Statements statements {};
58-
statements.insert_entry = TRY(database.prepare_statement("INSERT OR REPLACE INTO CacheIndex VALUES (?, ?, ?, ?, ?, ?);"sv));
98+
statements.insert_entry = TRY(database.prepare_statement("INSERT OR REPLACE INTO CacheIndex VALUES (?, ?, ?, ?, ?, ?, ?);"sv));
5999
statements.remove_entry = TRY(database.prepare_statement("DELETE FROM CacheIndex WHERE cache_key = ?;"sv));
60100
statements.remove_all_entries = TRY(database.prepare_statement("DELETE FROM CacheIndex;"sv));
61101
statements.select_entry = TRY(database.prepare_statement("SELECT * FROM CacheIndex WHERE cache_key = ?;"sv));
@@ -70,20 +110,21 @@ CacheIndex::CacheIndex(Database::Database& database, Statements statements)
70110
{
71111
}
72112

73-
void CacheIndex::create_entry(u64 cache_key, String url, u64 data_size, UnixDateTime request_time, UnixDateTime response_time)
113+
void CacheIndex::create_entry(u64 cache_key, String url, HTTP::HeaderMap response_headers, u64 data_size, UnixDateTime request_time, UnixDateTime response_time)
74114
{
75115
auto now = UnixDateTime::now();
76116

77117
Entry entry {
78118
.cache_key = cache_key,
79119
.url = move(url),
120+
.response_headers = move(response_headers),
80121
.data_size = data_size,
81122
.request_time = request_time,
82123
.response_time = response_time,
83124
.last_access_time = now,
84125
};
85126

86-
m_database.execute_statement(m_statements.insert_entry, {}, entry.cache_key, entry.url, entry.data_size, entry.request_time, entry.response_time, entry.last_access_time);
127+
m_database.execute_statement(m_statements.insert_entry, {}, entry.cache_key, entry.url, serialize_headers(entry.response_headers), entry.data_size, entry.request_time, entry.response_time, entry.last_access_time);
87128
m_entries.set(cache_key, move(entry));
88129
}
89130

@@ -122,12 +163,13 @@ Optional<CacheIndex::Entry&> CacheIndex::find_entry(u64 cache_key)
122163

123164
auto cache_key = m_database.result_column<u64>(statement_id, column++);
124165
auto url = m_database.result_column<String>(statement_id, column++);
166+
auto response_headers = m_database.result_column<ByteString>(statement_id, column++);
125167
auto data_size = m_database.result_column<u64>(statement_id, column++);
126168
auto request_time = m_database.result_column<UnixDateTime>(statement_id, column++);
127169
auto response_time = m_database.result_column<UnixDateTime>(statement_id, column++);
128170
auto last_access_time = m_database.result_column<UnixDateTime>(statement_id, column++);
129171

130-
Entry entry { cache_key, move(url), data_size, request_time, response_time, last_access_time };
172+
Entry entry { cache_key, move(url), deserialize_headers(response_headers), data_size, request_time, response_time, last_access_time };
131173
m_entries.set(cache_key, move(entry));
132174
},
133175
cache_key);

Services/RequestServer/Cache/CacheIndex.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <AK/Time.h>
1212
#include <AK/Types.h>
1313
#include <LibDatabase/Database.h>
14+
#include <LibHTTP/HeaderMap.h>
1415

1516
namespace RequestServer {
1617

@@ -21,6 +22,7 @@ class CacheIndex {
2122
u64 cache_key { 0 };
2223

2324
String url;
25+
HTTP::HeaderMap response_headers;
2426
u64 data_size { 0 };
2527

2628
UnixDateTime request_time;
@@ -31,7 +33,7 @@ class CacheIndex {
3133
public:
3234
static ErrorOr<CacheIndex> create(Database::Database&);
3335

34-
void create_entry(u64 cache_key, String url, u64 data_size, UnixDateTime request_time, UnixDateTime response_time);
36+
void create_entry(u64 cache_key, String url, HTTP::HeaderMap, u64 data_size, UnixDateTime request_time, UnixDateTime response_time);
3537
void remove_entry(u64 cache_key);
3638
void remove_all_entries();
3739

Services/RequestServer/Cache/DiskCache.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ Variant<Optional<CacheEntryReader&>, DiskCache::CacheHasOpenEntry> DiskCache::op
7575
return Optional<CacheEntryReader&> {};
7676
}
7777

78-
auto cache_entry = CacheEntryReader::create(*this, m_index, cache_key, index_entry->data_size);
78+
auto cache_entry = CacheEntryReader::create(*this, m_index, cache_key, index_entry->response_headers, index_entry->data_size);
7979
if (cache_entry.is_error()) {
8080
dbgln("\033[31;1mUnable to open cache entry for\033[0m {}: {}", request.url(), cache_entry.error());
8181
m_index.remove_entry(cache_key);

Services/RequestServer/Cache/Version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
namespace RequestServer {
1212

1313
// Increment this version when a breaking change is made to the cache index or cache entry formats.
14-
static constexpr inline u32 CACHE_VERSION = 1u;
14+
static constexpr inline u32 CACHE_VERSION = 2u;
1515

1616
}

Services/RequestServer/Request.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Request::~Request()
117117
curl_slist_free_all(string_list);
118118

119119
if (m_cache_entry_writer.has_value())
120-
(void)m_cache_entry_writer->flush();
120+
(void)m_cache_entry_writer->flush(move(m_response_headers));
121121
}
122122

123123
void Request::notify_request_unblocked(Badge<DiskCache>)
@@ -520,7 +520,7 @@ void Request::transfer_headers_to_client_if_needed()
520520
m_client.async_headers_became_available(m_request_id, m_response_headers, m_status_code, m_reason_phrase);
521521

522522
if (m_cache_entry_writer.has_value()) {
523-
if (m_cache_entry_writer->write_headers(m_status_code, m_reason_phrase, m_response_headers).is_error())
523+
if (m_cache_entry_writer->write_status_and_reason(m_status_code, m_reason_phrase, m_response_headers).is_error())
524524
m_cache_entry_writer.clear();
525525
}
526526
}

0 commit comments

Comments
 (0)