ladybird/Services/RequestServer/main.cpp

110 lines
4 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <AK/Format.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Process.h>
#include <LibCore/System.h>
#include <LibHTTP/Cache/DiskCache.h>
#include <LibIPC/SingleServer.h>
#include <LibMain/Main.h>
#include <RequestServer/ConnectionFromClient.h>
#include <RequestServer/Resolver.h>
#include <RequestServer/ResourceSubstitutionMap.h>
namespace RequestServer {
OwnPtr<ResourceSubstitutionMap> g_resource_substitution_map;
}
#ifndef AK_OS_WINDOWS
static void handle_signal(int signal)
{
VERIFY(signal == SIGINT || signal == SIGTERM);
Core::EventLoop::current().quit(0);
}
#endif
ErrorOr<int> ladybird_main(Main::Arguments arguments)
{
AK::set_rich_debug_enabled(true);
Vector<ByteString> certificates;
StringView mach_server_name;
StringView http_disk_cache_mode;
StringView resource_map_path;
bool wait_for_debugger = false;
Core::ArgsParser args_parser;
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
args_parser.add_option(http_disk_cache_mode, "HTTP disk cache mode", "http-disk-cache-mode", 0, "mode");
args_parser.add_option(resource_map_path, "Path to JSON file mapping URLs to local files", "resource-map", 0, "path");
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
args_parser.parse(arguments);
if (wait_for_debugger)
Core::Process::wait_for_debugger_and_break();
// FIXME: Update RequestServer to support multiple custom root certificates.
if (!certificates.is_empty())
RequestServer::set_default_certificate_path(certificates.first());
if (!resource_map_path.is_empty()) {
auto map = RequestServer::ResourceSubstitutionMap::load_from_file(resource_map_path);
if (map.is_error())
warnln("Unable to load resource substitution map from '{}': {}", resource_map_path, map.error());
else
RequestServer::g_resource_substitution_map = map.release_value();
}
#if !defined(AK_OS_WINDOWS)
MUST(Core::System::signal(SIGPIPE, SIG_IGN));
#endif
Core::EventLoop event_loop;
// FIXME: Have another way to signal the event loop to gracefully quit on windows.
#ifndef AK_OS_WINDOWS
Core::EventLoop::register_signal(SIGINT, handle_signal);
Core::EventLoop::register_signal(SIGTERM, handle_signal);
#endif
Optional<HTTP::DiskCache> disk_cache;
if (http_disk_cache_mode != "disabled"sv) {
auto mode = TRY([&]() -> ErrorOr<HTTP::DiskCache::Mode> {
if (http_disk_cache_mode == "enabled"sv)
return HTTP::DiskCache::Mode::Normal;
if (http_disk_cache_mode == "partitioned"sv)
return HTTP::DiskCache::Mode::Partitioned;
if (http_disk_cache_mode == "testing"sv)
return HTTP::DiskCache::Mode::Testing;
return Error::from_string_literal("Unrecognized disk cache mode");
}());
if (auto cache = HTTP::DiskCache::create(mode); cache.is_error())
LibRequests+RequestServer: Begin implementing an HTTP disk cache This adds a disk cache for HTTP responses received from the network. For now, we take a rather conservative approach to caching. We don't cache a response until we're 100% sure it is cacheable (there are heuristics we can implement in the future based on the absence of specific headers). The cache is broken into 2 categories of files: 1. An index file. This is a SQL database containing metadata about each cache entry (URL, timestamps, etc.). 2. Cache files. Each cached response is in its own file. The file is an amalgamation of all info needed to reconstruct an HTTP response. This includes the status code, headers, body, etc. A cache entry is created once we receive the headers for a response. The index, however, is not updated at this point. We stream the body into the cache entry as it is received. Once we've successfully cached the entire body, we create an index entry in the database. If any of these steps failed along the way, the cache entry is removed and the index is left untouched. Subsequent requests are checked for cache hits from the index. If a hit is found, we read just enough of the cache entry to inform WebContent of the status code and headers. The body of the response is piped to WC via syscalls, such that the transfer happens entirely in the kernel; no need to allocate the memory for the body in userspace (WC still allocates a buffer to hold the data, of course). If an error occurs while piping the body, we currently error out the request. There is a FIXME to switch to a network request. Cache hits are also validated for freshness before they are used. If a response has expired, we remove it and its index entry, and proceed with a network request.
2025-10-07 20:59:21 -03:00
warnln("Unable to create disk cache: {}", cache.error());
else
disk_cache = cache.release_value();
LibRequests+RequestServer: Begin implementing an HTTP disk cache This adds a disk cache for HTTP responses received from the network. For now, we take a rather conservative approach to caching. We don't cache a response until we're 100% sure it is cacheable (there are heuristics we can implement in the future based on the absence of specific headers). The cache is broken into 2 categories of files: 1. An index file. This is a SQL database containing metadata about each cache entry (URL, timestamps, etc.). 2. Cache files. Each cached response is in its own file. The file is an amalgamation of all info needed to reconstruct an HTTP response. This includes the status code, headers, body, etc. A cache entry is created once we receive the headers for a response. The index, however, is not updated at this point. We stream the body into the cache entry as it is received. Once we've successfully cached the entire body, we create an index entry in the database. If any of these steps failed along the way, the cache entry is removed and the index is left untouched. Subsequent requests are checked for cache hits from the index. If a hit is found, we read just enough of the cache entry to inform WebContent of the status code and headers. The body of the response is piped to WC via syscalls, such that the transfer happens entirely in the kernel; no need to allocate the memory for the body in userspace (WC still allocates a buffer to hold the data, of course). If an error occurs while piping the body, we currently error out the request. There is a FIXME to switch to a network request. Cache hits are also validated for freshness before they are used. If a response has expired, we remove it and its index entry, and proceed with a network request.
2025-10-07 20:59:21 -03:00
}
// Connections are stored on the stack to ensure they are destroyed before static destruction begins. This prevents
// crashes from notifiers trying to unregister from already-destroyed thread data during process exit.
RequestServer::ConnectionFromClient::ConnectionMap connections;
auto client = TRY(IPC::take_over_accepted_client_from_system_server<RequestServer::ConnectionFromClient>(
mach_server_name,
RequestServer::ConnectionFromClient::IsPrimaryConnection::Yes, connections, disk_cache));
return event_loop.exec();
}