This commit is contained in:
@ -46,6 +46,8 @@ CheckOptions:
|
|||||||
# Public struct members (like a Rust struct) -> x, y, width
|
# Public struct members (like a Rust struct) -> x, y, width
|
||||||
- key: readability-identifier-naming.PublicMemberCase
|
- key: readability-identifier-naming.PublicMemberCase
|
||||||
value: lower_case
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.StructMemberIgnoredRegexp
|
||||||
|
value: ^_[a-z0-9_]*$
|
||||||
|
|
||||||
# Private/Protected class members -> m_parser, m_count
|
# Private/Protected class members -> m_parser, m_count
|
||||||
- key: readability-identifier-naming.PrivateMemberCase
|
- key: readability-identifier-naming.PrivateMemberCase
|
||||||
|
|||||||
@ -17,7 +17,7 @@ set(SRC_FILES
|
|||||||
"imp/cpp/StreamReader.cpp"
|
"imp/cpp/StreamReader.cpp"
|
||||||
"imp/cpp/StreamWriter.cpp"
|
"imp/cpp/StreamWriter.cpp"
|
||||||
|
|
||||||
#"imp/cpp/Http/Common.cpp"
|
"imp/cpp/Http/Common.cpp"
|
||||||
#"imp/cpp/Http/Client.cpp"
|
#"imp/cpp/Http/Client.cpp"
|
||||||
#"imp/cpp/Http/Server.cpp"
|
#"imp/cpp/Http/Server.cpp"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -17,61 +17,73 @@
|
|||||||
#include <IACore/Http/Client.hpp>
|
#include <IACore/Http/Client.hpp>
|
||||||
|
|
||||||
namespace IACore {
|
namespace IACore {
|
||||||
Result<UniquePtr<HttpClient>> HttpClient::Create(const String &host) {
|
// Helper struct to access protected constructor via make_box (std::make_unique)
|
||||||
return MakeUniqueProtected<HttpClient>(httplib::Client(host));
|
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,
|
static auto build_headers(Span<const HttpClient::Header> headers,
|
||||||
const char *defaultContentType) {
|
const char *default_content_type)
|
||||||
|
-> httplib::Headers {
|
||||||
httplib::Headers out;
|
httplib::Headers out;
|
||||||
bool hasContentType = false;
|
bool has_content_type = false;
|
||||||
|
|
||||||
for (const auto &h : headers) {
|
for (const auto &h : headers) {
|
||||||
out.emplace(h.first, h.second);
|
out.emplace(h.first, h.second);
|
||||||
|
|
||||||
if (h.first ==
|
if (h.first == HttpClient::header_type_to_string(
|
||||||
HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE))
|
HttpClient::EHeaderType::CONTENT_TYPE)) {
|
||||||
hasContentType = true;
|
has_content_type = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasContentType && defaultContentType)
|
if (!has_content_type && default_content_type) {
|
||||||
out.emplace("Content-Type", defaultContentType);
|
out.emplace("Content-Type", default_content_type);
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient::HttpClient(httplib::Client &&client)
|
HttpClient::HttpClient(httplib::Client &&client)
|
||||||
: m_client(std::move(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);
|
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);
|
m_client.enable_server_certificate_verification(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClient::DisableCertificateVerfication() {
|
void HttpClient::disable_certificate_verification() {
|
||||||
m_client.enable_server_certificate_verification(false);
|
m_client.enable_server_certificate_verification(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
String HttpClient::PreprocessResponse(const String &response) {
|
auto HttpClient::preprocess_response(const String &response) -> String {
|
||||||
const auto responseBytes =
|
const auto response_bytes = Span<const u8>{
|
||||||
Span<const u8>{(const u8 *)response.data(), response.size()};
|
reinterpret_cast<const u8 *>(response.data()), response.size()};
|
||||||
const auto compression = DataOps::DetectCompression(responseBytes);
|
const auto compression = DataOps::detect_compression(response_bytes);
|
||||||
|
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
case DataOps::CompressionType::Gzip: {
|
case DataOps::CompressionType::Gzip: {
|
||||||
const auto data = DataOps::GZipInflate(responseBytes);
|
const auto data = DataOps::gzip_inflate(response_bytes);
|
||||||
if (!data)
|
if (!data) {
|
||||||
return response;
|
return response;
|
||||||
return String((const char *)data->data(), data->size());
|
}
|
||||||
|
return String(reinterpret_cast<const char *>(data->data()), data->size());
|
||||||
}
|
}
|
||||||
|
|
||||||
case DataOps::CompressionType::Zlib: {
|
case DataOps::CompressionType::Zlib: {
|
||||||
const auto data = DataOps::ZlibInflate(responseBytes);
|
const auto data = DataOps::zlib_inflate(response_bytes);
|
||||||
if (!data)
|
if (!data) {
|
||||||
return response;
|
return response;
|
||||||
return String((const char *)data->data(), data->size());
|
}
|
||||||
|
return String(reinterpret_cast<const char *>(data->data()), data->size());
|
||||||
}
|
}
|
||||||
|
|
||||||
case DataOps::CompressionType::None:
|
case DataOps::CompressionType::None:
|
||||||
@ -81,53 +93,58 @@ String HttpClient::PreprocessResponse(const String &response) {
|
|||||||
return 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,
|
String adjusted_path = path;
|
||||||
const char *defaultContentType) {
|
if (!path.empty() && path[0] != '/') {
|
||||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
adjusted_path = "/" + path;
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
String content_type = default_content_type;
|
||||||
const String &body, const char *defaultContentType) {
|
if (http_headers.count("Content-Type")) {
|
||||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
const auto t = http_headers.find("Content-Type");
|
||||||
|
content_type = t->second;
|
||||||
String contentType = defaultContentType;
|
http_headers.erase(t);
|
||||||
if (httpHeaders.count("Content-Type")) {
|
|
||||||
const auto t = httpHeaders.find("Content-Type");
|
|
||||||
contentType = t->second;
|
|
||||||
httpHeaders.erase(t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_client.set_keep_alive(true);
|
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) {
|
String adjusted_path = path;
|
||||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
if (!path.empty() && path[0] != '/') {
|
||||||
if (res->status >= 200 && res->status < 300)
|
adjusted_path = "/" + path;
|
||||||
return PreprocessResponse(res->body);
|
|
||||||
else
|
|
||||||
return (std::format("HTTP Error {} : {}", res->status, res->body));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -16,7 +16,7 @@
|
|||||||
#include <IACore/Http/Common.hpp>
|
#include <IACore/Http/Common.hpp>
|
||||||
|
|
||||||
namespace IACore {
|
namespace IACore {
|
||||||
String HttpCommon::UrlEncode(const String &value) {
|
auto HttpCommon::url_encode(const String &value) -> String {
|
||||||
std::stringstream escaped;
|
std::stringstream escaped;
|
||||||
escaped.fill('0');
|
escaped.fill('0');
|
||||||
escaped << std::hex << std::uppercase;
|
escaped << std::hex << std::uppercase;
|
||||||
@ -33,16 +33,16 @@ String HttpCommon::UrlEncode(const String &value) {
|
|||||||
return escaped.str();
|
return escaped.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
String HttpCommon::UrlDecode(const String &value) {
|
auto HttpCommon::url_decode(const String &value) -> String {
|
||||||
String result;
|
String result;
|
||||||
result.reserve(value.length());
|
result.reserve(value.length());
|
||||||
|
|
||||||
for (size_t i = 0; i < value.length(); ++i) {
|
for (size_t i = 0; i < value.length(); ++i) {
|
||||||
if (value[i] == '%' && i + 2 < value.length()) {
|
if (value[i] == '%' && i + 2 < value.length()) {
|
||||||
std::string hexStr = value.substr(i + 1, 2);
|
std::string hex_str = value.substr(i + 1, 2);
|
||||||
char decodedChar =
|
char decoded_char =
|
||||||
static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
|
static_cast<char>(std::strtol(hex_str.c_str(), nullptr, 16));
|
||||||
result += decodedChar;
|
result += decoded_char;
|
||||||
i += 2;
|
i += 2;
|
||||||
} else if (value[i] == '+')
|
} else if (value[i] == '+')
|
||||||
result += ' ';
|
result += ' ';
|
||||||
@ -53,7 +53,7 @@ String HttpCommon::UrlDecode(const String &value) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
String HttpCommon::HeaderTypeToString(EHeaderType type) {
|
auto HttpCommon::header_type_to_string(EHeaderType type) -> String {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case EHeaderType::ACCEPT:
|
case EHeaderType::ACCEPT:
|
||||||
return "Accept";
|
return "Accept";
|
||||||
@ -111,7 +111,7 @@ String HttpCommon::HeaderTypeToString(EHeaderType type) {
|
|||||||
return "<Unknown>";
|
return "<Unknown>";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpCommon::IsSuccessResponseCode(EResponseCode code) {
|
auto HttpCommon::is_success_response_code(EResponseCode code) -> bool {
|
||||||
return (i32)code >= 200 && (i32)code < 300;
|
return (i32)code >= 200 && (i32)code < 300;
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -15,7 +15,159 @@
|
|||||||
|
|
||||||
#include <IACore/Http/Server.hpp>
|
#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
|
||||||
@ -114,13 +114,17 @@ auto IpcNode::connect(const char *connection_string) -> Result<void> {
|
|||||||
u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
|
u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
|
||||||
u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
|
u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
|
||||||
|
|
||||||
m_moni = make_box<RingBufferView>(
|
IA_TRY(m_moni,
|
||||||
|
RingBufferView::create(
|
||||||
&layout->moni_control,
|
&layout->moni_control,
|
||||||
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)), false);
|
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)),
|
||||||
|
false));
|
||||||
|
|
||||||
m_mino = make_box<RingBufferView>(
|
IA_TRY(m_mino,
|
||||||
|
RingBufferView::create(
|
||||||
&layout->mino_control,
|
&layout->mino_control,
|
||||||
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)), false);
|
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)),
|
||||||
|
false));
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
u_long mode = 1;
|
u_long mode = 1;
|
||||||
@ -135,14 +139,14 @@ auto IpcNode::connect(const char *connection_string) -> Result<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IpcNode::update() {
|
void IpcNode::update() {
|
||||||
if (!m_moni) {
|
if (!m_moni.is_valid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcPacketHeader header;
|
IpcPacketHeader header;
|
||||||
|
|
||||||
// Process all available messages from Manager
|
// 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()))) {
|
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||||
on_packet(header.id, {m_receive_buffer.data(), header.payload_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) {
|
auto IpcNode::send_packet(u16 packet_id, Span<const u8> payload)
|
||||||
if (m_mino) {
|
-> Result<void> {
|
||||||
m_mino->push(packet_id, payload);
|
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,
|
auto IpcManager::NodeSession::send_packet(u16 packet_id, Span<const u8> payload)
|
||||||
Span<const u8> payload) {
|
-> Result<void> {
|
||||||
// Protect the RingBuffer write cursor from concurrent threads
|
// Protect the RingBuffer write cursor from concurrent threads
|
||||||
std::scoped_lock lock(send_mutex);
|
std::scoped_lock lock(send_mutex);
|
||||||
if (moni) {
|
if (!moni.is_valid())
|
||||||
moni->push(packet_id, payload);
|
return fail("invalid MONI");
|
||||||
}
|
return moni.push(packet_id, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcManager::IpcManager() {
|
IpcManager::IpcManager() {
|
||||||
// SocketOps is smart enough to track multiple inits
|
ensure(SocketOps::is_initialized(),
|
||||||
SocketOps::initialize();
|
"SocketOps must be initialized before using IpcManager");
|
||||||
|
|
||||||
m_receive_buffer.resize(UINT16_MAX + 1);
|
m_receive_buffer.resize(UINT16_MAX + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,9 +220,6 @@ IpcManager::~IpcManager() {
|
|||||||
SocketOps::close(session->listener_socket);
|
SocketOps::close(session->listener_socket);
|
||||||
}
|
}
|
||||||
m_pending_sessions.clear();
|
m_pending_sessions.clear();
|
||||||
|
|
||||||
// SocketOps is smart enough to track multiple terminates
|
|
||||||
SocketOps::terminate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IpcManager::update() {
|
void IpcManager::update() {
|
||||||
@ -270,7 +273,7 @@ void IpcManager::update() {
|
|||||||
|
|
||||||
IpcPacketHeader header;
|
IpcPacketHeader header;
|
||||||
|
|
||||||
while (node->mino->pop(
|
while (node->mino.pop(
|
||||||
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||||
on_packet(node_id, header.id,
|
on_packet(node_id, header.id,
|
||||||
{m_receive_buffer.data(), header.payload_size});
|
{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_offset = header_size + half_size;
|
||||||
layout->mino_data_size = half_size;
|
layout->mino_data_size = half_size;
|
||||||
|
|
||||||
session->moni = make_box<RingBufferView>(
|
IA_TRY(session->moni,
|
||||||
|
RingBufferView::create(
|
||||||
&layout->moni_control,
|
&layout->moni_control,
|
||||||
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
|
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
|
||||||
static_cast<usize>(layout->moni_data_size)),
|
static_cast<usize>(layout->moni_data_size)),
|
||||||
true);
|
true));
|
||||||
|
|
||||||
session->mino = make_box<RingBufferView>(
|
IA_TRY(session->mino,
|
||||||
|
RingBufferView::create(
|
||||||
&layout->mino_control,
|
&layout->mino_control,
|
||||||
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
|
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
|
||||||
static_cast<usize>(layout->mino_data_size)),
|
static_cast<usize>(layout->mino_data_size)),
|
||||||
true);
|
true));
|
||||||
|
|
||||||
IpcConnectionDescriptor desc;
|
IpcConnectionDescriptor desc;
|
||||||
desc.socket_path = sock_path;
|
desc.socket_path = sock_path;
|
||||||
@ -446,12 +451,11 @@ void IpcManager::send_signal(NativeProcessID node, u8 signal) {
|
|||||||
it_node->second->send_signal(signal);
|
it_node->second->send_signal(signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IpcManager::send_packet(NativeProcessID node, u16 packet_id,
|
auto IpcManager::send_packet(NativeProcessID node, u16 packet_id,
|
||||||
Span<const u8> payload) {
|
Span<const u8> payload) -> Result<void> {
|
||||||
const auto it_node = m_active_session_map.find(node);
|
const auto it_node = m_active_session_map.find(node);
|
||||||
if (it_node == m_active_session_map.end()) {
|
if (it_node == m_active_session_map.end())
|
||||||
return;
|
return fail("no such node");
|
||||||
}
|
return it_node->second->send_packet(packet_id, payload);
|
||||||
it_node->second->send_packet(packet_id, payload);
|
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -53,8 +53,11 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RingBufferView(Span<u8> buffer, bool is_owner);
|
static auto default_instance() -> RingBufferView;
|
||||||
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
|
|
||||||
|
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:
|
// Returns:
|
||||||
// - Ok(nullopt) if empty
|
// - Ok(nullopt) if empty
|
||||||
@ -70,6 +73,12 @@ public:
|
|||||||
|
|
||||||
auto get_control_block() -> ControlBlock *;
|
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:
|
private:
|
||||||
u8 *m_data_ptr{};
|
u8 *m_data_ptr{};
|
||||||
u32 m_capacity{};
|
u32 m_capacity{};
|
||||||
@ -84,10 +93,40 @@ private:
|
|||||||
// Implementation
|
// Implementation
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
|
auto RingBufferView::default_instance() -> RingBufferView {
|
||||||
ensure(buffer.size() > sizeof(ControlBlock),
|
return RingBufferView(nullptr, {}, false);
|
||||||
"Buffer too small for ControlBlock");
|
}
|
||||||
|
|
||||||
|
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_control_block = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||||
m_data_ptr = buffer.data() + sizeof(ControlBlock);
|
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->consumer.capacity = m_capacity;
|
||||||
m_control_block->producer.write_offset.store(0, std::memory_order_release);
|
m_control_block->producer.write_offset.store(0, std::memory_order_release);
|
||||||
m_control_block->consumer.read_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,
|
inline RingBufferView::RingBufferView(ControlBlock *control_block,
|
||||||
Span<u8> buffer, bool is_owner) {
|
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_control_block = control_block;
|
||||||
m_data_ptr = buffer.data();
|
m_data_ptr = buffer.data();
|
||||||
m_capacity = static_cast<u32>(buffer.size());
|
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)
|
inline auto RingBufferView::push(u16 packet_id, Span<const u8> data)
|
||||||
-> Result<void> {
|
-> Result<void> {
|
||||||
ensure(data.size() <= std::numeric_limits<u16>::max(),
|
if (data.size() > std::numeric_limits<u16>::max()) {
|
||||||
"Data size exceeds u16 limit");
|
return fail("Data size exceeds u16 limit");
|
||||||
|
}
|
||||||
|
|
||||||
const u32 total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
|
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
|
} // namespace IACore
|
||||||
@ -17,26 +17,63 @@
|
|||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if !IA_PLATFORM_WINDOWS
|
||||||
#include <errhandlingapi.h>
|
|
||||||
#include <libloaderapi.h>
|
|
||||||
#else
|
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Ensure 'ia' namespace alias exists for IA_TRY macro usage
|
||||||
|
namespace ia = IACore;
|
||||||
|
|
||||||
namespace IACore {
|
namespace IACore {
|
||||||
|
|
||||||
class DynamicLib {
|
class DynamicLib {
|
||||||
public:
|
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;
|
other.m_handle = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT {
|
auto operator=(DynamicLib &&other) noexcept -> DynamicLib & {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
Unload(); // Free current if exists
|
unload();
|
||||||
m_handle = other.m_handle;
|
m_handle = other.m_handle;
|
||||||
other.m_handle = nullptr;
|
other.m_handle = nullptr;
|
||||||
}
|
}
|
||||||
@ -44,88 +81,46 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
DynamicLib(const DynamicLib &) = delete;
|
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
|
[[nodiscard]] auto get_symbol(const String &name) const -> Result<void *> {
|
||||||
NO_DISCARD("Check for load errors")
|
if (!m_handle) {
|
||||||
|
return fail("Library not loaded");
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
void *sym = nullptr;
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), void *);
|
sym = static_cast<void *>(
|
||||||
if (!sym)
|
GetProcAddress(static_cast<HMODULE>(m_handle), name.c_str()));
|
||||||
return (GetWindowsError());
|
if (!sym) {
|
||||||
|
return fail(get_windows_error());
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
// Clear any previous error
|
// Clear any previous error
|
||||||
dlerror();
|
dlerror();
|
||||||
sym = dlsym(m_handle, name.c_str());
|
sym = dlsym(m_handle, name.c_str());
|
||||||
const char *err = dlerror();
|
if (const char *err = dlerror()) {
|
||||||
if (err)
|
return fail(err);
|
||||||
return (String(err));
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template helper for casting
|
|
||||||
template <typename FuncT>
|
template <typename FuncT>
|
||||||
tl::Result<FuncT> GetFunction(const String &name) const {
|
[[nodiscard]] auto get_function(const String &name) const -> Result<FuncT> {
|
||||||
auto res = GetSymbol(name);
|
void *sym = nullptr;
|
||||||
if (!res)
|
IA_TRY(sym, get_symbol(name));
|
||||||
return (res.error());
|
return reinterpret_cast<FuncT>(sym);
|
||||||
return REINTERPRET(*res, FuncT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unload() {
|
void unload() {
|
||||||
if (m_handle) {
|
if (m_handle) {
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
FreeLibrary(CAST(m_handle, HMODULE));
|
FreeLibrary(static_cast<HMODULE>(m_handle));
|
||||||
#else
|
#else
|
||||||
dlclose(m_handle);
|
dlclose(m_handle);
|
||||||
#endif
|
#endif
|
||||||
@ -133,28 +128,30 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsLoaded() const { return m_handle != nullptr; }
|
[[nodiscard]] auto is_loaded() const -> bool { return m_handle != nullptr; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void *m_handle;
|
void *m_handle = nullptr;
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
static String GetWindowsError() {
|
static auto get_windows_error() -> String {
|
||||||
DWORD errorID = ::GetLastError();
|
const DWORD error_id = ::GetLastError();
|
||||||
if (errorID == 0)
|
if (error_id == 0) {
|
||||||
return String();
|
return String();
|
||||||
|
}
|
||||||
|
|
||||||
LPSTR messageBuffer = nullptr;
|
LPSTR message_buffer = nullptr;
|
||||||
size_t size = FormatMessageA(
|
const usize size = FormatMessageA(
|
||||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
nullptr, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
(LPSTR)&messageBuffer, 0, NULL);
|
reinterpret_cast<LPSTR>(&message_buffer), 0, nullptr);
|
||||||
|
|
||||||
String message(messageBuffer, size);
|
String message(message_buffer, size);
|
||||||
LocalFree(messageBuffer);
|
LocalFree(message_buffer);
|
||||||
return String("Win32 Error: ") + message;
|
return "Win32 Error: " + message;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -16,88 +16,83 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class Environment {
|
||||||
class Environment
|
public:
|
||||||
{
|
static auto find(const String &name) -> Option<String> {
|
||||||
public:
|
|
||||||
static Optional<String> Find(const String &name)
|
|
||||||
{
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#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)
|
if (buffer_size == 0) {
|
||||||
{
|
|
||||||
// DWORD 0 means failed
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Allocate (bufferSize includes the null terminator request)
|
|
||||||
String result;
|
String result;
|
||||||
result.resize(bufferSize);
|
result.resize(buffer_size);
|
||||||
|
|
||||||
// 3. Fetch
|
const DWORD actual_size =
|
||||||
// Returns num chars written excluding null terminator
|
GetEnvironmentVariableA(name.c_str(), result.data(), buffer_size);
|
||||||
DWORD actualSize = GetEnvironmentVariableA(name.c_str(), result.data(), bufferSize);
|
|
||||||
|
|
||||||
if (actualSize == 0 || actualSize > bufferSize)
|
if (actual_size == 0 || actual_size > buffer_size) {
|
||||||
{
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize down to exclude the null terminator and any slack
|
// Resize down to exclude the null terminator and any slack
|
||||||
result.resize(actualSize);
|
result.resize(actual_size);
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// getenv returns a pointer to the environment area
|
|
||||||
const char *val = std::getenv(name.c_str());
|
const char *val = std::getenv(name.c_str());
|
||||||
if (val == nullptr)
|
if (val == nullptr) {
|
||||||
{
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return String(val);
|
return String(val);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static String Get(const String &name, const String &defaultValue = "")
|
static auto get(const String &name, const String &default_value = "")
|
||||||
{
|
-> String {
|
||||||
return Find(name).value_or(defaultValue);
|
return find(name).value_or(default_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Set(const String &name, const String &value)
|
static auto set(const String &name, const String &value) -> Result<void> {
|
||||||
{
|
if (name.empty()) {
|
||||||
if (name.empty())
|
return fail("Environment variable name cannot be empty");
|
||||||
return FALSE;
|
}
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#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
|
#else
|
||||||
// Returns 0 on success, -1 on error
|
if (setenv(name.c_str(), value.c_str(), 1) != 0) {
|
||||||
return setenv(name.c_str(), value.c_str(), 1) == 0;
|
return fail("Failed to set environment variable: {}", name);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Unset(const String &name)
|
static auto unset(const String &name) -> Result<void> {
|
||||||
{
|
if (name.empty()) {
|
||||||
if (name.empty())
|
return fail("Environment variable name cannot be empty");
|
||||||
return FALSE;
|
}
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#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
|
#else
|
||||||
return unsetenv(name.c_str()) == 0;
|
if (unsetenv(name.c_str()) != 0) {
|
||||||
|
return fail("Failed to unset environment variable: {}", name);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Exists(const String &name)
|
static auto exists(const String &name) -> bool {
|
||||||
{
|
return find(name).has_value();
|
||||||
#if IA_PLATFORM_WINDOWS
|
|
||||||
return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0;
|
|
||||||
#else
|
|
||||||
return std::getenv(name.c_str()) != nullptr;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -16,73 +16,83 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/Http/Common.hpp>
|
#include <IACore/Http/Common.hpp>
|
||||||
|
#include <IACore/JSON.hpp>
|
||||||
|
|
||||||
namespace IACore {
|
namespace IACore {
|
||||||
class HttpClient : public HttpCommon {
|
class HttpClient : public HttpCommon {
|
||||||
public:
|
public:
|
||||||
static Result<UniquePtr<HttpClient>> Create(const String &host);
|
static auto create(const String &host) -> Result<Box<HttpClient>>;
|
||||||
|
|
||||||
~HttpClient();
|
~HttpClient();
|
||||||
|
|
||||||
public:
|
HttpClient(HttpClient &&) = default;
|
||||||
Result<String>
|
HttpClient(const HttpClient &) = delete;
|
||||||
RawGet(const String &path, Span<const Header> headers,
|
auto operator=(HttpClient &&) -> HttpClient & = default;
|
||||||
const char *defaultContentType = "application/x-www-form-urlencoded");
|
auto operator=(const HttpClient &) -> HttpClient & = delete;
|
||||||
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();
|
|
||||||
|
|
||||||
public:
|
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:
|
private:
|
||||||
httplib::Client m_client;
|
httplib::Client m_client;
|
||||||
EResponseCode m_lastResponseCode;
|
EResponseCode m_last_response_code;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String PreprocessResponse(const String &response);
|
auto preprocess_response(const String &response) -> String;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
HttpClient(httplib::Client &&client);
|
explicit HttpClient(httplib::Client &&client);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename _response_type>
|
template <typename ResponseType>
|
||||||
Result<_response_type> HttpClient::JsonGet(const String &path,
|
auto HttpClient::json_get(const String &path, Span<const Header> headers)
|
||||||
Span<const Header> headers) {
|
-> Result<ResponseType> {
|
||||||
const auto rawResponse = RawGet(path, headers, "application/json");
|
String raw_response;
|
||||||
if (!rawResponse)
|
IA_TRY(raw_response, raw_get(path, headers, "application/json"));
|
||||||
return (rawResponse.error());
|
|
||||||
if (LastResponseCode() != EResponseCode::OK)
|
if (last_response_code() != EResponseCode::OK) {
|
||||||
return (
|
return fail("Server responded with code {}",
|
||||||
std::format("Server responded with code {}", (i32)LastResponseCode()));
|
static_cast<i32>(last_response_code()));
|
||||||
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
}
|
||||||
|
return Json::parse_to_struct<ResponseType>(raw_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename _payload_type, typename _response_type>
|
template <typename PayloadType, typename ResponseType>
|
||||||
Result<_response_type> HttpClient::JsonPost(const String &path,
|
auto HttpClient::json_post(const String &path, Span<const Header> headers,
|
||||||
Span<const Header> headers,
|
const PayloadType &body) -> Result<ResponseType> {
|
||||||
const _payload_type &body) {
|
String encoded_body;
|
||||||
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
|
IA_TRY(encoded_body, Json::encode_struct(body));
|
||||||
const auto rawResponse =
|
|
||||||
RawPost(path, headers, encodedBody, "application/json");
|
String raw_response;
|
||||||
if (!rawResponse)
|
IA_TRY(raw_response,
|
||||||
return (rawResponse.error());
|
raw_post(path, headers, encoded_body, "application/json"));
|
||||||
if (LastResponseCode() != EResponseCode::OK)
|
|
||||||
return (
|
if (last_response_code() != EResponseCode::OK) {
|
||||||
std::format("Server responded with code {}", (i32)LastResponseCode()));
|
return fail("Server responded with code {}",
|
||||||
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
static_cast<i32>(last_response_code()));
|
||||||
|
}
|
||||||
|
return Json::parse_to_struct<ResponseType>(raw_response);
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -125,29 +125,31 @@ public:
|
|||||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||||
};
|
};
|
||||||
|
|
||||||
using Header = KeyValuePair<String, String>;
|
using Header = Pair<String, String>;
|
||||||
|
|
||||||
static String UrlEncode(const String &value);
|
static auto url_encode(const String &value) -> String;
|
||||||
static String UrlDecode(const String &value);
|
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 auto create_header(EHeaderType key, const String &value)
|
||||||
static inline Header CreateHeader(const String &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:
|
protected:
|
||||||
HttpCommon() = default;
|
HttpCommon() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpCommon::Header HttpCommon::CreateHeader(EHeaderType key,
|
auto HttpCommon::create_header(EHeaderType key, const String &value)
|
||||||
const String &value) {
|
-> HttpCommon::Header {
|
||||||
return std::make_pair(HeaderTypeToString(key), value);
|
return std::make_pair(header_type_to_string(key), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpCommon::Header HttpCommon::CreateHeader(const String &key,
|
auto HttpCommon::create_header(const String &key, const String &value)
|
||||||
const String &value) {
|
-> HttpCommon::Header {
|
||||||
return std::make_pair(key, value);
|
return std::make_pair(key, value);
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -16,12 +16,138 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/Http/Common.hpp>
|
#include <IACore/Http/Common.hpp>
|
||||||
|
#include <IACore/JSON.hpp>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class HttpServer : public HttpCommon {
|
||||||
class HttpServer : public HttpCommon
|
public:
|
||||||
{
|
struct Request {
|
||||||
public:
|
String path;
|
||||||
HttpServer(const String &host, u32 port);
|
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
|
} // namespace IACore
|
||||||
@ -76,7 +76,7 @@ public:
|
|||||||
void update();
|
void update();
|
||||||
|
|
||||||
void send_signal(u8 signal);
|
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:
|
protected:
|
||||||
virtual void on_signal(u8 signal) = 0;
|
virtual void on_signal(u8 signal) = 0;
|
||||||
@ -88,8 +88,8 @@ private:
|
|||||||
Vec<u8> m_receive_buffer;
|
Vec<u8> m_receive_buffer;
|
||||||
SocketHandle m_socket{INVALID_SOCKET};
|
SocketHandle m_socket{INVALID_SOCKET};
|
||||||
|
|
||||||
Box<RingBufferView> m_moni; // Manager Out, Node In
|
RingBufferView m_moni; // Manager Out, Node In
|
||||||
Box<RingBufferView> m_mino; // Manager In, Node Out
|
RingBufferView m_mino; // Manager In, Node Out
|
||||||
};
|
};
|
||||||
|
|
||||||
class IpcManager {
|
class IpcManager {
|
||||||
@ -105,13 +105,15 @@ class IpcManager {
|
|||||||
SocketHandle listener_socket{INVALID_SOCKET};
|
SocketHandle listener_socket{INVALID_SOCKET};
|
||||||
SocketHandle data_socket{INVALID_SOCKET};
|
SocketHandle data_socket{INVALID_SOCKET};
|
||||||
|
|
||||||
Box<RingBufferView> moni; // Manager Out, Node In
|
RingBufferView moni =
|
||||||
Box<RingBufferView> mino; // Manager In, Node Out
|
RingBufferView::default_instance(); // Manager Out, Node In
|
||||||
|
RingBufferView mino =
|
||||||
|
RingBufferView::default_instance(); // Manager In, Node Out
|
||||||
|
|
||||||
bool is_ready{false};
|
bool is_ready{false};
|
||||||
|
|
||||||
void send_signal(u8 signal);
|
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:
|
public:
|
||||||
@ -131,7 +133,8 @@ public:
|
|||||||
void shutdown_node(NativeProcessID node);
|
void shutdown_node(NativeProcessID node);
|
||||||
|
|
||||||
void send_signal(NativeProcessID node, u8 signal);
|
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:
|
protected:
|
||||||
virtual void on_signal(NativeProcessID node, u8 signal) = 0;
|
virtual void on_signal(NativeProcessID node, u8 signal) = 0;
|
||||||
|
|||||||
@ -77,6 +77,8 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static auto is_initialized() -> bool { return s_init_count > 0; }
|
||||||
|
|
||||||
static auto is_port_available_tcp(u16 port) -> bool {
|
static auto is_port_available_tcp(u16 port) -> bool {
|
||||||
return is_port_available(port, SOCK_STREAM);
|
return is_port_available(port, SOCK_STREAM);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user