From 00afac8ce18652740a3aeb2a0d4e6f2fb4db5e91 Mon Sep 17 00:00:00 2001 From: RippeR37 Date: Tue, 25 Mar 2025 20:28:01 +0100 Subject: [PATCH 1/3] [#41] Implement simple networking stack based on libcurl This commit adds simple networking functionality built on top of libcurl library. Main implementation focuses on introduction of new (optional) thread (NetThread) that will be used to process networking I/O operations and some simple utilities (`SimpleUrlLoader` class) that can be used to make network requests. For now only simple functionality exists: * Simple fetching via GET/HEAD (single callback when request finishes) * Optionally providing custom headers for the requests * Reading response headers * Specifying timeout details * Reading response timing details This will be expanded in the followup commits. --- CMakeLists.txt | 2 + examples/simple/main.cc | 59 +++++- src/CMakeLists.txt | 38 ++-- src/base/init.cc | 32 ++- src/base/init.h | 12 +- src/base/net/impl/net_thread.cc | 40 ++++ src/base/net/impl/net_thread.h | 31 +++ src/base/net/impl/net_thread_impl.cc | 302 +++++++++++++++++++++++++++ src/base/net/impl/net_thread_impl.h | 65 ++++++ src/base/net/resource_request.h | 25 +++ src/base/net/resource_response.h | 28 +++ src/base/net/result.h | 18 ++ src/base/net/simple_url_loader.cc | 24 +++ src/base/net/simple_url_loader.h | 25 +++ tests/perf/main.cc | 2 +- tests/unit/main.cc | 2 +- vcpkg.json | 6 + 17 files changed, 688 insertions(+), 23 deletions(-) create mode 100644 src/base/net/impl/net_thread.cc create mode 100644 src/base/net/impl/net_thread.h create mode 100644 src/base/net/impl/net_thread_impl.cc create mode 100644 src/base/net/impl/net_thread_impl.h create mode 100644 src/base/net/resource_request.h create mode 100644 src/base/net/resource_response.h create mode 100644 src/base/net/result.h create mode 100644 src/base/net/simple_url_loader.cc create mode 100644 src/base/net/simple_url_loader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a45b4014c..268d712c53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,8 @@ message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") find_package(Threads REQUIRED) # GLOG find_package(glog CONFIG REQUIRED) +# curl +find_package(CURL CONFIG REQUIRED) # diff --git a/examples/simple/main.cc b/examples/simple/main.cc index c3af257af1..88df546f22 100644 --- a/examples/simple/main.cc +++ b/examples/simple/main.cc @@ -1,9 +1,11 @@ #include #include "base/bind.h" +#include "base/bind_post_task.h" #include "base/callback.h" #include "base/init.h" #include "base/logging.h" +#include "base/net/simple_url_loader.h" #include "base/synchronization/auto_signaller.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" @@ -138,8 +140,61 @@ void ThreadPoolSingleThreadExample() { pool.Stop(); } +void NetExample() { + base::Thread thread{}; + thread.Start(); + + base::WaitableEvent finished_event{}; + auto response_callback = base::BindOnce( + [](base::WaitableEvent* event, base::net::ResourceResponse response) { + LOG(INFO) << "Result: " << static_cast(response.result); + LOG(INFO) << "HTTP code: " << response.code; + LOG(INFO) << "Final URL: " << response.final_url; + LOG(INFO) << "Downloaded " << response.data.size() << " bytes"; + LOG(INFO) << "Latency: " << response.timing_connect.InMilliseconds() + << "ms"; + LOG(INFO) << "Headers"; + for (const auto& [h, v] : response.headers) { + LOG(INFO) << " " << h << ": " << v; + } + LOG(INFO) << "Content:\n" + << std::string{response.data.begin(), response.data.end()}; + event->Signal(); + }, + &finished_event); + + // Try to download and signal on finish + base::net::SimpleUrlLoader::DownloadUnbounded( + base::net::ResourceRequest{ + "https://www.google.com/robots.txt", + base::net::kDefaultHeaders, + base::net::kNoTimeout, + base::net::kNoTimeout, + true, + true, + }, + base::BindPostTask(thread.TaskRunner(), std::move(response_callback), + FROM_HERE)); + + // Timeout = 5s + thread.TaskRunner()->PostDelayedTask( + FROM_HERE, + base::BindOnce( + [](base::WaitableEvent* event) { + LOG(WARNING) << "Failed to download in 5 seconds"; + event->Signal(); + }, + &finished_event), + base::Seconds(5)); + + finished_event.Wait(); +} + int main(int argc, char* argv[]) { - base::Initialize(argc, argv); + base::InitOptions init_options; + init_options.InitializeNetworking = true; + + base::Initialize(argc, argv, std::move(init_options)); const auto timer = base::ElapsedTimer{}; @@ -149,6 +204,8 @@ int main(int argc, char* argv[]) { ThreadPoolSequencedExample(); ThreadPoolSingleThreadExample(); + NetExample(); + LOG(INFO) << "Example finished in " << timer.Elapsed().InMillisecondsF() << "ms"; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b38937cc99..2a59658881 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,7 +32,8 @@ target_include_directories(libbase PUBLIC target_link_libraries(libbase PUBLIC ${LIBBASE_LINK_FLAGS} Threads::Threads - glog::glog) + glog::glog + CURL::libcurl) target_sources(libbase PRIVATE @@ -40,69 +41,78 @@ target_sources(libbase base/barrier_callback.h base/barrier_closure.cc base/barrier_closure.h - base/bind.h base/bind_internals.h base/bind_post_task.h - base/callback.h + base/bind.h base/callback_forward.h base/callback_helpers.cc base/callback_helpers.h base/callback_iface.h base/callback_internals.h + base/callback.h base/init.cc base/init.h base/logging.cc base/logging.h base/memory/weak_ptr.h - base/message_loop/message_loop.h base/message_loop/message_loop_impl.cc base/message_loop/message_loop_impl.h - base/message_loop/message_pump.h + base/message_loop/message_loop.h base/message_loop/message_pump_impl.cc base/message_loop/message_pump_impl.h + base/message_loop/message_pump.h + base/net/impl/net_thread_impl.cc + base/net/impl/net_thread_impl.h + base/net/impl/net_thread.cc + base/net/impl/net_thread.h + base/net/resource_request.h + base/net/resource_response.h + base/net/result.h + base/net/simple_url_loader.cc + base/net/simple_url_loader.h base/sequence_checker.cc base/sequence_checker.h base/sequence_id.cc base/sequence_id.h - base/sequenced_task_runner.h base/sequenced_task_runner_helpers.cc base/sequenced_task_runner_helpers.h base/sequenced_task_runner_internals.h + base/sequenced_task_runner.h base/single_thread_task_runner.h base/source_location.h base/synchronization/auto_signaller.cc base/synchronization/auto_signaller.h base/synchronization/waitable_event.cc base/synchronization/waitable_event.h + base/task_runner_internals.h base/task_runner.cc base/task_runner.h - base/task_runner_internals.h - base/threading/delayed_task_manager.cc - base/threading/delayed_task_manager.h base/threading/delayed_task_manager_shared_instance.cc base/threading/delayed_task_manager_shared_instance.h + base/threading/delayed_task_manager.cc + base/threading/delayed_task_manager.h base/threading/sequenced_task_runner_handle.cc base/threading/sequenced_task_runner_handle.h base/threading/task_runner_impl.cc base/threading/task_runner_impl.h - base/threading/thread.cc - base/threading/thread.h base/threading/thread_pool.cc base/threading/thread_pool.h - base/time/time.cc - base/time/time.h + base/threading/thread.cc + base/threading/thread.h base/time/time_delta.cc base/time/time_delta.h base/time/time_ticks.cc base/time/time_ticks.h + base/time/time.cc + base/time/time.h base/timer/elapsed_timer.cc base/timer/elapsed_timer.h base/trace_event/trace_argument_packer.h base/trace_event/trace_async.h base/trace_event/trace_complete.h base/trace_event/trace_counter.h - base/trace_event/trace_event.h base/trace_event/trace_event_register.h + base/trace_event/trace_event.h base/trace_event/trace_events.cc base/trace_event/trace_events.h base/trace_event/trace_flow.h diff --git a/src/base/init.cc b/src/base/init.cc index a42139aa59..a735f9fc9c 100644 --- a/src/base/init.cc +++ b/src/base/init.cc @@ -1,27 +1,51 @@ #include "base/init.h" #include "base/logging.h" +#include "base/net/impl/net_thread.h" namespace base { +namespace { +InitOptions g_init_options; +} + // NOLINTNEXTLINE(modernize-avoid-c-arrays) -void Initialize(int /*argc*/, char* argv[]) { - FLAGS_logtostderr = true; +void Initialize(int /*argc*/, char* argv[], InitOptions options) { + g_init_options = options; + + FLAGS_logtostderr = g_init_options.LogToStderr; google::InitGoogleLogging(argv[0]); google::InstallPrefixFormatter(&detail::LogFormatter); google::InstallFailureSignalHandler(); + + if (g_init_options.InitializeNetworking) { + // Initialize Networking thread + net::NetThread::GetInstance().Start(); + } } // NOLINTNEXTLINE(modernize-avoid-c-arrays) -void InitializeForTests(int /*argc*/, char* argv[]) { - FLAGS_logtostderr = true; +void InitializeForTests(int /*argc*/, char* argv[], InitOptions options) { + g_init_options = options; + + FLAGS_logtostderr = g_init_options.LogToStderr; google::InitGoogleLogging(argv[0]); google::InstallPrefixFormatter(&detail::LogFormatter); + + if (g_init_options.InitializeNetworking) { + // Initialize Networking thread + net::NetThread::GetInstance().Start(); + } } void Deinitialize() { + if (g_init_options.InitializeNetworking) { + // Deinitialize Networking thread + net::NetThread::GetInstance().Stop(); + } + google::ShutdownGoogleLogging(); } diff --git a/src/base/init.h b/src/base/init.h index f26a956b31..b401e85de4 100644 --- a/src/base/init.h +++ b/src/base/init.h @@ -2,10 +2,18 @@ namespace base { +struct InitOptions { + // Logging + bool LogToStderr = true; + + // Networking + bool InitializeNetworking = false; +}; + // NOLINTNEXTLINE(modernize-avoid-c-arrays) -void Initialize(int argc, char* argv[]); +void Initialize(int argc, char* argv[], InitOptions options); // NOLINTNEXTLINE(modernize-avoid-c-arrays) -void InitializeForTests(int argc, char* argv[]); +void InitializeForTests(int argc, char* argv[], InitOptions options); void Deinitialize(); } // namespace base diff --git a/src/base/net/impl/net_thread.cc b/src/base/net/impl/net_thread.cc new file mode 100644 index 0000000000..3a74b07e75 --- /dev/null +++ b/src/base/net/impl/net_thread.cc @@ -0,0 +1,40 @@ +#include "base/net/impl/net_thread.h" + +#include "base/logging.h" +#include "base/net/impl/net_thread_impl.h" + +namespace base { +namespace net { + +// static +NetThread& NetThread::GetInstance() { + static NetThread instance; + return instance; +} + +void NetThread::Start() { + DCHECK(!impl_); + + impl_ = std::make_unique(); + impl_->Start(); +} + +void NetThread::Stop() { + DCHECK(impl_); + + impl_->Stop(); + impl_.reset(); +} + +void NetThread::EnqueueDownload( + ResourceRequest request, + std::optional max_response_size_bytes, + OnceCallback on_done_callback) { + DCHECK(impl_); + + impl_->EnqueueDownload(std::move(request), std::move(max_response_size_bytes), + std::move(on_done_callback)); +} + +} // namespace net +} // namespace base diff --git a/src/base/net/impl/net_thread.h b/src/base/net/impl/net_thread.h new file mode 100644 index 0000000000..c498f6125c --- /dev/null +++ b/src/base/net/impl/net_thread.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "base/callback.h" +#include "base/net/resource_request.h" +#include "base/net/resource_response.h" + +namespace base { +namespace net { + +class NetThread { + public: + static NetThread& GetInstance(); + + void Start(); + void Stop(); + + void EnqueueDownload(ResourceRequest request, + std::optional max_response_size_bytes, + OnceCallback on_done_callback); + + private: + class NetThreadImpl; + + std::unique_ptr impl_; +}; + +} // namespace net +} // namespace base diff --git a/src/base/net/impl/net_thread_impl.cc b/src/base/net/impl/net_thread_impl.cc new file mode 100644 index 0000000000..bde56a491c --- /dev/null +++ b/src/base/net/impl/net_thread_impl.cc @@ -0,0 +1,302 @@ +#include "base/net/impl/net_thread_impl.h" + +namespace base { +namespace net { + +namespace { +Result CurlCodeToNetResult(CURLcode code) { + switch (code) { + case CURLE_OK: + return Result::kOk; + + case CURLE_ABORTED_BY_CALLBACK: + return Result::kAborted; + + default: + return Result::kError; + } +} +} // namespace + +struct NetThread::NetThreadImpl::DownloadInfo { + ResourceRequest request; + std::optional max_response_size_bytes; + + struct curl_slist* headers = nullptr; + + ResourceResponse response; + OnceCallback on_done_callback; +}; + +// +// Logic executed on main thread +// + +NetThread::NetThreadImpl::NetThreadImpl() + : not_quit_(true), not_modified_(true), multi_handle_(nullptr) { + curl_global_init(CURL_GLOBAL_DEFAULT); +} + +NetThread::NetThreadImpl::~NetThreadImpl() { + if (thread_) { + Stop(); + } + + curl_global_cleanup(); +} + +void NetThread::NetThreadImpl::Start() { + CHECK(!thread_); + + not_quit_.test_and_set(); + not_modified_.test_and_set(); + + multi_handle_ = curl_multi_init(); + thread_ = + std::make_unique(&NetThreadImpl::RunLoop_NetThread, this); +} + +void NetThread::NetThreadImpl::Stop() { + not_quit_.clear(); + curl_multi_wakeup(multi_handle_); + + thread_->join(); + thread_.reset(); + + curl_multi_cleanup(multi_handle_); + multi_handle_ = nullptr; +} + +void NetThread::NetThreadImpl::EnqueueDownload( + ResourceRequest request, + std::optional max_response_size_bytes, + OnceCallback on_done_callback) { + DCHECK(thread_); + DCHECK(on_done_callback); + + { + std::unique_lock lock(mutex_); + pending_add_downloads_.push_back(DownloadInfo{ + std::move(request), + std::move(max_response_size_bytes), + nullptr, + {}, + std::move(on_done_callback), + }); + } + + not_modified_.clear(); + curl_multi_wakeup(multi_handle_); +} + +// +// Logic executed on NetThread +// + +void NetThread::NetThreadImpl::RunLoop_NetThread() { + while (not_quit_.test_and_set()) { + ProcessPendingActions_NetThread(); + Perform_NetThread(); + ProcessCompleted_NetThread(); + Wait_NetThread(); + } + + AbortAllDownloads_NetThread(); +} + +void NetThread::NetThreadImpl::ProcessPendingActions_NetThread() { + if (!not_modified_.test_and_set()) { + std::unique_lock lock(mutex_); + for (DownloadInfo& download : pending_add_downloads_) { + EnqueueDownload_NetThread(download); + } + pending_add_downloads_.clear(); + // TODO: handle pending_cancel_downloads_ + } +} + +void NetThread::NetThreadImpl::Perform_NetThread() { + int active = 0; + CURLMcode result = CURLM_OK; + do { + result = curl_multi_perform(multi_handle_, &active); + } while (result == CURLM_CALL_MULTI_PERFORM); + + if (result != CURLM_OK) { + LOG(ERROR) << __FUNCTION__ << "() curl_multi_perform failed: " + << curl_multi_strerror(result); + AbortAllDownloads_NetThread(); + } +} + +void NetThread::NetThreadImpl::ProcessCompleted_NetThread() { + CURLMsg* msg = nullptr; + int msgs_left = 0; + while ((msg = curl_multi_info_read(multi_handle_, &msgs_left))) { + if (msg->msg == CURLMSG_DONE) { + DownloadFinished_NetThread(msg->easy_handle, + CurlCodeToNetResult(msg->data.result)); + } + } +} + +void NetThread::NetThreadImpl::Wait_NetThread() { + const auto kWaitTimeout = base::Milliseconds(1000); + + int numfds = 0; + CURLMcode result = + curl_multi_poll(multi_handle_, nullptr, 0, + static_cast(kWaitTimeout.InMilliseconds()), &numfds); + if (result != CURLM_OK) { + LOG(ERROR) << __FUNCTION__ + << "() curl_multi_poll failed: " << curl_multi_strerror(result); + } +} + +void NetThread::NetThreadImpl::EnqueueDownload_NetThread( + DownloadInfo& download_info) { + CURL* easy_handle = curl_easy_init(); + + // Set easy handle's options + curl_easy_setopt(easy_handle, CURLOPT_PRIVATE, this); + curl_easy_setopt(easy_handle, CURLOPT_URL, download_info.request.url.c_str()); + if (download_info.request.follow_redirects) { + curl_easy_setopt(easy_handle, CURLOPT_FOLLOWLOCATION, 1L); + } + if (download_info.request.headers_only) { + curl_easy_setopt(easy_handle, CURLOPT_NOBODY, 1L); + } + for (const auto& header : download_info.request.headers) { + download_info.headers = + curl_slist_append(download_info.headers, header.c_str()); + } + if (download_info.headers) { + curl_easy_setopt(easy_handle, CURLOPT_HTTPHEADER, download_info.headers); + } + if (!download_info.request.connect_timeout.IsZero()) { + curl_easy_setopt(easy_handle, CURLOPT_CONNECTTIMEOUT_MS, + download_info.request.connect_timeout.InMilliseconds()); + } + if (!download_info.request.timeout.IsZero()) { + curl_easy_setopt(easy_handle, CURLOPT_TIMEOUT_MS, + download_info.request.timeout.InMilliseconds()); + } + + // Save download request/response data + active_downloads_.insert({easy_handle, std::move(download_info)}); + const auto& info = active_downloads_[easy_handle]; + + // WARNING: Lambda has to be converted to function pointer or it will crash! + curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, (void*)(&info)); + curl_easy_setopt( + easy_handle, CURLOPT_WRITEFUNCTION, + +[](char* data, size_t n, size_t l, void* userp) -> size_t { + auto* info = static_cast(userp); + if (info->max_response_size_bytes && + info->response.data.size() + (n * l) > + *info->max_response_size_bytes) { + // Force error + return 0; + } + info->response.data.insert(info->response.data.end(), + reinterpret_cast(data), + reinterpret_cast(data) + n * l); + return n * l; + }); + + curl_multi_add_handle(multi_handle_, easy_handle); +} + +void NetThread::NetThreadImpl::DownloadFinished_NetThread(CURL* finished_curl, + Result result) { + CHECK_GT(active_downloads_.count(finished_curl), 0); + + auto& download = active_downloads_[finished_curl]; + + // Result + download.response.result = result; + + // Response code + if (download.response.result == Result::kOk) { + long http_code = 0; + curl_easy_getinfo(finished_curl, CURLINFO_RESPONSE_CODE, &http_code); + download.response.code = static_cast(http_code); + } else { + download.response.code = -1; + } + + // Get the URL of the completed transfer + { + char* final_url = nullptr; + curl_easy_getinfo(finished_curl, CURLINFO_EFFECTIVE_URL, &final_url); + + download.response.final_url = final_url ? final_url : ""; + } + + // Get final headers of the completed transfer + { + struct curl_header* header = nullptr; + while ((header = + curl_easy_nextheader(finished_curl, CURLH_HEADER, 0, header))) { + download.response.headers.insert({header->name, header->value}); + } + } + + // Get timing data + { + curl_off_t time = {}; + if (curl_easy_getinfo(finished_curl, CURLINFO_QUEUE_TIME_T, &time) == + CURLE_OK) { + download.response.timing_queue = Microseconds(time); + } + if (curl_easy_getinfo(finished_curl, CURLINFO_CONNECT_TIME_T, &time) == + CURLE_OK) { + download.response.timing_connect = Microseconds(time); + } + if (curl_easy_getinfo(finished_curl, CURLINFO_STARTTRANSFER_TIME_T, + &time) == CURLE_OK) { + download.response.timing_start_transfer = Microseconds(time); + } + if (curl_easy_getinfo(finished_curl, CURLINFO_TOTAL_TIME_T, &time) == + CURLE_OK) { + download.response.timing_total = Microseconds(time); + } + } + + DCHECK(download.on_done_callback); + std::move(download.on_done_callback).Run(std::move(download.response)); + + RemoveDownload_NetThread(finished_curl); +} + +void NetThread::NetThreadImpl::RemoveDownload_NetThread(CURL* finished_curl) { + // Remove the completed request from multi-handle + curl_multi_remove_handle(multi_handle_, finished_curl); + if (active_downloads_.count(finished_curl) > 0) { + if (active_downloads_[finished_curl].headers) { + curl_slist_free_all(active_downloads_[finished_curl].headers); + } + } + curl_easy_cleanup(finished_curl); + + // Remove the handle from our list + active_downloads_.erase(finished_curl); +} + +void NetThread::NetThreadImpl::AbortAllDownloads_NetThread() { + for (auto& [easy_handle, info] : active_downloads_) { + std::move(info.on_done_callback) + .Run(ResourceResponse{Result::kAborted, -1, {}, {}, {}, {}, {}, {}}); + + curl_multi_remove_handle(multi_handle_, easy_handle); + if (info.headers) { + curl_slist_free_all(info.headers); + } + curl_easy_cleanup(easy_handle); + } + + active_downloads_.clear(); +} + +} // namespace net +} // namespace base diff --git a/src/base/net/impl/net_thread_impl.h b/src/base/net/impl/net_thread_impl.h new file mode 100644 index 0000000000..8bd5ff6865 --- /dev/null +++ b/src/base/net/impl/net_thread_impl.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include + +#include "base/callback.h" +#include "base/net/impl/net_thread.h" +#include "base/net/resource_request.h" +#include "base/net/resource_response.h" +#include "base/net/result.h" + +#include "curl/curl.h" + +namespace base { +namespace net { + +class NetThread::NetThreadImpl { + public: + NetThreadImpl(); + ~NetThreadImpl(); + + void Start(); + void Stop(); + + void EnqueueDownload(ResourceRequest request, + std::optional max_response_size_bytes, + OnceCallback on_done_callback); + + private: + struct DownloadInfo; + + void RunLoop_NetThread(); + + // Parts of RunLoop + void ProcessPendingActions_NetThread(); + void Perform_NetThread(); + void ProcessCompleted_NetThread(); + void Wait_NetThread(); + + void EnqueueDownload_NetThread(DownloadInfo& download_info); + void DownloadFinished_NetThread(CURL* finished_curl, Result result); + void RemoveDownload_NetThread(CURL* finished_curl); + void AbortAllDownloads_NetThread(); + + // Accessible anywhere + std::atomic_flag not_quit_; + std::atomic_flag not_modified_; + + // Accessible with mutex + std::mutex mutex_; + std::vector pending_add_downloads_; + // TODO: add pending_cancel_downloads_; + + // Accessible on `thread_` + CURLM* multi_handle_; + std::map active_downloads_; + + // Accessible on IoThread owner thread + std::unique_ptr thread_; +}; + +} // namespace net +} // namespace base diff --git a/src/base/net/resource_request.h b/src/base/net/resource_request.h new file mode 100644 index 0000000000..8a6fac80e6 --- /dev/null +++ b/src/base/net/resource_request.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include "base/time/time_delta.h" + +namespace base { +namespace net { + +const auto kDefaultHeaders = std::vector{}; +const auto kNoTimeout = base::TimeDelta{}; + +struct ResourceRequest { + std::string url; + std::vector headers = kDefaultHeaders; + + base::TimeDelta connect_timeout = kNoTimeout; + base::TimeDelta timeout = kNoTimeout; + bool follow_redirects = true; + bool headers_only = false; +}; + +} // namespace net +} // namespace base diff --git a/src/base/net/resource_response.h b/src/base/net/resource_response.h new file mode 100644 index 0000000000..56c7ebf2af --- /dev/null +++ b/src/base/net/resource_response.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +#include "base/net/result.h" +#include "base/time/time_delta.h" + +namespace base { +namespace net { + +struct ResourceResponse { + Result result; + int code; + std::string final_url; + std::map headers; + std::vector data; + + base::TimeDelta timing_queue = base::Seconds(-1); + base::TimeDelta timing_connect = base::Seconds(-1); + base::TimeDelta timing_start_transfer = base::Seconds(-1); + base::TimeDelta timing_total = base::Seconds(-1); +}; + +} // namespace net +} // namespace base diff --git a/src/base/net/result.h b/src/base/net/result.h new file mode 100644 index 0000000000..360ad63c5e --- /dev/null +++ b/src/base/net/result.h @@ -0,0 +1,18 @@ +#pragma once + +namespace base { +namespace net { + +enum class Result { + // + kOk, + + // + kError, + + // + kAborted, +}; + +} // namespace net +} // namespace base diff --git a/src/base/net/simple_url_loader.cc b/src/base/net/simple_url_loader.cc new file mode 100644 index 0000000000..3122c5f3d2 --- /dev/null +++ b/src/base/net/simple_url_loader.cc @@ -0,0 +1,24 @@ +#include "base/net/simple_url_loader.h" + +#include + +#include "base/net/impl/net_thread.h" + +namespace base { +namespace net { + +void SimpleUrlLoader::DownloadUnbounded(ResourceRequest request, + ResultCallback on_done_callback) { + NetThread::GetInstance().EnqueueDownload(request, std::nullopt, + std::move(on_done_callback)); +} + +void SimpleUrlLoader::DownloadLimited(ResourceRequest request, + size_t max_response_size_bytes, + ResultCallback on_done_callback) { + NetThread::GetInstance().EnqueueDownload(request, max_response_size_bytes, + std::move(on_done_callback)); +} + +} // namespace net +} // namespace base diff --git a/src/base/net/simple_url_loader.h b/src/base/net/simple_url_loader.h new file mode 100644 index 0000000000..6bbfd3e8ee --- /dev/null +++ b/src/base/net/simple_url_loader.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base/callback.h" +#include "base/net/resource_request.h" +#include "base/net/resource_response.h" + +namespace base { +namespace net { + +class SimpleUrlLoader { + public: + using ResultCallback = base::OnceCallback; + + static void DownloadUnbounded(ResourceRequest request, + ResultCallback on_done_callback); + static void DownloadLimited(ResourceRequest request, + size_t max_response_size_bytes, + ResultCallback on_done_callback); + + private: + // +}; + +} // namespace net +} // namespace base diff --git a/tests/perf/main.cc b/tests/perf/main.cc index 60fad01d09..4204a60f37 100644 --- a/tests/perf/main.cc +++ b/tests/perf/main.cc @@ -5,7 +5,7 @@ // `BENCHMARK_MAIN()` macro with `base` module initialized at the beginning // and deinitialized at the end. int main(int argc, char** argv) { - base::Initialize(argc, argv); + base::Initialize(argc, argv, {}); ::benchmark::Initialize(&argc, argv); if (::benchmark::ReportUnrecognizedArguments(argc, argv)) { diff --git a/tests/unit/main.cc b/tests/unit/main.cc index 1208b12d73..a2e1393465 100644 --- a/tests/unit/main.cc +++ b/tests/unit/main.cc @@ -2,7 +2,7 @@ #include "gtest/gtest.h" int main(int argc, char* argv[]) { - base::InitializeForTests(argc, argv); + base::InitializeForTests(argc, argv, {}); ::testing::InitGoogleTest(&argc, argv); const auto result = RUN_ALL_TESTS(); diff --git a/vcpkg.json b/vcpkg.json index e4dc6279af..044619a5f7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -19,6 +19,12 @@ "features": [ "customprefix" ] + }, + { + "name": "curl", + "features": [ + "ssl" + ] } ], "features": { From 062eb03fea5e1bd95b6cf80496e8426a11c197bf Mon Sep 17 00:00:00 2001 From: RippeR37 Date: Sun, 30 Mar 2025 18:50:52 +0200 Subject: [PATCH 2/3] fixup! [#41] Implement simple networking stack based on libcurl Fix compilation on ubuntu gcc/clang --- src/base/net/impl/net_thread_impl.cc | 4 ++-- src/base/net/resource_response.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/base/net/impl/net_thread_impl.cc b/src/base/net/impl/net_thread_impl.cc index bde56a491c..a820e6e1c0 100644 --- a/src/base/net/impl/net_thread_impl.cc +++ b/src/base/net/impl/net_thread_impl.cc @@ -184,10 +184,10 @@ void NetThread::NetThreadImpl::EnqueueDownload_NetThread( // Save download request/response data active_downloads_.insert({easy_handle, std::move(download_info)}); - const auto& info = active_downloads_[easy_handle]; + const auto& inserted_info = active_downloads_[easy_handle]; // WARNING: Lambda has to be converted to function pointer or it will crash! - curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, (void*)(&info)); + curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, (void*)(&inserted_info)); curl_easy_setopt( easy_handle, CURLOPT_WRITEFUNCTION, +[](char* data, size_t n, size_t l, void* userp) -> size_t { diff --git a/src/base/net/resource_response.h b/src/base/net/resource_response.h index 56c7ebf2af..b12ac21808 100644 --- a/src/base/net/resource_response.h +++ b/src/base/net/resource_response.h @@ -12,8 +12,8 @@ namespace base { namespace net { struct ResourceResponse { - Result result; - int code; + Result result = Result::kOk; + int code = -1; std::string final_url; std::map headers; std::vector data; From 277f4908dd7e68016bbdcbf24ff8561434691fad Mon Sep 17 00:00:00 2001 From: RippeR37 Date: Sun, 30 Mar 2025 19:02:36 +0200 Subject: [PATCH 3/3] fixup! [#41] Implement simple networking stack based on libcurl Fix compilation on MSVC --- src/base/net/impl/net_thread_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/net/impl/net_thread_impl.cc b/src/base/net/impl/net_thread_impl.cc index a820e6e1c0..b1168c1692 100644 --- a/src/base/net/impl/net_thread_impl.cc +++ b/src/base/net/impl/net_thread_impl.cc @@ -33,7 +33,7 @@ struct NetThread::NetThreadImpl::DownloadInfo { // NetThread::NetThreadImpl::NetThreadImpl() - : not_quit_(true), not_modified_(true), multi_handle_(nullptr) { + : not_quit_(), not_modified_(), multi_handle_(nullptr) { curl_global_init(CURL_GLOBAL_DEFAULT); }