diff --git a/Kinc/Backends/System/Linux/Sources/kinc/backend/funcs.h b/Kinc/Backends/System/Linux/Sources/kinc/backend/funcs.h
index 96182f8..b9efce7 100644
--- a/Kinc/Backends/System/Linux/Sources/kinc/backend/funcs.h
+++ b/Kinc/Backends/System/Linux/Sources/kinc/backend/funcs.h
@@ -29,6 +29,7 @@ struct linux_procs {
int (*window_display)(int window_index);
void (*window_show)(int window_index);
void (*window_hide)(int window_index);
+ void (*window_set_foreground)(int window_index);
void (*window_set_title)(int window_index, const char *title);
void (*window_change_mode)(int window_index, kinc_window_mode_t mode);
kinc_window_mode_t (*window_get_mode)(int window_index);
diff --git a/Kinc/Backends/System/Linux/Sources/kinc/backend/linuxunit.c b/Kinc/Backends/System/Linux/Sources/kinc/backend/linuxunit.c
index e72969b..832dddc 100644
--- a/Kinc/Backends/System/Linux/Sources/kinc/backend/linuxunit.c
+++ b/Kinc/Backends/System/Linux/Sources/kinc/backend/linuxunit.c
@@ -59,6 +59,7 @@ void kinc_linux_init_procs() {
procs.window_resize = kinc_wayland_window_resize;
procs.window_show = kinc_wayland_window_show;
procs.window_hide = kinc_wayland_window_hide;
+ procs.window_set_foreground = kinc_wayland_window_set_foreground;
procs.count_windows = kinc_wayland_count_windows;
procs.mouse_can_lock = kinc_wl_mouse_can_lock;
@@ -111,6 +112,8 @@ void kinc_linux_init_procs() {
procs.window_resize = kinc_x11_window_resize;
procs.window_show = kinc_x11_window_show;
procs.window_hide = kinc_x11_window_hide;
+ procs.window_set_foreground = kinc_x11_window_set_foreground;
+
procs.count_windows = kinc_x11_count_windows;
procs.display_init = kinc_x11_display_init;
diff --git a/Kinc/Backends/System/Linux/Sources/kinc/backend/window.c.h b/Kinc/Backends/System/Linux/Sources/kinc/backend/window.c.h
index ff54ba7..4f2599c 100644
--- a/Kinc/Backends/System/Linux/Sources/kinc/backend/window.c.h
+++ b/Kinc/Backends/System/Linux/Sources/kinc/backend/window.c.h
@@ -80,6 +80,10 @@ void kinc_window_hide(int window_index) {
procs.window_hide(window_index);
}
+void kinc_window_set_foreground(int window_index) {
+ procs.window_set_foreground(window_index);
+}
+
void kinc_window_set_title(int window_index, const char *title) {
procs.window_set_title(window_index, title);
}
diff --git a/Kinc/Backends/System/Linux/Sources/kinc/backend/x11/window.c.h b/Kinc/Backends/System/Linux/Sources/kinc/backend/x11/window.c.h
index fb8ada0..c2b76b6 100644
--- a/Kinc/Backends/System/Linux/Sources/kinc/backend/x11/window.c.h
+++ b/Kinc/Backends/System/Linux/Sources/kinc/backend/x11/window.c.h
@@ -145,6 +145,16 @@ void kinc_x11_window_hide(int window_index) {
xlib.XUnmapWindow(x11_ctx.display, window->window);
}
+void kinc_x11_window_set_foreground(int window_index) {
+ struct kinc_x11_window *window = &x11_ctx.windows[window_index];
+ if (window->window == None) {
+ return;
+ }
+ xlib.XRaiseWindow(x11_ctx.display, window->window);
+ xlib.XSetInputFocus(x11_ctx.display, window->window, RevertToParent, CurrentTime);
+ xlib.XFlush(x11_ctx.display);
+}
+
kinc_window_mode_t kinc_x11_window_get_mode(int window_index) {
return x11_ctx.windows[window_index].mode;
}
diff --git a/Kinc/Backends/System/Windows/Sources/kinc/backend/window.c.h b/Kinc/Backends/System/Windows/Sources/kinc/backend/window.c.h
index e50392e..c46cccc 100644
--- a/Kinc/Backends/System/Windows/Sources/kinc/backend/window.c.h
+++ b/Kinc/Backends/System/Windows/Sources/kinc/backend/window.c.h
@@ -392,6 +392,11 @@ void kinc_window_hide(int window_index) {
UpdateWindow(windows[window_index].handle);
}
+void kinc_window_set_foreground(int window_index) {
+ SetForegroundWindow(windows[window_index].handle);
+ SetFocus(windows[window_index].handle);
+}
+
void kinc_window_set_title(int window_index, const char *title) {
wchar_t buffer[1024];
MultiByteToWideChar(CP_UTF8, 0, title, -1, buffer, 1024);
diff --git a/Kinc/Sources/kinc/window.h b/Kinc/Sources/kinc/window.h
index 3610f7b..ba863ee 100644
--- a/Kinc/Sources/kinc/window.h
+++ b/Kinc/Sources/kinc/window.h
@@ -139,6 +139,11 @@ KINC_FUNC void kinc_window_show(int window);
///
KINC_FUNC void kinc_window_hide(int window);
+///
+/// Set a window to the foreground.
+///
+KINC_FUNC void kinc_window_set_foreground(int window);
+
///
/// Sets the title of a window.
///
diff --git a/README.md b/README.md
index cbcad28..47e0f58 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,20 @@
-# LNXCORE
+# LNXRNT
-LeenkxCore for Leenkx Full Stack SDK.
+Leenkx RunT - Runtime for Leenkx Full Stack SDK.
Based on [Krom](https://github.com/Kode/Krom). Powered by [Kinc](https://github.com/Kode/Kinc).
```bash
-git clone --recursive https://dev.leenkx.com/LeenkxTeam/LNXCORE
-cd LNXCORE
+git clone --recursive https://dev.leenkx.com/LeenkxTeam/LNXRNT
+cd LNXRNT
```
**Windows**
```bash
# Unpack `v8\libraries\win32\release\v8_monolith.7z` using 7-Zip - Extract Here (exceeds 100MB)
-Kinc/make -g direct3d11
-# Open generated Visual Studio project at `build\Krom.sln`
+Kinc\make -g direct3d11
+# Open generated Visual Studio project at `build\RunT.sln`
+# or use command line for solution file like C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" "build\Leenkx-RunT.sln" /p:Configuration=Release /p:Platform=x64 /m
# Build for x64 & release
```
@@ -21,12 +22,12 @@ Kinc/make -g direct3d11
```bash
Kinc/make -g opengl --compiler clang --compile
cd Deployment
-strip Krom
+strip RunT
```
**macOS**
```bash
Kinc/make -g metal
-# Open generated Xcode project at `build/Krom.xcodeproj`
+# Open generated Xcode project at `build/RunT.xcodeproj`
# Build
```
diff --git a/Sources/async_engine.cpp b/Sources/async_engine.cpp
new file mode 100644
index 0000000..c7011c2
--- /dev/null
+++ b/Sources/async_engine.cpp
@@ -0,0 +1,9 @@
+#include "async_engine.h"
+#include
+
+namespace EngineManager {
+
+std::unique_ptr AsyncEngine::instance_;
+std::once_flag AsyncEngine::initialized_;
+
+}
\ No newline at end of file
diff --git a/Sources/async_engine.h b/Sources/async_engine.h
new file mode 100644
index 0000000..2ab7584
--- /dev/null
+++ b/Sources/async_engine.h
@@ -0,0 +1,199 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace EngineManager {
+
+// thread communication
+template
+class LockFreeQueue {
+private:
+ struct Node {
+ std::atomic data{nullptr};
+ std::atomic next{nullptr};
+ };
+
+ std::atomic head_{nullptr};
+ std::atomic tail_{nullptr};
+
+public:
+ LockFreeQueue() {
+ Node* dummy = new Node;
+ head_.store(dummy);
+ tail_.store(dummy);
+ }
+
+ ~LockFreeQueue() {
+ while (Node* const old_head = head_.load()) {
+ head_.store(old_head->next);
+ delete old_head->data.load();
+ delete old_head;
+ }
+ }
+
+ void push(T item) {
+ Node* new_node = new Node;
+ T* data = new T(std::move(item));
+ new_node->data.store(data);
+
+ Node* prev_tail = tail_.exchange(new_node);
+ prev_tail->next.store(new_node);
+ }
+
+ bool try_pop(T& result) {
+ Node* head = head_.load();
+ Node* next = head->next.load();
+
+ if (next == nullptr) return false;
+
+ T* data = next->data.load();
+ if (data == nullptr) return false;
+
+ result = *data;
+ delete data;
+ head_.store(next);
+ delete head;
+ return true;
+ }
+
+ bool empty() const {
+ Node* head = head_.load();
+ return (head->next.load() == nullptr);
+ }
+};
+
+// thread pool stealing
+class ThreadPool {
+private:
+ std::vector workers_;
+ std::queue> tasks_;
+ std::mutex queue_mutex_;
+ std::condition_variable condition_;
+ std::atomic stop_{false};
+
+public:
+ explicit ThreadPool(size_t num_threads = std::thread::hardware_concurrency()) {
+ for (size_t i = 0; i < num_threads; ++i) {
+ workers_.emplace_back([this] {
+ for (;;) {
+ std::function task;
+
+ {
+ std::unique_lock lock(queue_mutex_);
+ condition_.wait(lock, [this] { return stop_ || !tasks_.empty(); });
+
+ if (stop_ && tasks_.empty()) return;
+
+ task = std::move(tasks_.front());
+ tasks_.pop();
+ }
+
+ task();
+ }
+ });
+ }
+ }
+
+ template
+ auto enqueue(F&& f, Args&&... args) -> std::future::type> {
+ using return_type = typename std::invoke_result::type;
+
+ auto task = std::make_shared>(
+ std::bind(std::forward(f), std::forward(args)...)
+ );
+
+ std::future res = task->get_future();
+
+ {
+ std::unique_lock lock(queue_mutex_);
+ if (stop_) {
+ throw std::runtime_error("ThreadPool: enqueue stopped");
+ }
+
+ tasks_.emplace([task](){ (*task)(); });
+ }
+
+ condition_.notify_one();
+ return res;
+ }
+
+ size_t pending_tasks() const {
+ std::lock_guard lock(const_cast(queue_mutex_));
+ return tasks_.size();
+ }
+
+ ~ThreadPool() {
+ stop_ = true;
+ condition_.notify_all();
+
+ for (std::thread& worker : workers_) {
+ worker.join();
+ }
+ }
+};
+
+class AsyncEngine {
+private:
+ static std::unique_ptr instance_;
+ static std::once_flag initialized_;
+
+ std::unique_ptr main_pool_;
+ std::unique_ptr io_pool_;
+ LockFreeQueue> event_queue_;
+
+ AsyncEngine() {
+ unsigned int hw_threads = std::thread::hardware_concurrency();
+ size_t main_threads = hw_threads > 4 ? hw_threads / 2 : 2;
+ size_t io_threads = hw_threads > 8 ? 4 : 2;
+
+ main_pool_ = std::make_unique(main_threads);
+ io_pool_ = std::make_unique(io_threads);
+ }
+
+public:
+ static AsyncEngine& instance() {
+ std::call_once(initialized_, []() {
+ instance_ = std::unique_ptr(new AsyncEngine());
+ });
+ return *instance_;
+ }
+
+ // main
+ template
+ auto execute(F&& f, Args&&... args) -> std::future::type> {
+ return main_pool_->enqueue(std::forward(f), std::forward(args)...);
+ }
+
+ template
+ auto execute_io(F&& f, Args&&... args) -> std::future::type> {
+ return io_pool_->enqueue(std::forward(f), std::forward(args)...);
+ }
+
+ void push_event(std::function event) {
+ event_queue_.push(std::move(event));
+ }
+
+ void process_events() {
+ std::function event;
+ while (event_queue_.try_pop(event)) {
+ event();
+ }
+ }
+
+ size_t pending_main_tasks() const { return main_pool_->pending_tasks(); }
+ size_t pending_io_tasks() const { return io_pool_->pending_tasks(); }
+
+ static void shutdown() {
+ instance_.reset();
+ }
+};
+
+}
diff --git a/Sources/broadcast_queue.h b/Sources/broadcast_queue.h
new file mode 100644
index 0000000..3023832
--- /dev/null
+++ b/Sources/broadcast_queue.h
@@ -0,0 +1,75 @@
+#pragma once
+#include
+#include
+#include
+
+// message queue
+struct BroadcastMessage {
+ std::vector frameData;
+ int excludeClientId;
+
+ BroadcastMessage() = default;
+ BroadcastMessage(std::vector data, int excludeId)
+ : frameData(std::move(data)), excludeClientId(excludeId) {}
+};
+
+class LockFreeBroadcastQueue {
+private:
+ static const size_t QUEUE_SIZE = 65536; // Must be power of 2
+ static const size_t QUEUE_MASK = QUEUE_SIZE - 1;
+
+ struct alignas(64) QueueSlot {
+ std::atomic ready{false};
+ BroadcastMessage message;
+ };
+
+ alignas(64) std::atomic head{0};
+ alignas(64) std::atomic tail{0};
+ QueueSlot queue[QUEUE_SIZE];
+
+public:
+ bool push(BroadcastMessage&& message) {
+ const size_t currentTail = tail.load(std::memory_order_relaxed);
+ const size_t nextTail = (currentTail + 1) & QUEUE_MASK;
+
+ // queue full
+ if (nextTail == head.load(std::memory_order_acquire)) {
+ return false;
+ }
+
+ QueueSlot& slot = queue[currentTail];
+ slot.message = std::move(message);
+ slot.ready.store(true, std::memory_order_release);
+ tail.store(nextTail, std::memory_order_release);
+
+ return true;
+ }
+
+ bool pop(BroadcastMessage& message) {
+ const size_t currentHead = head.load(std::memory_order_relaxed);
+
+ // empty
+ if (currentHead == tail.load(std::memory_order_acquire)) {
+ return false;
+ }
+
+ QueueSlot& slot = queue[currentHead];
+ if (!slot.ready.load(std::memory_order_acquire)) {
+ return false;
+ }
+
+ message = std::move(slot.message);
+ slot.ready.store(false, std::memory_order_release);
+ head.store((currentHead + 1) & QUEUE_MASK, std::memory_order_release);
+
+ return true;
+ }
+
+ size_t size() const {
+ return (tail.load(std::memory_order_relaxed) - head.load(std::memory_order_relaxed)) & QUEUE_MASK;
+ }
+
+ bool empty() const {
+ return head.load(std::memory_order_relaxed) == tail.load(std::memory_order_relaxed);
+ }
+};
diff --git a/Sources/connection_pool.cpp b/Sources/connection_pool.cpp
new file mode 100644
index 0000000..8a6a3c1
--- /dev/null
+++ b/Sources/connection_pool.cpp
@@ -0,0 +1,181 @@
+#include "connection_pool.h"
+#include "socket_optimization.h"
+#include
+
+#ifdef _WIN32
+#include
+#pragma comment(lib, "ws2_32.lib")
+#else
+#include
+#include
+#include
+#include
+#include
+#endif
+
+// global
+ConnectionPool g_connection_pool;
+
+ConnectionPool::~ConnectionPool() {
+ std::lock_guard lock(pool_mutex_);
+
+ for (auto& host_pair : pool_) {
+ while (!host_pair.second.empty()) {
+ auto conn = host_pair.second.front();
+ host_pair.second.pop();
+#ifdef _WIN32
+ closesocket(conn.socket);
+#else
+ close(conn.socket);
+#endif
+ }
+ }
+}
+
+socket_t ConnectionPool::acquire_connection(const std::string& host, int port, bool is_ssl) {
+ std::string key = make_key(host, port, is_ssl);
+
+ {
+ std::lock_guard lock(pool_mutex_);
+ auto it = pool_.find(key);
+ if (it != pool_.end() && !it->second.empty()) {
+ auto conn = it->second.front();
+ it->second.pop();
+
+ if (is_socket_alive(conn.socket)) {
+ kinc_log(KINC_LOG_LEVEL_INFO, "Reusing pooled connection for %s:%d", host.c_str(), port);
+ return conn.socket;
+ } else {
+#ifdef _WIN32
+ closesocket(conn.socket);
+#else
+ close(conn.socket);
+#endif
+ }
+ }
+ }
+
+ socket_t sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (sock == INVALID_SOCKET) {
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create socket for %s:%d", host.c_str(), port);
+ return INVALID_SOCKET;
+ }
+
+ SocketOptimization::optimizeHttp(static_cast(sock));
+
+ struct sockaddr_in server_addr;
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_port = htons(port);
+
+ struct hostent* host_entry = gethostbyname(host.c_str());
+ if (!host_entry) {
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to resolve hostname %s", host.c_str());
+#ifdef _WIN32
+ closesocket(sock);
+#else
+ close(sock);
+#endif
+ return INVALID_SOCKET;
+ }
+
+ memcpy(&server_addr.sin_addr, host_entry->h_addr, host_entry->h_length);
+
+ if (::connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to connect to %s:%d", host.c_str(), port);
+#ifdef _WIN32
+ closesocket(sock);
+#else
+ close(sock);
+#endif
+ return INVALID_SOCKET;
+ }
+
+ kinc_log(KINC_LOG_LEVEL_INFO, "Created new connection for %s:%d", host.c_str(), port);
+ return sock;
+}
+
+void ConnectionPool::release_connection(const std::string& host, int port, socket_t socket, bool is_ssl) {
+ if (socket == INVALID_SOCKET) return;
+
+ std::string key = make_key(host, port, is_ssl);
+
+ std::lock_guard lock(pool_mutex_);
+ auto& queue = pool_[key];
+
+ if (queue.size() >= MAX_CONNECTIONS_PER_HOST) {
+#ifdef _WIN32
+ closesocket(socket);
+#else
+ close(socket);
+#endif
+ return;
+ }
+
+ PooledConnection conn;
+ conn.socket = socket;
+ conn.last_used = std::chrono::steady_clock::now();
+ conn.is_ssl = is_ssl;
+
+ queue.push(conn);
+ kinc_log(KINC_LOG_LEVEL_INFO, "Pooled connection for %s:%d", host.c_str(), port);
+}
+
+void ConnectionPool::cleanup_expired() {
+ std::lock_guard lock(pool_mutex_);
+ auto now = std::chrono::steady_clock::now();
+
+ for (auto& host_pair : pool_) {
+ auto& queue = host_pair.second;
+ std::queue new_queue;
+
+ while (!queue.empty()) {
+ auto conn = queue.front();
+ queue.pop();
+
+ auto age = std::chrono::duration_cast(now - conn.last_used);
+ if (age.count() < CONNECTION_TIMEOUT_SECONDS && is_socket_alive(conn.socket)) {
+ new_queue.push(conn);
+ } else {
+#ifdef _WIN32
+ closesocket(conn.socket);
+#else
+ close(conn.socket);
+#endif
+ }
+ }
+
+ queue = std::move(new_queue);
+ }
+}
+
+size_t ConnectionPool::get_pool_size() const {
+ std::lock_guard lock(pool_mutex_);
+ size_t total = 0;
+ for (const auto& host_pair : pool_) {
+ total += host_pair.second.size();
+ }
+ return total;
+}
+
+std::string ConnectionPool::make_key(const std::string& host, int port, bool is_ssl) const {
+ return host + ":" + std::to_string(port) + (is_ssl ? ":ssl" : "");
+}
+
+bool ConnectionPool::is_socket_alive(socket_t socket) const {
+ char test_byte;
+ int result = ::recv(socket, &test_byte, 1, MSG_PEEK);
+
+#ifdef _WIN32
+ if (result == SOCKET_ERROR) {
+ int error = WSAGetLastError();
+ return (error == WSAEWOULDBLOCK);
+ }
+#else
+ if (result < 0) {
+ return (errno == EWOULDBLOCK || errno == EAGAIN);
+ }
+#endif
+
+ return true;
+}
diff --git a/Sources/connection_pool.h b/Sources/connection_pool.h
new file mode 100644
index 0000000..8054f6a
--- /dev/null
+++ b/Sources/connection_pool.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include
+typedef SOCKET socket_t;
+#else
+#include
+typedef int socket_t;
+#endif
+
+class ConnectionPool {
+private:
+ struct PooledConnection {
+ socket_t socket;
+ std::chrono::steady_clock::time_point last_used;
+ bool is_ssl;
+ };
+
+ std::unordered_map> pool_;
+ mutable std::mutex pool_mutex_;
+ static constexpr int MAX_CONNECTIONS_PER_HOST = 4;
+ static constexpr int CONNECTION_TIMEOUT_SECONDS = 30;
+
+public:
+ ConnectionPool() = default;
+ ~ConnectionPool();
+
+ socket_t acquire_connection(const std::string& host, int port, bool is_ssl = false);
+
+ // returns connection to pool for reuse
+ void release_connection(const std::string& host, int port, socket_t socket, bool is_ssl = false);
+
+ void cleanup_expired();
+
+ size_t get_pool_size() const;
+
+private:
+ std::string make_key(const std::string& host, int port, bool is_ssl) const;
+ bool is_socket_alive(socket_t socket) const;
+};
+
+extern ConnectionPool g_connection_pool;
diff --git a/Sources/global_thread_pool.cpp b/Sources/global_thread_pool.cpp
new file mode 100644
index 0000000..41ad9a0
--- /dev/null
+++ b/Sources/global_thread_pool.cpp
@@ -0,0 +1,4 @@
+#include "global_thread_pool.h"
+
+std::unique_ptr GlobalThreadPool::instance_;
+std::once_flag GlobalThreadPool::initialized_;
diff --git a/Sources/global_thread_pool.h b/Sources/global_thread_pool.h
new file mode 100644
index 0000000..99cdee3
--- /dev/null
+++ b/Sources/global_thread_pool.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "thread_pool.h"
+#include
+#include
+
+class GlobalThreadPool {
+private:
+ static std::unique_ptr instance_;
+ static std::once_flag initialized_;
+
+public:
+ static ThreadPool& getInstance() {
+ std::call_once(initialized_, []() {
+ unsigned int hw_threads = std::thread::hardware_concurrency();
+ size_t num_threads = hw_threads > 4 ? hw_threads / 2 : 2;
+ instance_ = std::make_unique(num_threads);
+ });
+ return *instance_;
+ }
+
+ static void shutdown() {
+ instance_.reset();
+ }
+};
diff --git a/Sources/httprequest.cpp b/Sources/httprequest.cpp
new file mode 100644
index 0000000..250b4bc
--- /dev/null
+++ b/Sources/httprequest.cpp
@@ -0,0 +1,2387 @@
+#ifdef WITH_NETWORKING
+
+#include "httprequest.h"
+#include "connection_pool.h"
+#include "global_thread_pool.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#define NOMINMAX
+#include
+#include
+#include
+#pragma comment(lib, "ws2_32.lib")
+#else
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#define SOCKET int
+#define INVALID_SOCKET -1
+#define SOCKET_ERROR -1
+#define closesocket close
+#endif
+
+#ifdef WITH_SSL
+#ifdef _WIN32
+#define SECURITY_WIN32
+#include
+#include
+#include
+#include
+#pragma comment(lib, "crypt32.lib")
+#pragma comment(lib, "secur32.lib")
+#else
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+#endif
+
+using namespace v8;
+
+namespace HttpRequestWrapper {
+ // timeout helper functions for SSL/socket operations
+ static int receiveWithTimeout(SOCKET socket, char* buffer, int buffer_size, int timeout_ms) {
+ // non-blocking
+#ifdef _WIN32
+ u_long mode = 1;
+ if (ioctlsocket(socket, FIONBIO, &mode) != 0) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to set socket to non-blocking mode: %d", WSAGetLastError());
+ #endif
+ return -1;
+ }
+#else
+ int flags = fcntl(socket, F_GETFL, 0);
+ if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) < 0) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to set socket to non-blocking mode: %s", strerror(errno));
+ #endif
+ return -1;
+ }
+#endif
+
+ fd_set read_fds;
+ FD_ZERO(&read_fds);
+ FD_SET(socket, &read_fds);
+
+ struct timeval timeout;
+ timeout.tv_sec = timeout_ms / 1000;
+ timeout.tv_usec = (timeout_ms % 1000) * 1000;
+
+#ifdef _WIN32
+ int select_result = select(0, &read_fds, NULL, NULL, &timeout);
+#else
+ int select_result = select(socket + 1, &read_fds, NULL, NULL, &timeout);
+#endif
+
+ if (select_result > 0 && FD_ISSET(socket, &read_fds)) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Socket has data available, attempting recv...");
+ #endif
+ int received = ::recv(socket, buffer, buffer_size, 0);
+
+ // restore blocking mode
+#ifdef _WIN32
+ mode = 0;
+ ioctlsocket(socket, FIONBIO, &mode);
+#else
+ // restore original flags
+ fcntl(socket, F_SETFL, flags);
+#endif
+
+ if (received > 0) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Received %d bytes with timeout handling", received);
+ #endif
+ return received;
+ } else if (received == 0) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Connection closed gracefully by server");
+ #endif
+ return 0;
+ } else {
+#ifdef _WIN32
+ int error = WSAGetLastError();
+ if (error == WSAEWOULDBLOCK) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "No data available (WSAEWOULDBLOCK)");
+ #endif
+ return 0;
+ } else {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Receive error: %d", error);
+ #endif
+ return -1;
+ }
+#else
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "No data available (EAGAIN/EWOULDBLOCK)");
+ #endif
+ return 0;
+ } else {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Receive error: %s", strerror(errno));
+ #endif
+ return -1;
+ }
+#endif
+ }
+ } else if (select_result == 0) {
+ // timeouts are expected
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Socket receive timeout after %d ms - no data available", timeout_ms);
+ #endif
+
+ // restore
+#ifdef _WIN32
+ mode = 0;
+ ioctlsocket(socket, FIONBIO, &mode);
+#else
+ fcntl(socket, F_SETFL, flags);
+#endif
+ return 0;
+ } else {
+#ifdef _WIN32
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Select error: %d", WSAGetLastError());
+ #endif
+#else
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Select error: %s", strerror(errno));
+ #endif
+#endif
+
+#ifdef _WIN32
+ mode = 0;
+ ioctlsocket(socket, FIONBIO, &mode);
+#else
+ fcntl(socket, F_SETFL, flags);
+#endif
+ return -1;
+ }
+ }
+
+ static std::unordered_map> active_requests;
+ static std::atomic next_request_id{1};
+ static std::atomic initialized{false};
+
+ void initialize() {
+ if (!initialized.exchange(true)) {
+#ifdef _WIN32
+ WSADATA wsaData;
+ int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
+ if (result != 0) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "WSAStartup failed: %d", result);
+ #endif
+ } else {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Initializing native HTTP request support");
+ #endif
+ }
+#else
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Initializing native HTTP request support");
+ #endif
+#endif
+ }
+ }
+
+ void cleanup() {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Starting HTTP request cleanup, waiting for active operations...");
+ #endif
+
+ bool has_active_operations = true;
+ int wait_count = 0;
+ const int max_wait_ms = 5000;
+
+ while (has_active_operations && wait_count < max_wait_ms) {
+ has_active_operations = false;
+
+ for (const auto& pair : active_requests) {
+ if (pair.second && pair.second->active_operation_.load()) {
+ has_active_operations = true;
+ break;
+ }
+ }
+
+ if (has_active_operations) {
+ #ifdef _WIN32
+ Sleep(10);
+ #else
+ usleep(10000);
+ #endif
+ wait_count += 10;
+ }
+ }
+
+ if (wait_count >= max_wait_ms) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_WARNING, "HTTP request cleanup timed out after %d ms, forcing cleanup", max_wait_ms);
+ #endif
+ } else {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "All active operations completed after %d ms", wait_count);
+ #endif
+ }
+
+ active_requests.clear();
+ if (initialized.exchange(false)) {
+#ifdef _WIN32
+ WSACleanup();
+#endif
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request cleanup complete");
+ #endif
+ }
+ }
+
+ void processEvents() {
+ // called from main thread
+ for (auto& pair : active_requests) {
+ if (pair.second) {
+ pair.second->processEvents();
+ }
+ }
+ }
+
+ int createHttpRequest(v8::Isolate* isolate) {
+ int id = next_request_id++;
+ auto client = std::make_unique(isolate, getGlobalContext());
+ active_requests[id] = std::move(client);
+ return id;
+ }
+
+ HttpRequestClient::HttpRequestClient(Isolate* isolate, Global* global_context)
+ : isolate_(isolate), global_context_(global_context), ready_state_(UNSENT),
+ response_type_(DEFAULT), async_(true), timeout_(0), with_credentials_(false),
+ done_event_fired_(false), active_operation_(false)
+ #ifdef WITH_SSL
+ #ifdef _WIN32
+ , ssl_cred_handle_(nullptr), ssl_context_handle_(nullptr), ssl_context_initialized_(false), ssl_socket_(-1)
+ #else
+ , ssl_ctx_(nullptr), ssl_(nullptr), ssl_initialized_(false)
+ #endif
+ #endif
+ {
+ #ifdef WITH_SSL
+ #ifdef _WIN32
+ // SSL handles to zero
+ memset(&ssl_cred_handle_, 0, sizeof(ssl_cred_handle_));
+ memset(&ssl_context_handle_, 0, sizeof(ssl_context_handle_));
+ #endif
+ #endif
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Creating HTTP request client");
+ #endif
+ }
+
+ HttpRequestClient::~HttpRequestClient() {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Destroying HTTP request client");
+ #endif
+ #ifdef WITH_SSL
+ cleanupSSL();
+ #endif
+ }
+
+ void HttpRequestClient::open(const std::string& method, const std::string& url, bool async) {
+ std::lock_guard lock(state_mutex_);
+
+ method_ = method;
+ url_ = url;
+ async_ = async;
+
+ response_ = HttpResponse();
+
+ setReadyState(OPENED);
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request opened: %s %s", method.c_str(), url.c_str());
+ #endif
+ }
+
+ void HttpRequestClient::setRequestHeader(const std::string& header, const std::string& value) {
+ std::lock_guard lock(state_mutex_);
+
+ if (ready_state_ != OPENED) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Cannot set request header: request not opened");
+ #endif
+ return;
+ }
+
+ request_headers_[header] = value;
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Set request header: %s = %s", header.c_str(), value.c_str());
+ #endif
+ }
+
+ void HttpRequestClient::send(const std::string& data) {
+ std::lock_guard lock(state_mutex_);
+
+ if (ready_state_ != OPENED) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Cannot send: request not opened");
+ #endif
+ return;
+ }
+
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Sending HTTP request with data length: %zu", data.length());
+ #endif
+
+ request_data_ = data;
+
+ if (async_) {
+ // use thread pool
+ GlobalThreadPool::getInstance().enqueue([this]() {
+ this->performRequest();
+ });
+ } else {
+ // synchronously
+ performRequest();
+ }
+ }
+
+ void HttpRequestClient::abort() {
+ std::lock_guard lock(state_mutex_);
+
+ if (ready_state_ == DONE) {
+ return;
+ }
+
+ setReadyState(DONE);
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request aborted");
+ #endif
+
+ if (!on_abort_.IsEmpty()) {
+ callCallback(on_abort_, 0, nullptr);
+ }
+ }
+
+ std::string HttpRequestClient::getAllResponseHeaders() {
+ std::lock_guard lock(state_mutex_);
+
+ std::ostringstream headers;
+ for (const auto& header : response_.headers) {
+ headers << header.first << ": " << header.second << "\r\n";
+ }
+ return headers.str();
+ }
+
+ std::string HttpRequestClient::getResponseHeader(const std::string& header) {
+ std::lock_guard lock(state_mutex_);
+
+ std::string lower_header = header;
+ std::transform(lower_header.begin(), lower_header.end(), lower_header.begin(), ::tolower);
+
+ for (const auto& h : response_.headers) {
+ std::string lower_h = h.first;
+ std::transform(lower_h.begin(), lower_h.end(), lower_h.begin(), ::tolower);
+ if (lower_h == lower_header) {
+ return h.second;
+ }
+ }
+ return "";
+ }
+
+ void HttpRequestClient::setOnReadyStateChange(Local callback) {
+ on_ready_state_change_.Reset(isolate_, callback);
+ }
+
+ void HttpRequestClient::setOnLoad(Local callback) {
+ on_load_.Reset(isolate_, callback);
+ }
+
+ void HttpRequestClient::setOnError(Local callback) {
+ on_error_.Reset(isolate_, callback);
+ }
+
+ void HttpRequestClient::setOnTimeout(Local callback) {
+ on_timeout_.Reset(isolate_, callback);
+ }
+
+ void HttpRequestClient::setOnAbort(Local callback) {
+ on_abort_.Reset(isolate_, callback);
+ }
+
+ void HttpRequestClient::setOnProgress(Local callback) {
+ on_progress_.Reset(isolate_, callback);
+ }
+
+ void HttpRequestClient::setOnLoadStart(Local callback) {
+ on_load_start_.Reset(isolate_, callback);
+ }
+
+ void HttpRequestClient::setOnLoadEnd(Local callback) {
+ on_load_end_.Reset(isolate_, callback);
+ }
+
+ void HttpRequestClient::performRequest() {
+ active_operation_ = true;
+
+ handleLoadStart();
+
+ try {
+ std::string host, path;
+ int port;
+ bool is_https;
+
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "HTTP DEBUG: Starting request to URL: %s", url_.c_str());
+ #endif
+
+ if (!parseUrl(url_, host, port, path, is_https)) {
+ response_.error = "Invalid URL";
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "HTTP DEBUG: Failed to parse URL: %s", url_.c_str());
+ #endif
+ setReadyState(DONE);
+ handleError(response_.error);
+ active_operation_ = false;
+ return;
+ }
+
+ // check if its a local file request
+ if (port == 0) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Loading local file: %s", path.c_str());
+ #endif
+ loadLocalFile(path);
+ active_operation_ = false;
+ return;
+ }
+
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Parsed URL - host: %s, port: %d, path: %s, https: %s",
+ host.c_str(), port, path.c_str(), is_https ? "true" : "false");
+ #endif
+
+ #ifdef WITH_SSL
+ if (is_https) {
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Using HTTPS/SSL for request");
+ #endif
+ }
+ #else
+ if (is_https) {
+ response_.error = "HTTPS not supported - build with WITH_SSL=1";
+ setReadyState(DONE);
+ handleError(response_.error);
+ active_operation_ = false;
+ return;
+ }
+ #endif
+
+ // use connection pool
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_INFO, "Acquiring connection for HTTP request");
+ #endif
+ SOCKET sock = g_connection_pool.acquire_connection(host, port, is_https);
+ if (sock == INVALID_SOCKET) {
+ response_.error = "Failed to acquire connection";
+ #ifdef DEBUG_NETWORK
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Connection acquisition failed");
+ #endif
+ setReadyState(DONE);
+ handleError(response_.error);
+ active_operation_ = false;
+ return;
+ }
+
+ // set timeout when specified
+ if (timeout_ > 0) {
+#ifdef _WIN32
+ DWORD timeout_ms = timeout_;
+ ::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout_ms, sizeof(timeout_ms));
+ ::setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout_ms, sizeof(timeout_ms));
+#else
+ struct timeval timeout;
+ timeout.tv_sec = timeout_ / 1000;
+ timeout.tv_usec = (timeout_ % 1000) * 1000;
+ ::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
+ ::setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
+#endif
+ }
+ kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", host.c_str(), port);
+
+ #ifdef WITH_SSL
+ if (is_https) {
+ kinc_log(KINC_LOG_LEVEL_INFO, "Performing SSL handshake");
+ ssl_socket_ = static_cast(sock);
+ if (!performSSLHandshake(static_cast(sock), host)) {
+ response_.error = "SSL handshake failed";
+ closesocket(sock);
+ setReadyState(DONE);
+ handleError(response_.error);
+ active_operation_ = false;
+ return;
+ }
+ kinc_log(KINC_LOG_LEVEL_INFO, "SSL handshake completed successfully");
+ }
+ #endif
+
+ std::string request_data = formatHttpRequest(method_, path, host, request_data_);
+ kinc_log(KINC_LOG_LEVEL_INFO, "Sending HTTP request (%zu bytes):", request_data.length());
+ kinc_log(KINC_LOG_LEVEL_INFO, "Request preview: %.200s", request_data.c_str());
+
+ int bytes_sent = 0;
+ #ifdef WITH_SSL
+ if (is_https) {
+ bytes_sent = sslWrite(request_data.c_str(), static_cast(request_data.length()));
+ } else {
+ bytes_sent = ::send(sock, request_data.c_str(), static_cast(request_data.length()), 0);
+ }
+ #else
+ bytes_sent = ::send(sock, request_data.c_str(), static_cast(request_data.length()), 0);
+ #endif
+
+ if (bytes_sent == SOCKET_ERROR || bytes_sent == 0) {
+ response_.error = "Failed to send request";
+ kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send HTTP request");
+ setReadyState(DONE);
+ handleError(response_.error);
+ active_operation_ = false;
+ return;
+ }
+
+ kinc_log(KINC_LOG_LEVEL_INFO, "HTTP request sent successfully");
+
+ char buffer[4096];
+ int bytes_received;
+ std::string response_data;
+ int total_bytes = 0;
+
+ kinc_log(KINC_LOG_LEVEL_INFO, "Starting to receive HTTP response");
+ while (true) {
+ #ifdef WITH_SSL
+ if (is_https) {
+ kinc_log(KINC_LOG_LEVEL_INFO, "Calling sslRead for %zu bytes", sizeof(buffer) - 1);
+ bytes_received = sslRead(buffer, sizeof(buffer) - 1);
+ kinc_log(KINC_LOG_LEVEL_INFO, "sslRead returned %d bytes", bytes_received);
+ } else {
+ bytes_received = ::recv(sock, buffer, sizeof(buffer) - 1, 0);
+ }
+ #else
+ bytes_received = ::recv(sock, buffer, sizeof(buffer) - 1, 0);
+ #endif
+
+ if (bytes_received <= 0) {
+ kinc_log(KINC_LOG_LEVEL_INFO, "Breaking from response reading loop, bytes_received: %d", bytes_received);
+ break;
+ }
+
+ buffer[bytes_received] = '\0';
+ response_data += buffer;
+ total_bytes += bytes_received;
+ kinc_log(KINC_LOG_LEVEL_INFO, "Received %d bytes (total: %d)", bytes_received, total_bytes);
+ }
+ kinc_log(KINC_LOG_LEVEL_INFO, "Finished receiving data, total bytes: %d", total_bytes);
+
+ #ifdef WITH_SSL
+ if (is_https) {
+ cleanupSSL();
+ }
+ #endif
+
+ // return connection to pool for reuse instead of closing
+ g_connection_pool.release_connection(host, port, sock, is_https);
+
+ if (bytes_received == SOCKET_ERROR) {
+ response_.error = "Failed to receive response";
+ setReadyState(DONE);
+ handleError(response_.error);
+ active_operation_ = false;
+ return;
+ }
+
+ kinc_log(KINC_LOG_LEVEL_INFO, "Raw HTTP response data (%zu bytes):", response_data.length());
+ kinc_log(KINC_LOG_LEVEL_INFO, "Response preview: %.200s", response_data.c_str());
+
+ parseHttpResponse(response_data);
+
+ kinc_log(KINC_LOG_LEVEL_INFO, "Parsed response - status: %d, body length: %zu", response_.status, response_.responseText.length());
+ kinc_log(KINC_LOG_LEVEL_INFO, "Response body preview: %.200s", response_.responseText.c_str());
+
+ setReadyState(HEADERS_RECEIVED);
+
+ setReadyState(DONE);
+
+ handleLoad();
+
+ handleLoadEnd();
+
+ active_operation_ = false;
+
+ } catch (const std::exception& e) {
+ response_.error = e.what();
+ setReadyState(DONE);
+ handleError(response_.error);
+ handleLoadEnd();
+ active_operation_ = false;
+ }
+ }
+
+ void HttpRequestClient::setReadyState(ReadyState state) {
+ if (ready_state_ != state) {
+ ready_state_ = state;
+ handleReadyStateChange();
+ }
+ }
+
+ // event handle is called from the background thread
+ void HttpRequestClient::handleReadyStateChange() {
+ std::lock_guard lock(event_queue_mutex_);
+ event_queue_.push(Event(HTTP_READY_STATE_CHANGE));
+ }
+
+ void HttpRequestClient::handleLoad() {
+ std::lock_guard lock(event_queue_mutex_);
+ event_queue_.push(Event(HTTP_LOAD));
+ }
+
+ void HttpRequestClient::handleError(const std::string& error) {
+ std::lock_guard lock(event_queue_mutex_);
+ event_queue_.push(Event(HTTP_ERROR, error));
+ }
+
+ void HttpRequestClient::handleTimeout() {
+ std::lock_guard lock(event_queue_mutex_);
+ event_queue_.push(Event(HTTP_TIMEOUT));
+ }
+
+ void HttpRequestClient::handleAbort() {
+ std::lock_guard lock(event_queue_mutex_);
+ event_queue_.push(Event(HTTP_ABORT));
+ }
+
+ void HttpRequestClient::handleProgress() {
+ std::lock_guard lock(event_queue_mutex_);
+ event_queue_.push(Event(HTTP_PROGRESS));
+ }
+
+ void HttpRequestClient::handleLoadStart() {
+ std::lock_guard lock(event_queue_mutex_);
+ event_queue_.push(Event(HTTP_LOAD_START));
+ }
+
+ void HttpRequestClient::handleLoadEnd() {
+ std::lock_guard lock(event_queue_mutex_);
+ event_queue_.push(Event(HTTP_LOAD_END));
+ }
+
+ // event called from main thread
+ void HttpRequestClient::processReadyStateChangeEvent() {
+ if (ready_state_ == DONE && done_event_fired_) {
+ kinc_log(KINC_LOG_LEVEL_INFO, "Skipping duplicate DONE ready state change event");
+ return;
+ }
+ if (ready_state_ == DONE) {
+ done_event_fired_ = true;
+ }
+
+ kinc_log(KINC_LOG_LEVEL_INFO, "Processing HTTP ready state change event, ready state: %d", ready_state_);
+ if (!on_ready_state_change_.IsEmpty()) {
+ callCallback(on_ready_state_change_, 0, nullptr);
+ } else {
+ kinc_log(KINC_LOG_LEVEL_INFO, "No ready state change callback set for HTTP request");
+ }
+ }
+
+ void HttpRequestClient::processLoadEvent() {
+ kinc_log(KINC_LOG_LEVEL_INFO, "Processing HTTP load event");
+ if (!on_load_.IsEmpty()) {
+ callCallback(on_load_, 0, nullptr);
+ } else {
+ kinc_log(KINC_LOG_LEVEL_INFO, "No load callback set for HTTP request");
+ }
+ }
+
+ void HttpRequestClient::processErrorEvent(const std::string& error) {
+ kinc_log(KINC_LOG_LEVEL_INFO, "Processing HTTP error event: %s", error.c_str());
+ if (!on_error_.IsEmpty()) {
+ Locker locker{isolate_};
+
+ Isolate::Scope isolate_scope(isolate_);
+ HandleScope handle_scope(isolate_);
+ Local context = global_context_->Get(isolate_);
+ Context::Scope context_scope(context);
+
+ Local