|
#include "wren.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <curl/curl.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
typedef HANDLE thread_t;
|
|
typedef CRITICAL_SECTION mutex_t;
|
|
typedef CONDITION_VARIABLE cond_t;
|
|
#else
|
|
#include <pthread.h>
|
|
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;
|
|
}
|