#include "wren.h" #include #include #include #include #ifdef _WIN32 #include typedef HANDLE thread_t; typedef CRITICAL_SECTION mutex_t; typedef CONDITION_VARIABLE cond_t; #else #include typedef pthread_t thread_t; typedef pthread_mutex_t mutex_t; typedef pthread_cond_t cond_t; #endif // --- Data Structures --- typedef struct { int isError; long statusCode; char* body; size_t body_len; } ResponseData; typedef struct { char* memory; size_t size; } MemoryStruct; typedef struct HttpContext { WrenVM* vm; WrenHandle* callback; char* url; char* method; char* body; struct curl_slist* headers; bool success; char* response_body; size_t response_body_len; long status_code; char* error_message; struct HttpContext* next; } HttpContext; // --- Thread-Safe Queue --- typedef struct { HttpContext *head, *tail; mutex_t mutex; cond_t cond; } ThreadSafeQueue; void http_queue_init(ThreadSafeQueue* q) { q->head = q->tail = NULL; #ifdef _WIN32 InitializeCriticalSection(&q->mutex); InitializeConditionVariable(&q->cond); #else pthread_mutex_init(&q->mutex, NULL); pthread_cond_init(&q->cond, NULL); #endif } void http_queue_destroy(ThreadSafeQueue* q) { #ifdef _WIN32 DeleteCriticalSection(&q->mutex); #else pthread_mutex_destroy(&q->mutex); pthread_cond_destroy(&q->cond); #endif } void http_queue_push(ThreadSafeQueue* q, HttpContext* context) { #ifdef _WIN32 EnterCriticalSection(&q->mutex); #else pthread_mutex_lock(&q->mutex); #endif if(context) context->next = NULL; if (q->tail) q->tail->next = context; else q->head = context; q->tail = context; #ifdef _WIN32 WakeConditionVariable(&q->cond); LeaveCriticalSection(&q->mutex); #else pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); #endif } HttpContext* http_queue_pop(ThreadSafeQueue* q) { #ifdef _WIN32 EnterCriticalSection(&q->mutex); while (q->head == NULL) { SleepConditionVariableCS(&q->cond, &q->mutex, INFINITE); } #else pthread_mutex_lock(&q->mutex); while (q->head == NULL) { pthread_cond_wait(&q->cond, &q->mutex); } #endif HttpContext* context = q->head; q->head = q->head->next; if (q->head == NULL) q->tail = NULL; #ifdef _WIN32 LeaveCriticalSection(&q->mutex); #else pthread_mutex_unlock(&q->mutex); #endif return context; } bool http_queue_empty(ThreadSafeQueue* q) { #ifdef _WIN32 EnterCriticalSection(&q->mutex); bool empty = (q->head == NULL); LeaveCriticalSection(&q->mutex); #else pthread_mutex_lock(&q->mutex); bool empty = (q->head == NULL); pthread_mutex_unlock(&q->mutex); #endif return empty; } // --- libcurl Helpers --- static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; MemoryStruct *mem = (MemoryStruct *)userp; char *ptr = (char*)realloc(mem->memory, mem->size + realsize + 1); if (ptr == NULL) return 0; mem->memory = ptr; memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; return realsize; } // --- Async HTTP Manager --- typedef struct { WrenVM* vm; volatile bool running; thread_t threads[4]; ThreadSafeQueue requestQueue; ThreadSafeQueue completionQueue; } AsyncHttpManager; static AsyncHttpManager* httpManager = NULL; void free_http_context(HttpContext* context) { if (!context) return; free(context->url); free(context->method); free(context->body); curl_slist_free_all(context->headers); free(context->response_body); free(context->error_message); free(context); } #ifdef _WIN32 DWORD WINAPI httpWorkerThread(LPVOID arg) { #else void* httpWorkerThread(void* arg) { #endif AsyncHttpManager* manager = (AsyncHttpManager*)arg; while (manager->running) { HttpContext* context = http_queue_pop(&manager->requestQueue); if (!context || !manager->running) { if (context) free_http_context(context); break; } CURL *curl = curl_easy_init(); if (curl) { MemoryStruct chunk; chunk.memory = (char*)malloc(1); chunk.size = 0; curl_easy_setopt(curl, CURLOPT_URL, context->url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_memory_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); curl_easy_setopt(curl, CURLOPT_USERAGENT, "wren-curl-agent/1.0"); if (strcmp(context->method, "POST") == 0) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, context->body); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, context->headers); } CURLcode res = curl_easy_perform(curl); if (res == CURLE_OK) { context->success = true; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &context->status_code); context->response_body = chunk.memory; context->response_body_len = chunk.size; } else { context->success = false; context->status_code = -1; context->error_message = strdup(curl_easy_strerror(res)); free(chunk.memory); } curl_easy_cleanup(curl); } else { context->success = false; context->error_message = strdup("Failed to initialize cURL handle."); } http_queue_push(&manager->completionQueue, context); } return 0; } void httpManager_create(WrenVM* vm) { httpManager = (AsyncHttpManager*)malloc(sizeof(AsyncHttpManager)); httpManager->vm = vm; httpManager->running = true; http_queue_init(&httpManager->requestQueue); http_queue_init(&httpManager->completionQueue); for (int i = 0; i < 4; ++i) { #ifdef _WIN32 httpManager->threads[i] = CreateThread(NULL, 0, httpWorkerThread, httpManager, 0, NULL); #else pthread_create(&httpManager->threads[i], NULL, httpWorkerThread, httpManager); #endif } } void httpManager_destroy() { httpManager->running = false; for (int i = 0; i < 4; ++i) { http_queue_push(&httpManager->requestQueue, NULL); } for (int i = 0; i < 4; ++i) { #ifdef _WIN32 WaitForSingleObject(httpManager->threads[i], INFINITE); CloseHandle(httpManager->threads[i]); #else pthread_join(httpManager->threads[i], NULL); #endif } http_queue_destroy(&httpManager->requestQueue); http_queue_destroy(&httpManager->completionQueue); free(httpManager); } void httpManager_processCompletions() { while (!http_queue_empty(&httpManager->completionQueue)) { HttpContext* context = http_queue_pop(&httpManager->completionQueue); WrenHandle* callHandle = wrenMakeCallHandle(httpManager->vm, "call(_,_)"); wrenEnsureSlots(httpManager->vm, 3); wrenSetSlotHandle(httpManager->vm, 0, context->callback); if (context->success) { wrenSetSlotNull(httpManager->vm, 1); wrenGetVariable(httpManager->vm, "requests", "Response", 2); void* foreign = wrenSetSlotNewForeign(httpManager->vm, 2, 2, sizeof(ResponseData)); ResponseData* data = (ResponseData*)foreign; data->isError = false; data->statusCode = context->status_code; data->body = context->response_body; data->body_len = context->response_body_len; context->response_body = NULL; } else { wrenSetSlotString(httpManager->vm, 1, context->error_message); wrenSetSlotNull(httpManager->vm, 2); } wrenCall(httpManager->vm, callHandle); wrenReleaseHandle(httpManager->vm, context->callback); wrenReleaseHandle(httpManager->vm, callHandle); free_http_context(context); } } void httpManager_submit(HttpContext* context) { http_queue_push(&httpManager->requestQueue, context); } // --- Wren Foreign Methods --- void responseFinalize(void* data) { ResponseData* response = (ResponseData*)data; free(response->body); } void responseAllocate(WrenVM* vm) { ResponseData* data = (ResponseData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(ResponseData)); data->isError = 0; data->statusCode = 0; data->body = NULL; data->body_len = 0; } void responseIsError(WrenVM* vm) { ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0); wrenSetSlotBool(vm, 0, data->isError ? true : false); } void responseStatusCode(WrenVM* vm) { ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0); wrenSetSlotDouble(vm, 0, (double)data->statusCode); } void responseBody(WrenVM* vm) { ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0); wrenSetSlotBytes(vm, 0, data->body ? data->body : "", data->body_len); } void responseJson(WrenVM* vm) { // CORRECTED: Replaced incorrect call with the actual logic. ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0); wrenSetSlotBytes(vm, 0, data->body ? data->body : "", data->body_len); } void requestsGet(WrenVM* vm) { HttpContext* context = (HttpContext*)calloc(1, sizeof(HttpContext)); context->vm = vm; context->method = strdup("GET"); context->url = strdup(wrenGetSlotString(vm, 1)); context->callback = wrenGetSlotHandle(vm, 3); httpManager_submit(context); } void requestsPost(WrenVM* vm) { HttpContext* context = (HttpContext*)calloc(1, sizeof(HttpContext)); context->vm = vm; context->method = strdup("POST"); context->url = strdup(wrenGetSlotString(vm, 1)); context->body = strdup(wrenGetSlotString(vm, 2)); const char* contentType = wrenGetSlotString(vm, 3); char contentTypeHeader[256]; snprintf(contentTypeHeader, sizeof(contentTypeHeader), "Content-Type: %s", contentType); context->headers = curl_slist_append(NULL, contentTypeHeader); context->callback = wrenGetSlotHandle(vm, 5); httpManager_submit(context); } // --- FFI Binding Functions --- WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) { if (strcmp(module, "requests") != 0) return NULL; if (strcmp(className, "Requests") == 0 && isStatic) { if (strcmp(signature, "get_(_,_,_)") == 0) return requestsGet; if (strcmp(signature, "post_(_,_,_,_,_)") == 0) return requestsPost; } if (strcmp(className, "Response") == 0 && !isStatic) { if (strcmp(signature, "isError") == 0) return responseIsError; if (strcmp(signature, "statusCode") == 0) return responseStatusCode; if (strcmp(signature, "body") == 0) return responseBody; if (strcmp(signature, "json()") == 0) return responseJson; } return NULL; } WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) { WrenForeignClassMethods methods = {0, 0}; if (strcmp(module, "requests") == 0) { if (strcmp(className, "Response") == 0) { methods.allocate = responseAllocate; methods.finalize = responseFinalize; } } return methods; }