ladybird/Services/RequestServer/Cache/CacheEntry.h
Timothy Flynn 5384f84550 RequestServer: Create disk cache writers for new requests immediately
We previously waited until we received all response headers before we
would create the cache entry. We now create one immediately, and handle
writing the headers in its own function. This will allow us to know if
a cache entry writer already exists for a given cache key, and thus
prevent creating a second writer at the same time.
2025-10-28 11:52:51 +01:00

137 lines
3.9 KiB
C++

/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <AK/LexicalPath.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <LibCore/File.h>
#include <LibHTTP/HeaderMap.h>
#include <RequestServer/Forward.h>
namespace RequestServer {
struct [[gnu::packed]] CacheHeader {
static ErrorOr<CacheHeader> read_from_stream(Stream&);
ErrorOr<void> write_to_stream(Stream&) const;
static constexpr auto CACHE_MAGIC = 0xcafef00du;
static constexpr auto CACHE_VERSION = 1;
u32 magic { CACHE_MAGIC };
u32 version { CACHE_VERSION };
u32 url_size { 0 };
u32 url_hash { 0 };
u32 status_code { 0 };
u32 reason_phrase_size { 0 };
u32 reason_phrase_hash { 0 };
u32 headers_size { 0 };
u32 headers_hash { 0 };
};
struct [[gnu::packed]] CacheFooter {
static ErrorOr<CacheFooter> read_from_stream(Stream&);
ErrorOr<void> write_to_stream(Stream&) const;
u64 data_size { 0 };
u32 crc32 { 0 };
};
// A cache entry is an amalgamation of all information needed to reconstruct HTTP responses. It is created once we have
// received the response headers for a request. The body is streamed into the entry as it is received. The cache format
// on disk is:
//
// [CacheHeader][URL][ReasonPhrase][HttpHeaders][Data][CacheFooter]
class CacheEntry {
public:
virtual ~CacheEntry() = default;
void remove();
void mark_for_deletion(Badge<DiskCache>) { m_marked_for_deletion = true; }
protected:
CacheEntry(DiskCache&, CacheIndex&, u64 cache_key, String url, LexicalPath, CacheHeader);
void close_and_destroy_cache_entry();
DiskCache& m_disk_cache;
CacheIndex& m_index;
u64 m_cache_key { 0 };
String m_url;
LexicalPath m_path;
CacheHeader m_cache_header;
CacheFooter m_cache_footer;
bool m_marked_for_deletion { false };
};
class CacheEntryWriter : public CacheEntry {
public:
static ErrorOr<NonnullOwnPtr<CacheEntryWriter>> create(DiskCache&, CacheIndex&, u64 cache_key, String url, UnixDateTime request_time);
virtual ~CacheEntryWriter() override = default;
ErrorOr<void> write_headers(u32 status_code, Optional<String> reason_phrase, HTTP::HeaderMap const&);
ErrorOr<void> write_data(ReadonlyBytes);
ErrorOr<void> flush();
private:
CacheEntryWriter(DiskCache&, CacheIndex&, u64 cache_key, String url, LexicalPath, NonnullOwnPtr<Core::OutputBufferedFile>, CacheHeader, UnixDateTime request_time);
NonnullOwnPtr<Core::OutputBufferedFile> m_file;
UnixDateTime m_request_time;
UnixDateTime m_response_time;
};
class CacheEntryReader : public CacheEntry {
public:
static ErrorOr<NonnullOwnPtr<CacheEntryReader>> create(DiskCache&, CacheIndex&, u64 cache_key, u64 data_size);
virtual ~CacheEntryReader() override = default;
void pipe_to(int pipe_fd, Function<void(u64 bytes_piped)> on_complete, Function<void(u64 bytes_piped)> on_error);
u32 status_code() const { return m_cache_header.status_code; }
Optional<String> const& reason_phrase() const { return m_reason_phrase; }
HTTP::HeaderMap const& headers() const { return m_headers; }
private:
CacheEntryReader(DiskCache&, CacheIndex&, u64 cache_key, String url, LexicalPath, NonnullOwnPtr<Core::File>, int fd, CacheHeader, Optional<String> reason_phrase, HTTP::HeaderMap, u64 data_offset, u64 data_size);
void pipe_without_blocking();
void pipe_complete();
void pipe_error(Error);
ErrorOr<void> read_and_validate_footer();
NonnullOwnPtr<Core::File> m_file;
int m_fd { -1 };
RefPtr<Core::Notifier> m_pipe_write_notifier;
int m_pipe_fd { -1 };
Function<void(u64)> m_on_pipe_complete;
Function<void(u64)> m_on_pipe_error;
u64 m_bytes_piped { 0 };
Optional<String> m_reason_phrase;
HTTP::HeaderMap m_headers;
u64 const m_data_offset { 0 };
u64 const m_data_size { 0 };
};
}