Complete
Some checks failed
CI / build-linux-and-wasm (x64-linux) (push) Has been cancelled

This commit is contained in:
2026-01-22 21:45:17 +05:30
parent 3ad1e3a2fb
commit 6acc2cb45d
14 changed files with 690 additions and 342 deletions

View File

@ -46,6 +46,8 @@ CheckOptions:
# Public struct members (like a Rust struct) -> x, y, width
- key: readability-identifier-naming.PublicMemberCase
value: lower_case
- key: readability-identifier-naming.StructMemberIgnoredRegexp
value: ^_[a-z0-9_]*$
# Private/Protected class members -> m_parser, m_count
- key: readability-identifier-naming.PrivateMemberCase

View File

@ -17,7 +17,7 @@ set(SRC_FILES
"imp/cpp/StreamReader.cpp"
"imp/cpp/StreamWriter.cpp"
#"imp/cpp/Http/Common.cpp"
"imp/cpp/Http/Common.cpp"
#"imp/cpp/Http/Client.cpp"
#"imp/cpp/Http/Server.cpp"
)

View File

@ -17,61 +17,73 @@
#include <IACore/Http/Client.hpp>
namespace IACore {
Result<UniquePtr<HttpClient>> HttpClient::Create(const String &host) {
return MakeUniqueProtected<HttpClient>(httplib::Client(host));
// Helper struct to access protected constructor via make_box (std::make_unique)
struct PublicHttpClient : public HttpClient {
explicit PublicHttpClient(httplib::Client &&client)
: HttpClient(std::move(client)) {}
};
auto HttpClient::create(const String &host) -> Result<Box<HttpClient>> {
return make_box<PublicHttpClient>(httplib::Client(host));
}
httplib::Headers BuildHeaders(Span<const HttpClient::Header> headers,
const char *defaultContentType) {
static auto build_headers(Span<const HttpClient::Header> headers,
const char *default_content_type)
-> httplib::Headers {
httplib::Headers out;
bool hasContentType = false;
bool has_content_type = false;
for (const auto &h : headers) {
out.emplace(h.first, h.second);
if (h.first ==
HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE))
hasContentType = true;
if (h.first == HttpClient::header_type_to_string(
HttpClient::EHeaderType::CONTENT_TYPE)) {
has_content_type = true;
}
}
if (!hasContentType && defaultContentType)
out.emplace("Content-Type", defaultContentType);
if (!has_content_type && default_content_type) {
out.emplace("Content-Type", default_content_type);
}
return out;
}
HttpClient::HttpClient(httplib::Client &&client)
: m_client(std::move(client)),
m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR) {
m_last_response_code(EResponseCode::INTERNAL_SERVER_ERROR) {
m_client.enable_server_certificate_verification(true);
}
HttpClient::~HttpClient() {}
HttpClient::~HttpClient() = default;
void HttpClient::EnableCertificateVerfication() {
void HttpClient::enable_certificate_verification() {
m_client.enable_server_certificate_verification(true);
}
void HttpClient::DisableCertificateVerfication() {
void HttpClient::disable_certificate_verification() {
m_client.enable_server_certificate_verification(false);
}
String HttpClient::PreprocessResponse(const String &response) {
const auto responseBytes =
Span<const u8>{(const u8 *)response.data(), response.size()};
const auto compression = DataOps::DetectCompression(responseBytes);
auto HttpClient::preprocess_response(const String &response) -> String {
const auto response_bytes = Span<const u8>{
reinterpret_cast<const u8 *>(response.data()), response.size()};
const auto compression = DataOps::detect_compression(response_bytes);
switch (compression) {
case DataOps::CompressionType::Gzip: {
const auto data = DataOps::GZipInflate(responseBytes);
if (!data)
const auto data = DataOps::gzip_inflate(response_bytes);
if (!data) {
return response;
return String((const char *)data->data(), data->size());
}
return String(reinterpret_cast<const char *>(data->data()), data->size());
}
case DataOps::CompressionType::Zlib: {
const auto data = DataOps::ZlibInflate(responseBytes);
if (!data)
const auto data = DataOps::zlib_inflate(response_bytes);
if (!data) {
return response;
return String((const char *)data->data(), data->size());
}
return String(reinterpret_cast<const char *>(data->data()), data->size());
}
case DataOps::CompressionType::None:
@ -81,53 +93,58 @@ String HttpClient::PreprocessResponse(const String &response) {
return response;
}
Result<String>
auto HttpClient::raw_get(const String &path, Span<const Header> headers,
const char *default_content_type) -> Result<String> {
auto http_headers = build_headers(headers, default_content_type);
HttpClient::RawGet(const String &path, Span<const Header> headers,
const char *defaultContentType) {
auto httpHeaders = BuildHeaders(headers, defaultContentType);
auto res = m_client.Get(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
httpHeaders);
if (res) {
m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300)
return PreprocessResponse(res->body);
else
return (std::format("HTTP Error {} : {}", res->status, res->body));
String adjusted_path = path;
if (!path.empty() && path[0] != '/') {
adjusted_path = "/" + path;
}
return (std::format("Network Error: {}", httplib::to_string(res.error())));
auto res = m_client.Get(adjusted_path.c_str(), http_headers);
if (res) {
m_last_response_code = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300) {
return preprocess_response(res->body);
}
return fail("HTTP Error {} : {}", res->status, res->body);
}
return fail("Network Error: {}", httplib::to_string(res.error()));
}
Result<String>
auto HttpClient::raw_post(const String &path, Span<const Header> headers,
const String &body, const char *default_content_type)
-> Result<String> {
auto http_headers = build_headers(headers, default_content_type);
HttpClient::RawPost(const String &path, Span<const Header> headers,
const String &body, const char *defaultContentType) {
auto httpHeaders = BuildHeaders(headers, defaultContentType);
String contentType = defaultContentType;
if (httpHeaders.count("Content-Type")) {
const auto t = httpHeaders.find("Content-Type");
contentType = t->second;
httpHeaders.erase(t);
String content_type = default_content_type;
if (http_headers.count("Content-Type")) {
const auto t = http_headers.find("Content-Type");
content_type = t->second;
http_headers.erase(t);
}
m_client.set_keep_alive(true);
auto res = m_client.Post(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
httpHeaders, body, contentType.c_str());
if (res) {
m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300)
return PreprocessResponse(res->body);
else
return (std::format("HTTP Error {} : {}", res->status, res->body));
String adjusted_path = path;
if (!path.empty() && path[0] != '/') {
adjusted_path = "/" + path;
}
return (std::format("Network Error: {}", httplib::to_string(res.error())));
auto res = m_client.Post(adjusted_path.c_str(), http_headers, body,
content_type.c_str());
if (res) {
m_last_response_code = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300) {
return preprocess_response(res->body);
}
return fail("HTTP Error {} : {}", res->status, res->body);
}
return fail("Network Error: {}", httplib::to_string(res.error()));
}
} // namespace IACore
} // namespace IACore

View File

@ -16,7 +16,7 @@
#include <IACore/Http/Common.hpp>
namespace IACore {
String HttpCommon::UrlEncode(const String &value) {
auto HttpCommon::url_encode(const String &value) -> String {
std::stringstream escaped;
escaped.fill('0');
escaped << std::hex << std::uppercase;
@ -33,16 +33,16 @@ String HttpCommon::UrlEncode(const String &value) {
return escaped.str();
}
String HttpCommon::UrlDecode(const String &value) {
auto HttpCommon::url_decode(const String &value) -> String {
String result;
result.reserve(value.length());
for (size_t i = 0; i < value.length(); ++i) {
if (value[i] == '%' && i + 2 < value.length()) {
std::string hexStr = value.substr(i + 1, 2);
char decodedChar =
static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
result += decodedChar;
std::string hex_str = value.substr(i + 1, 2);
char decoded_char =
static_cast<char>(std::strtol(hex_str.c_str(), nullptr, 16));
result += decoded_char;
i += 2;
} else if (value[i] == '+')
result += ' ';
@ -53,7 +53,7 @@ String HttpCommon::UrlDecode(const String &value) {
return result;
}
String HttpCommon::HeaderTypeToString(EHeaderType type) {
auto HttpCommon::header_type_to_string(EHeaderType type) -> String {
switch (type) {
case EHeaderType::ACCEPT:
return "Accept";
@ -111,7 +111,7 @@ String HttpCommon::HeaderTypeToString(EHeaderType type) {
return "<Unknown>";
}
bool HttpCommon::IsSuccessResponseCode(EResponseCode code) {
auto HttpCommon::is_success_response_code(EResponseCode code) -> bool {
return (i32)code >= 200 && (i32)code < 300;
}
} // namespace IACore

View File

@ -15,7 +15,159 @@
#include <IACore/Http/Server.hpp>
namespace IACore
{
namespace IACore {
// =============================================================================
// Request Implementation
// =============================================================================
}
auto HttpServer::Request::get_header(const String &key) const -> String {
if (auto it = headers.find(key); it != headers.end()) {
return it->second;
}
return "";
}
auto HttpServer::Request::get_param(const String &key) const -> String {
if (auto it = params.find(key); it != params.end()) {
return it->second;
}
return "";
}
auto HttpServer::Request::get_path_param(const String &key) const -> String {
if (auto it = path_params.find(key); it != path_params.end()) {
return it->second;
}
return "";
}
auto HttpServer::Request::has_header(const String &key) const -> bool {
return headers.contains(key);
}
auto HttpServer::Request::has_param(const String &key) const -> bool {
return params.contains(key);
}
auto HttpServer::Request::has_path_param(const String &key) const -> bool {
return path_params.contains(key);
}
// =============================================================================
// Response Implementation
// =============================================================================
void HttpServer::Response::set_content(const String &content,
const String &type) {
body = content;
content_type = type;
}
void HttpServer::Response::set_status(EResponseCode status_code) {
code = status_code;
}
void HttpServer::Response::add_header(const String &key, const String &value) {
headers[key] = value;
}
// =============================================================================
// HttpServer Implementation
// =============================================================================
struct PublicHttpServer : public HttpServer {
PublicHttpServer() = default;
};
HttpServer::HttpServer() = default;
HttpServer::~HttpServer() { stop(); }
auto HttpServer::create() -> Result<Box<HttpServer>> {
return make_box<PublicHttpServer>();
}
auto HttpServer::listen(const String &host, u32 port) -> Result<void> {
if (!m_server.listen(host.c_str(), static_cast<int>(port))) {
return fail("Failed to start HTTP server on {}:{}", host, port);
}
return {};
}
void HttpServer::stop() {
if (m_server.is_running()) {
m_server.stop();
}
}
auto HttpServer::is_running() const -> bool { return m_server.is_running(); }
void HttpServer::register_handler(const String &method, const String &pattern,
Handler handler) {
auto wrapper = [handler = std::move(handler)](const httplib::Request &req,
httplib::Response &res) {
Request ia_req;
ia_req.path = req.path;
ia_req.method = req.method;
ia_req.body = req.body;
// Convert headers
for (const auto &item : req.headers) {
ia_req.headers[item.first] = item.second;
}
// Convert params
for (const auto &item : req.params) {
ia_req.params[item.first] = item.second;
}
// Convert path params
for (const auto &item : req.path_params) {
ia_req.path_params[item.first] = item.second;
}
Response ia_res;
handler(ia_req, ia_res);
res.status = static_cast<int>(ia_res.code);
res.set_content(ia_res.body, ia_res.content_type.c_str());
for (const auto &item : ia_res.headers) {
res.set_header(item.first.c_str(), item.second.c_str());
}
};
if (method == "GET") {
m_server.Get(pattern.c_str(), wrapper);
} else if (method == "POST") {
m_server.Post(pattern.c_str(), wrapper);
} else if (method == "PUT") {
m_server.Put(pattern.c_str(), wrapper);
} else if (method == "DELETE") {
m_server.Delete(pattern.c_str(), wrapper);
} else if (method == "OPTIONS") {
m_server.Options(pattern.c_str(), wrapper);
}
}
void HttpServer::get(const String &pattern, Handler handler) {
register_handler("GET", pattern, std::move(handler));
}
void HttpServer::post(const String &pattern, Handler handler) {
register_handler("POST", pattern, std::move(handler));
}
void HttpServer::put(const String &pattern, Handler handler) {
register_handler("PUT", pattern, std::move(handler));
}
void HttpServer::del(const String &pattern, Handler handler) {
register_handler("DELETE", pattern, std::move(handler));
}
void HttpServer::options(const String &pattern, Handler handler) {
register_handler("OPTIONS", pattern, std::move(handler));
}
} // namespace IACore

View File

@ -114,13 +114,17 @@ auto IpcNode::connect(const char *connection_string) -> Result<void> {
u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
m_moni = make_box<RingBufferView>(
&layout->moni_control,
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)), false);
IA_TRY(m_moni,
RingBufferView::create(
&layout->moni_control,
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)),
false));
m_mino = make_box<RingBufferView>(
&layout->mino_control,
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)), false);
IA_TRY(m_mino,
RingBufferView::create(
&layout->mino_control,
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)),
false));
#if IA_PLATFORM_WINDOWS
u_long mode = 1;
@ -135,14 +139,14 @@ auto IpcNode::connect(const char *connection_string) -> Result<void> {
}
void IpcNode::update() {
if (!m_moni) {
if (!m_moni.is_valid()) {
return;
}
IpcPacketHeader header;
// Process all available messages from Manager
while (m_moni->pop(
while (m_moni.pop(
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
on_packet(header.id, {m_receive_buffer.data(), header.payload_size});
}
@ -166,10 +170,11 @@ void IpcNode::send_signal(u8 signal) {
}
}
void IpcNode::send_packet(u16 packet_id, Span<const u8> payload) {
if (m_mino) {
m_mino->push(packet_id, payload);
}
auto IpcNode::send_packet(u16 packet_id, Span<const u8> payload)
-> Result<void> {
if (!m_mino.is_valid())
return fail("invalid MINO");
return m_mino.push(packet_id, payload);
}
// =============================================================================
@ -183,18 +188,19 @@ void IpcManager::NodeSession::send_signal(u8 signal) {
}
}
void IpcManager::NodeSession::send_packet(u16 packet_id,
Span<const u8> payload) {
auto IpcManager::NodeSession::send_packet(u16 packet_id, Span<const u8> payload)
-> Result<void> {
// Protect the RingBuffer write cursor from concurrent threads
std::scoped_lock lock(send_mutex);
if (moni) {
moni->push(packet_id, payload);
}
if (!moni.is_valid())
return fail("invalid MONI");
return moni.push(packet_id, payload);
}
IpcManager::IpcManager() {
// SocketOps is smart enough to track multiple inits
SocketOps::initialize();
ensure(SocketOps::is_initialized(),
"SocketOps must be initialized before using IpcManager");
m_receive_buffer.resize(UINT16_MAX + 1);
}
@ -214,9 +220,6 @@ IpcManager::~IpcManager() {
SocketOps::close(session->listener_socket);
}
m_pending_sessions.clear();
// SocketOps is smart enough to track multiple terminates
SocketOps::terminate();
}
void IpcManager::update() {
@ -270,7 +273,7 @@ void IpcManager::update() {
IpcPacketHeader header;
while (node->mino->pop(
while (node->mino.pop(
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
on_packet(node_id, header.id,
{m_receive_buffer.data(), header.payload_size});
@ -345,17 +348,19 @@ auto IpcManager::spawn_node(const Path &executable_path, u32 shared_memory_size)
layout->mino_data_offset = header_size + half_size;
layout->mino_data_size = half_size;
session->moni = make_box<RingBufferView>(
&layout->moni_control,
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
static_cast<usize>(layout->moni_data_size)),
true);
IA_TRY(session->moni,
RingBufferView::create(
&layout->moni_control,
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
static_cast<usize>(layout->moni_data_size)),
true));
session->mino = make_box<RingBufferView>(
&layout->mino_control,
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
static_cast<usize>(layout->mino_data_size)),
true);
IA_TRY(session->mino,
RingBufferView::create(
&layout->mino_control,
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
static_cast<usize>(layout->mino_data_size)),
true));
IpcConnectionDescriptor desc;
desc.socket_path = sock_path;
@ -446,12 +451,11 @@ void IpcManager::send_signal(NativeProcessID node, u8 signal) {
it_node->second->send_signal(signal);
}
void IpcManager::send_packet(NativeProcessID node, u16 packet_id,
Span<const u8> payload) {
auto IpcManager::send_packet(NativeProcessID node, u16 packet_id,
Span<const u8> payload) -> Result<void> {
const auto it_node = m_active_session_map.find(node);
if (it_node == m_active_session_map.end()) {
return;
}
it_node->second->send_packet(packet_id, payload);
if (it_node == m_active_session_map.end())
return fail("no such node");
return it_node->second->send_packet(packet_id, payload);
}
} // namespace IACore

View File

@ -53,8 +53,11 @@ public:
};
public:
RingBufferView(Span<u8> buffer, bool is_owner);
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
static auto default_instance() -> RingBufferView;
static auto create(Span<u8> buffer, bool is_owner) -> Result<RingBufferView>;
static auto create(ControlBlock *control_block, Span<u8> buffer,
bool is_owner) -> Result<RingBufferView>;
// Returns:
// - Ok(nullopt) if empty
@ -70,6 +73,12 @@ public:
auto get_control_block() -> ControlBlock *;
[[nodiscard]] auto is_valid() const -> bool;
protected:
RingBufferView(Span<u8> buffer, bool is_owner);
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
private:
u8 *m_data_ptr{};
u32 m_capacity{};
@ -84,10 +93,40 @@ private:
// Implementation
// =============================================================================
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
ensure(buffer.size() > sizeof(ControlBlock),
"Buffer too small for ControlBlock");
auto RingBufferView::default_instance() -> RingBufferView {
return RingBufferView(nullptr, {}, false);
}
inline auto RingBufferView::create(Span<u8> buffer, bool is_owner)
-> Result<RingBufferView> {
if (buffer.size() <= sizeof(ControlBlock)) {
return fail("Buffer too small for ControlBlock");
}
if (!is_owner) {
auto *cb = reinterpret_cast<ControlBlock *>(buffer.data());
u32 capacity = static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
if (cb->consumer.capacity != capacity) {
return fail("Capacity mismatch");
}
}
return RingBufferView(buffer, is_owner);
}
inline auto RingBufferView::create(ControlBlock *control_block, Span<u8> buffer,
bool is_owner) -> Result<RingBufferView> {
if (control_block == nullptr) {
return fail("ControlBlock is null");
}
if (buffer.empty()) {
return fail("Buffer is empty");
}
return RingBufferView(control_block, buffer, is_owner);
}
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
m_control_block = reinterpret_cast<ControlBlock *>(buffer.data());
m_data_ptr = buffer.data() + sizeof(ControlBlock);
@ -97,17 +136,11 @@ inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
m_control_block->consumer.capacity = m_capacity;
m_control_block->producer.write_offset.store(0, std::memory_order_release);
m_control_block->consumer.read_offset.store(0, std::memory_order_release);
} else {
ensure(m_control_block->consumer.capacity == m_capacity,
"Capacity mismatch");
}
}
inline RingBufferView::RingBufferView(ControlBlock *control_block,
Span<u8> buffer, bool is_owner) {
ensure(control_block != nullptr, "ControlBlock is null");
ensure(!buffer.empty(), "Buffer is empty");
m_control_block = control_block;
m_data_ptr = buffer.data();
m_capacity = static_cast<u32>(buffer.size());
@ -154,8 +187,9 @@ inline auto RingBufferView::pop(PacketHeader &out_header, Span<u8> out_buffer)
inline auto RingBufferView::push(u16 packet_id, Span<const u8> data)
-> Result<void> {
ensure(data.size() <= std::numeric_limits<u16>::max(),
"Data size exceeds u16 limit");
if (data.size() > std::numeric_limits<u16>::max()) {
return fail("Data size exceeds u16 limit");
}
const u32 total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
@ -228,4 +262,8 @@ inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size)
}
}
[[nodiscard]] auto RingBufferView::is_valid() const -> bool {
return m_control_block && m_data_ptr && m_capacity;
}
} // namespace IACore

View File

@ -17,26 +17,63 @@
#include <IACore/PCH.hpp>
#if IA_PLATFORM_WINDOWS
#include <errhandlingapi.h>
#include <libloaderapi.h>
#else
#if !IA_PLATFORM_WINDOWS
#include <dlfcn.h>
#endif
// Ensure 'ia' namespace alias exists for IA_TRY macro usage
namespace ia = IACore;
namespace IACore {
class DynamicLib {
public:
DynamicLib() : m_handle(nullptr) {}
// Factory method to load a dynamic library
[[nodiscard]] static auto load(const String &search_path, const String &name)
-> Result<DynamicLib> {
namespace fs = std::filesystem;
auto full_path = fs::path(search_path) / name;
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle) {
if (!full_path.has_extension()) {
#if IA_PLATFORM_WINDOWS
full_path += ".dll";
#elif IA_PLATFORM_APPLE
full_path += ".dylib";
#else
full_path += ".so";
#endif
}
DynamicLib lib;
#if IA_PLATFORM_WINDOWS
const HMODULE h = LoadLibraryA(full_path.string().c_str());
if (!h) {
return fail(get_windows_error());
}
lib.m_handle = static_cast<void *>(h);
#else
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
void *h = dlopen(full_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!h) {
const char *err = dlerror();
return fail(err ? err : "Unknown dlopen error");
}
lib.m_handle = h;
#endif
return lib;
}
DynamicLib() = default;
DynamicLib(DynamicLib &&other) noexcept : m_handle(other.m_handle) {
other.m_handle = nullptr;
}
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT {
auto operator=(DynamicLib &&other) noexcept -> DynamicLib & {
if (this != &other) {
Unload(); // Free current if exists
unload();
m_handle = other.m_handle;
other.m_handle = nullptr;
}
@ -44,88 +81,46 @@ public:
}
DynamicLib(const DynamicLib &) = delete;
DynamicLib &operator=(const DynamicLib &) = delete;
auto operator=(const DynamicLib &) -> DynamicLib & = delete;
~DynamicLib() { Unload(); }
~DynamicLib() { unload(); }
// Automatically detects extension (.dll/.so) if not provided
NO_DISCARD("Check for load errors")
static tl::Result<DynamicLib> Load(const String &searchPath,
const String &name) {
namespace fs = std::filesystem;
fs::path fullPath = fs::path(searchPath) / name;
if (!fullPath.has_extension()) {
#if IA_PLATFORM_WINDOWS
fullPath += ".dll";
#elif IA_PLATFORM_MAC
fullPath += ".dylib";
#else
fullPath += ".so";
#endif
[[nodiscard]] auto get_symbol(const String &name) const -> Result<void *> {
if (!m_handle) {
return fail("Library not loaded");
}
DynamicLib lib;
#if IA_PLATFORM_WINDOWS
HMODULE h = LoadLibraryA(fullPath.string().c_str());
if (!h) {
return (GetWindowsError());
}
lib.m_handle = CAST(h, void *);
#else
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!h) {
// dlerror returns a string describing the last error
const char *err = dlerror();
return (String(err ? err : "Unknown dlopen error"));
}
lib.m_handle = h;
#endif
return std::move(lib);
}
NO_DISCARD("Check if symbol exists")
tl::Result<void *> GetSymbol(const String &name) const {
if (!m_handle)
return (String("Library not loaded"));
void *sym = nullptr;
#if IA_PLATFORM_WINDOWS
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), void *);
if (!sym)
return (GetWindowsError());
sym = static_cast<void *>(
GetProcAddress(static_cast<HMODULE>(m_handle), name.c_str()));
if (!sym) {
return fail(get_windows_error());
}
#else
// Clear any previous error
dlerror();
sym = dlsym(m_handle, name.c_str());
const char *err = dlerror();
if (err)
return (String(err));
if (const char *err = dlerror()) {
return fail(err);
}
#endif
return sym;
}
// Template helper for casting
template <typename FuncT>
tl::Result<FuncT> GetFunction(const String &name) const {
auto res = GetSymbol(name);
if (!res)
return (res.error());
return REINTERPRET(*res, FuncT);
[[nodiscard]] auto get_function(const String &name) const -> Result<FuncT> {
void *sym = nullptr;
IA_TRY(sym, get_symbol(name));
return reinterpret_cast<FuncT>(sym);
}
void Unload() {
void unload() {
if (m_handle) {
#if IA_PLATFORM_WINDOWS
FreeLibrary(CAST(m_handle, HMODULE));
FreeLibrary(static_cast<HMODULE>(m_handle));
#else
dlclose(m_handle);
#endif
@ -133,28 +128,30 @@ public:
}
}
bool IsLoaded() const { return m_handle != nullptr; }
[[nodiscard]] auto is_loaded() const -> bool { return m_handle != nullptr; }
private:
void *m_handle;
void *m_handle = nullptr;
#if IA_PLATFORM_WINDOWS
static String GetWindowsError() {
DWORD errorID = ::GetLastError();
if (errorID == 0)
static auto get_windows_error() -> String {
const DWORD error_id = ::GetLastError();
if (error_id == 0) {
return String();
}
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(
LPSTR message_buffer = nullptr;
const usize size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&messageBuffer, 0, NULL);
nullptr, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPSTR>(&message_buffer), 0, nullptr);
String message(messageBuffer, size);
LocalFree(messageBuffer);
return String("Win32 Error: ") + message;
String message(message_buffer, size);
LocalFree(message_buffer);
return "Win32 Error: " + message;
}
#endif
};
} // namespace IACore

View File

@ -16,88 +16,83 @@
#pragma once
#include <IACore/PCH.hpp>
#include <cstdlib>
namespace IACore
{
class Environment
{
public:
static Optional<String> Find(const String &name)
{
namespace IACore {
class Environment {
public:
static auto find(const String &name) -> Option<String> {
#if IA_PLATFORM_WINDOWS
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
const DWORD buffer_size = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
if (bufferSize == 0)
{
// DWORD 0 means failed
return std::nullopt;
}
if (buffer_size == 0) {
return std::nullopt;
}
// 2. Allocate (bufferSize includes the null terminator request)
String result;
result.resize(bufferSize);
String result;
result.resize(buffer_size);
// 3. Fetch
// Returns num chars written excluding null terminator
DWORD actualSize = GetEnvironmentVariableA(name.c_str(), result.data(), bufferSize);
const DWORD actual_size =
GetEnvironmentVariableA(name.c_str(), result.data(), buffer_size);
if (actualSize == 0 || actualSize > bufferSize)
{
return std::nullopt;
}
if (actual_size == 0 || actual_size > buffer_size) {
return std::nullopt;
}
// Resize down to exclude the null terminator and any slack
result.resize(actualSize);
return result;
// Resize down to exclude the null terminator and any slack
result.resize(actual_size);
return result;
#else
// getenv returns a pointer to the environment area
const char *val = std::getenv(name.c_str());
if (val == nullptr)
{
return std::nullopt;
}
return String(val);
const char *val = std::getenv(name.c_str());
if (val == nullptr) {
return std::nullopt;
}
return String(val);
#endif
}
}
static String Get(const String &name, const String &defaultValue = "")
{
return Find(name).value_or(defaultValue);
}
static auto get(const String &name, const String &default_value = "")
-> String {
return find(name).value_or(default_value);
}
static bool Set(const String &name, const String &value)
{
if (name.empty())
return FALSE;
static auto set(const String &name, const String &value) -> Result<void> {
if (name.empty()) {
return fail("Environment variable name cannot be empty");
}
#if IA_PLATFORM_WINDOWS
return SetEnvironmentVariableA(name.c_str(), value.c_str()) != 0;
if (SetEnvironmentVariableA(name.c_str(), value.c_str()) == 0) {
return fail("Failed to set environment variable: {}", name);
}
#else
// Returns 0 on success, -1 on error
return setenv(name.c_str(), value.c_str(), 1) == 0;
if (setenv(name.c_str(), value.c_str(), 1) != 0) {
return fail("Failed to set environment variable: {}", name);
}
#endif
}
return {};
}
static bool Unset(const String &name)
{
if (name.empty())
return FALSE;
static auto unset(const String &name) -> Result<void> {
if (name.empty()) {
return fail("Environment variable name cannot be empty");
}
#if IA_PLATFORM_WINDOWS
return SetEnvironmentVariableA(name.c_str(), nullptr) != 0;
if (SetEnvironmentVariableA(name.c_str(), nullptr) == 0) {
return fail("Failed to unset environment variable: {}", name);
}
#else
return unsetenv(name.c_str()) == 0;
if (unsetenv(name.c_str()) != 0) {
return fail("Failed to unset environment variable: {}", name);
}
#endif
}
return {};
}
static bool Exists(const String &name)
{
#if IA_PLATFORM_WINDOWS
return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0;
#else
return std::getenv(name.c_str()) != nullptr;
#endif
}
};
static auto exists(const String &name) -> bool {
return find(name).has_value();
}
};
} // namespace IACore

View File

@ -16,73 +16,83 @@
#pragma once
#include <IACore/Http/Common.hpp>
#include <IACore/JSON.hpp>
namespace IACore {
class HttpClient : public HttpCommon {
public:
static Result<UniquePtr<HttpClient>> Create(const String &host);
static auto create(const String &host) -> Result<Box<HttpClient>>;
~HttpClient();
public:
Result<String>
RawGet(const String &path, Span<const Header> headers,
const char *defaultContentType = "application/x-www-form-urlencoded");
Result<String>
RawPost(const String &path, Span<const Header> headers, const String &body,
const char *defaultContentType = "application/x-www-form-urlencoded");
template <typename _response_type>
Result<_response_type> JsonGet(const String &path,
Span<const Header> headers);
template <typename _payload_type, typename _response_type>
Result<_response_type> JsonPost(const String &path,
Span<const Header> headers,
const _payload_type &body);
// Certificate verfication is enabled by default
void EnableCertificateVerfication();
void DisableCertificateVerfication();
HttpClient(HttpClient &&) = default;
HttpClient(const HttpClient &) = delete;
auto operator=(HttpClient &&) -> HttpClient & = default;
auto operator=(const HttpClient &) -> HttpClient & = delete;
public:
EResponseCode LastResponseCode() { return m_lastResponseCode; }
auto raw_get(const String &path, Span<const Header> headers,
const char *default_content_type =
"application/x-www-form-urlencoded") -> Result<String>;
auto raw_post(
const String &path, Span<const Header> headers, const String &body,
const char *default_content_type = "application/x-www-form-urlencoded")
-> Result<String>;
template <typename ResponseType>
auto json_get(const String &path, Span<const Header> headers)
-> Result<ResponseType>;
template <typename PayloadType, typename ResponseType>
auto json_post(const String &path, Span<const Header> headers,
const PayloadType &body) -> Result<ResponseType>;
// Certificate verification is enabled by default
void enable_certificate_verification();
void disable_certificate_verification();
public:
auto last_response_code() -> EResponseCode { return m_last_response_code; }
private:
httplib::Client m_client;
EResponseCode m_lastResponseCode;
EResponseCode m_last_response_code;
private:
String PreprocessResponse(const String &response);
auto preprocess_response(const String &response) -> String;
protected:
HttpClient(httplib::Client &&client);
explicit HttpClient(httplib::Client &&client);
};
template <typename _response_type>
Result<_response_type> HttpClient::JsonGet(const String &path,
Span<const Header> headers) {
const auto rawResponse = RawGet(path, headers, "application/json");
if (!rawResponse)
return (rawResponse.error());
if (LastResponseCode() != EResponseCode::OK)
return (
std::format("Server responded with code {}", (i32)LastResponseCode()));
return JSON::ParseToStruct<_response_type>(*rawResponse);
template <typename ResponseType>
auto HttpClient::json_get(const String &path, Span<const Header> headers)
-> Result<ResponseType> {
String raw_response;
IA_TRY(raw_response, raw_get(path, headers, "application/json"));
if (last_response_code() != EResponseCode::OK) {
return fail("Server responded with code {}",
static_cast<i32>(last_response_code()));
}
return Json::parse_to_struct<ResponseType>(raw_response);
}
template <typename _payload_type, typename _response_type>
Result<_response_type> HttpClient::JsonPost(const String &path,
Span<const Header> headers,
const _payload_type &body) {
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
const auto rawResponse =
RawPost(path, headers, encodedBody, "application/json");
if (!rawResponse)
return (rawResponse.error());
if (LastResponseCode() != EResponseCode::OK)
return (
std::format("Server responded with code {}", (i32)LastResponseCode()));
return JSON::ParseToStruct<_response_type>(*rawResponse);
template <typename PayloadType, typename ResponseType>
auto HttpClient::json_post(const String &path, Span<const Header> headers,
const PayloadType &body) -> Result<ResponseType> {
String encoded_body;
IA_TRY(encoded_body, Json::encode_struct(body));
String raw_response;
IA_TRY(raw_response,
raw_post(path, headers, encoded_body, "application/json"));
if (last_response_code() != EResponseCode::OK) {
return fail("Server responded with code {}",
static_cast<i32>(last_response_code()));
}
return Json::parse_to_struct<ResponseType>(raw_response);
}
} // namespace IACore

View File

@ -125,29 +125,31 @@ public:
NETWORK_AUTHENTICATION_REQUIRED = 511
};
using Header = KeyValuePair<String, String>;
using Header = Pair<String, String>;
static String UrlEncode(const String &value);
static String UrlDecode(const String &value);
static auto url_encode(const String &value) -> String;
static auto url_decode(const String &value) -> String;
static String HeaderTypeToString(EHeaderType type);
static auto header_type_to_string(EHeaderType type) -> String;
static inline Header CreateHeader(EHeaderType key, const String &value);
static inline Header CreateHeader(const String &key, const String &value);
static inline auto create_header(EHeaderType key, const String &value)
-> Header;
static inline auto create_header(const String &key, const String &value)
-> Header;
static bool IsSuccessResponseCode(EResponseCode code);
static auto is_success_response_code(EResponseCode code) -> bool;
protected:
HttpCommon() = default;
};
HttpCommon::Header HttpCommon::CreateHeader(EHeaderType key,
const String &value) {
return std::make_pair(HeaderTypeToString(key), value);
auto HttpCommon::create_header(EHeaderType key, const String &value)
-> HttpCommon::Header {
return std::make_pair(header_type_to_string(key), value);
}
HttpCommon::Header HttpCommon::CreateHeader(const String &key,
const String &value) {
auto HttpCommon::create_header(const String &key, const String &value)
-> HttpCommon::Header {
return std::make_pair(key, value);
}
} // namespace IACore

View File

@ -16,12 +16,138 @@
#pragma once
#include <IACore/Http/Common.hpp>
#include <IACore/JSON.hpp>
#include <functional>
namespace IACore {
class HttpServer : public HttpCommon {
public:
struct Request {
String path;
String method;
String body;
HashMap<String, String> headers;
HashMap<String, String> params; // Query parameters
HashMap<String, String> path_params; // Path parameters (e.g. /users/:id)
[[nodiscard]] auto get_header(const String &key) const -> String;
[[nodiscard]] auto get_param(const String &key) const -> String;
[[nodiscard]] auto get_path_param(const String &key) const -> String;
[[nodiscard]] auto has_header(const String &key) const -> bool;
[[nodiscard]] auto has_param(const String &key) const -> bool;
[[nodiscard]] auto has_path_param(const String &key) const -> bool;
};
struct Response {
EResponseCode code = EResponseCode::OK;
String body;
HashMap<String, String> headers;
String content_type = "text/plain";
void set_content(const String &content, const String &type);
void set_status(EResponseCode status_code);
void add_header(const String &key, const String &value);
};
using Handler = std::function<void(const Request &, Response &)>;
public:
static auto create() -> Result<Box<HttpServer>>;
~HttpServer();
HttpServer(HttpServer &&) = default;
HttpServer(const HttpServer &) = delete;
auto operator=(HttpServer &&) -> HttpServer & = default;
auto operator=(const HttpServer &) -> HttpServer & = delete;
auto listen(const String &host, u32 port) -> Result<void>;
void stop();
auto is_running() const -> bool;
void get(const String &pattern, Handler handler);
void post(const String &pattern, Handler handler);
void put(const String &pattern, Handler handler);
void del(const String &pattern, Handler handler);
void options(const String &pattern, Handler handler);
template <typename ResponseType>
void json_get(const String &pattern,
std::function<Result<ResponseType>(const Request &)> handler);
template <typename PayloadType, typename ResponseType>
void
json_post(const String &pattern,
std::function<Result<ResponseType>(const PayloadType &)> handler);
protected:
HttpServer();
private:
httplib::Server m_server;
void register_handler(const String &method, const String &pattern,
Handler handler);
};
// =============================================================================
// Template Implementations
// =============================================================================
template <typename ResponseType>
void HttpServer::json_get(
const String &pattern,
std::function<Result<ResponseType>(const Request &)> handler) {
get(pattern, [handler](const Request &req, Response &res) {
auto result = handler(req);
if (!result) {
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
res.set_content(result.error(), "text/plain");
return;
}
auto json_res = Json::encode_struct(*result);
if (!json_res) {
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
res.set_content("Failed to encode JSON response", "text/plain");
return;
}
res.set_status(EResponseCode::OK);
res.set_content(*json_res, "application/json");
});
}
template <typename PayloadType, typename ResponseType>
void HttpServer::json_post(
const String &pattern,
std::function<Result<ResponseType>(const PayloadType &)> handler) {
post(pattern, [handler](const Request &req, Response &res) {
auto payload = Json::parse_to_struct<PayloadType>(req.body);
if (!payload) {
res.set_status(EResponseCode::BAD_REQUEST);
res.set_content("Invalid JSON Payload", "text/plain");
return;
}
auto result = handler(*payload);
if (!result) {
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
res.set_content(result.error(), "text/plain");
return;
}
auto json_res = Json::encode_struct(*result);
if (!json_res) {
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
res.set_content("Failed to encode JSON response", "text/plain");
return;
}
res.set_status(EResponseCode::OK);
res.set_content(*json_res, "application/json");
});
}
namespace IACore
{
class HttpServer : public HttpCommon
{
public:
HttpServer(const String &host, u32 port);
};
} // namespace IACore

View File

@ -76,7 +76,7 @@ public:
void update();
void send_signal(u8 signal);
void send_packet(u16 packet_id, Span<const u8> payload);
auto send_packet(u16 packet_id, Span<const u8> payload) -> Result<void>;
protected:
virtual void on_signal(u8 signal) = 0;
@ -88,8 +88,8 @@ private:
Vec<u8> m_receive_buffer;
SocketHandle m_socket{INVALID_SOCKET};
Box<RingBufferView> m_moni; // Manager Out, Node In
Box<RingBufferView> m_mino; // Manager In, Node Out
RingBufferView m_moni; // Manager Out, Node In
RingBufferView m_mino; // Manager In, Node Out
};
class IpcManager {
@ -105,13 +105,15 @@ class IpcManager {
SocketHandle listener_socket{INVALID_SOCKET};
SocketHandle data_socket{INVALID_SOCKET};
Box<RingBufferView> moni; // Manager Out, Node In
Box<RingBufferView> mino; // Manager In, Node Out
RingBufferView moni =
RingBufferView::default_instance(); // Manager Out, Node In
RingBufferView mino =
RingBufferView::default_instance(); // Manager In, Node Out
bool is_ready{false};
void send_signal(u8 signal);
void send_packet(u16 packet_id, Span<const u8> payload);
auto send_packet(u16 packet_id, Span<const u8> payload) -> Result<void>;
};
public:
@ -131,7 +133,8 @@ public:
void shutdown_node(NativeProcessID node);
void send_signal(NativeProcessID node, u8 signal);
void send_packet(NativeProcessID node, u16 packet_id, Span<const u8> payload);
auto send_packet(NativeProcessID node, u16 packet_id, Span<const u8> payload)
-> Result<void>;
protected:
virtual void on_signal(NativeProcessID node, u8 signal) = 0;

View File

@ -77,6 +77,8 @@ public:
#endif
}
static auto is_initialized() -> bool { return s_init_count > 0; }
static auto is_port_available_tcp(u16 port) -> bool {
return is_port_available(port, SOCK_STREAM);
}