diff --git a/libs/request/request.c b/libs/request/request.c index b70645a..80e1205 100644 --- a/libs/request/request.c +++ b/libs/request/request.c @@ -1,35 +1,23 @@ #include -#include #include +#include #include -#include -#include +#include -bool curl_init = false; +CURL* curlHandle = NULL; + +GroundStruct template; + +const char* VALID_REQUEST_TYPES[] = { + "GET", + "POST" +}; typedef struct { char* data; size_t size; } ResponseBuffer; -typedef struct { - CURL* handle; - bool connected; - char* url; -} WebSocketConnection; - -// Global storage for WebSocket connections (simple implementation) -#define MAX_WS_CONNECTIONS 10 -WebSocketConnection ws_connections[MAX_WS_CONNECTIONS] = {0}; - -void curl_setup() { - if (!curl_init) { - curl_global_init(CURL_GLOBAL_ALL); - curl_init = true; - } -} - -// Proper write callback that accumulates data size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) { size_t total_size = size * nmemb; ResponseBuffer* buffer = (ResponseBuffer*)userdata; @@ -48,295 +36,117 @@ size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) { return total_size; } -// GET request -GroundValue get_request(GroundScope* scope, List args) { - curl_setup(); - CURL* handle = curl_easy_init(); - - if (!handle) { - ERROR("CURL failed to init", "GenericRequestError"); - } - - ResponseBuffer buffer = {0}; - buffer.data = malloc(1); - buffer.data[0] = '\0'; - buffer.size = 0; - - curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal); - curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(handle, CURLOPT_WRITEDATA, &buffer); - curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); - - CURLcode result = curl_easy_perform(handle); - - if (result != CURLE_OK) { - char buf[256]; - snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result)); - ERROR(buf, "ActionRequestError"); - } - - curl_easy_cleanup(handle); - - GroundValue ret = groundCreateValue(STRING, buffer.data); - return ret; -} +GroundValue ground_request(GroundScope* scope, List args) { -// POST request -GroundValue post_request(GroundScope* scope, List args) { - curl_setup(); - CURL* handle = curl_easy_init(); - - if (!handle) { - ERROR("CURL failed to init", "GenericRequestError"); - } - - ResponseBuffer buffer = {0}; - buffer.data = malloc(1); - buffer.data[0] = '\0'; - buffer.size = 0; - - curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal); - curl_easy_setopt(handle, CURLOPT_POST, 1L); - curl_easy_setopt(handle, CURLOPT_POSTFIELDS, args.values[1].data.stringVal); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(handle, CURLOPT_WRITEDATA, &buffer); - curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); - - CURLcode result = curl_easy_perform(handle); - - if (result != CURLE_OK) { - free(buffer.data); - char buf[256]; - snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result)); - curl_easy_cleanup(handle); - ERROR(buf, "ActionRequestError"); - } - - curl_easy_cleanup(handle); - - GroundValue ret = groundCreateValue(STRING, buffer.data); - return ret; -} + GroundObject obj = *args.values[0].data.customVal; -// ============== WebSocket Functions ============== + // check arg types + GroundObjectField* address = groundFindField(obj, "address"); + if (address == NULL || address->value.type != STRING) { + ERROR("Object does not have address field as string", "ValueError"); + } + GroundObjectField* type = groundFindField(obj, "type"); + if (type == NULL || type->value.type != STRING) { + ERROR("Object does not have type field as string", "ValueError"); + } + GroundObjectField* userAgent = groundFindField(obj, "userAgent"); + if (userAgent == NULL || userAgent->value.type != STRING) { + ERROR("Object does not have userAgent field as string", "ValueError"); + } + GroundObjectField* httpHeaders = groundFindField(obj, "httpHeaders"); + if (httpHeaders == NULL || httpHeaders->value.type != LIST) { + ERROR("Object does not have httpHeaders field as list", "ValueError"); + } + GroundObjectField* verbose = groundFindField(obj, "verbose"); + if (verbose == NULL || verbose->value.type != BOOL) { + ERROR("Object does not have verbose field as bool", "ValueError"); + } -// Find an empty slot for a new WebSocket connection -int find_ws_slot() { - for (int i = 0; i < MAX_WS_CONNECTIONS; i++) { - if (!ws_connections[i].connected) { - return i; + // check request type string + bool requestTypeOk = false; + for (int i = 0; i < sizeof(VALID_REQUEST_TYPES)/sizeof(VALID_REQUEST_TYPES[0]); i++) { + if (strcmp(type->value.data.stringVal, VALID_REQUEST_TYPES[i]) == 0) { + requestTypeOk = true; + break; } } - return -1; -} + if (!requestTypeOk) + ERROR("Invalid request type! Choices: GET, POST", "ValueError"); + -// WebSocket Connect - returns connection ID as INT -GroundValue ws_connect(GroundScope* scope, List args) { - curl_setup(); - - int slot = find_ws_slot(); - if (slot == -1) { - ERROR("Maximum WebSocket connections reached", "OverflowError"); - } - - CURL* handle = curl_easy_init(); - if (!handle) { - ERROR("CURL failed to init", "GenericWSError"); - } - - curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal); - curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 2L); // WebSocket mode - curl_easy_setopt(handle, CURLOPT_TIMEOUT, 10L); - - CURLcode result = curl_easy_perform(handle); - - if (result != CURLE_OK) { - char buf[256]; - snprintf(buf, sizeof(buf) - 1, "Curl WebSocket failed: %s\n", curl_easy_strerror(result)); - curl_easy_cleanup(handle); - ERROR(buf, "ActionWSError"); - } - - ws_connections[slot].handle = handle; - ws_connections[slot].connected = true; - ws_connections[slot].url = strdup(args.values[0].data.stringVal); - - return groundCreateValue(INT, (int64_t)slot); -} + ResponseBuffer buffer = {0}; -// WebSocket Send - sends a text message, returns bytes sent as INT -GroundValue ws_send(GroundScope* scope, List args) { - int conn_id = (int)args.values[0].data.intVal; - if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - ERROR("Invalid WebSocket connection ID", "GenericWSError"); - } - - CURL* handle = ws_connections[conn_id].handle; - const char* message = args.values[1].data.stringVal; - size_t sent; - - CURLcode result = curl_ws_send(handle, message, strlen(message), &sent, 0, CURLWS_TEXT); - - if (result != CURLE_OK) { - char buf[256]; - snprintf(buf, sizeof(buf) - 1, "WebSocket send failed: %s\n", curl_easy_strerror(result)); - ERROR(buf, "ActionWSError"); - } - - return groundCreateValue(INT, (int64_t)sent); -} + // set curl params + curl_easy_setopt(curlHandle, CURLOPT_URL, address->value.data.stringVal); + curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, (void*)&buffer); + curl_easy_setopt(curlHandle, CURLOPT_USERAGENT, userAgent->value.data.stringVal); + curl_easy_setopt(curlHandle, CURLOPT_VERBOSE, verbose->value.data.boolVal); -// WebSocket Receive - receives a message (blocking) -GroundValue ws_receive(GroundScope* scope, List args) { - int conn_id = (int)args.values[0].data.intVal; - if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - ERROR("Invalid WebSocket connection ID", "GenericWSError"); + // append headers to http request + struct curl_slist* httpHeaderList = NULL; + GroundValue* httpHeaderStrings = httpHeaders->value.data.listVal.values; + for (int i = 0; i < httpHeaders->value.data.listVal.size; i++) { + httpHeaderList = curl_slist_append(httpHeaderList, httpHeaderStrings[i].data.stringVal); } - - CURL* handle = ws_connections[conn_id].handle; - char buffer[4096]; - size_t received; - const struct curl_ws_frame* meta; - - CURLcode result = curl_ws_recv(handle, buffer, sizeof(buffer) - 1, &received, &meta); - - if (result != CURLE_OK) { - if (result == CURLE_AGAIN) { - // No data available right now - return groundCreateValue(STRING, ""); - } - char buf[256]; - snprintf(buf, sizeof(buf) - 1, "WebSocket receive failed: %s\n", curl_easy_strerror(result)); - ERROR(buf, "ActionWSError"); - } - - buffer[received] = '\0'; - - // Handle different frame types - if (meta->flags & CURLWS_CLOSE) { - printf("WebSocket close frame received\n"); - ws_connections[conn_id].connected = false; - return groundCreateValue(STRING, "[CLOSED]"); - } - - if (meta->flags & CURLWS_PING) { - // Automatically respond to ping with pong - curl_ws_send(handle, "", 0, &received, 0, CURLWS_PONG); - return groundCreateValue(STRING, "[PING]"); - } - - char* data = malloc(received + 1); - memcpy(data, buffer, received); - data[received] = '\0'; - - return groundCreateValue(STRING, data); -} + curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, httpHeaderList); -// WebSocket Receive with timeout (non-blocking version) -GroundValue ws_receive_timeout(GroundScope* scope, List args) { - int conn_id = (int)args.values[0].data.intVal; - long timeout_ms = (long)args.values[1].data.intVal; - - if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - ERROR("Invalid WebSocket connection ID", "GenericWSError"); - } - - CURL* handle = ws_connections[conn_id].handle; - - // Set socket to non-blocking mode - curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, timeout_ms); - - char buffer[4096]; - size_t received; - const struct curl_ws_frame* meta; - - CURLcode result = curl_ws_recv(handle, buffer, sizeof(buffer) - 1, &received, &meta); - - if (result == CURLE_AGAIN || result == CURLE_OPERATION_TIMEDOUT) { - return groundCreateValue(STRING, ""); - } - - if (result != CURLE_OK) { - char buf[256]; - snprintf(buf, sizeof(buf) - 1, "WebSocket receive failed: %s\n", curl_easy_strerror(result)); - ERROR(buf, "ActionWSError"); - } - - buffer[received] = '\0'; - - if (meta->flags & CURLWS_CLOSE) { - ws_connections[conn_id].connected = false; - return groundCreateValue(STRING, "[CLOSED]"); - } - - if (meta->flags & CURLWS_PING) { - curl_ws_send(handle, "", 0, &received, 0, CURLWS_PONG); - return groundCreateValue(STRING, "[PING]"); - } - - char* data = malloc(received + 1); - memcpy(data, buffer, received); - data[received] = '\0'; - - return groundCreateValue(STRING, data); -} -// WebSocket Close - properly close a connection, returns BOOL success -GroundValue ws_close(GroundScope* scope, List args) { - int conn_id = (int)args.values[0].data.intVal; - if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - ERROR("Invalid or already connected websocket", "GenericWSError"); - } - - CURL* handle = ws_connections[conn_id].handle; - size_t sent; - - // Send close frame - curl_ws_send(handle, "", 0, &sent, 0, CURLWS_CLOSE); - - // Cleanup - curl_easy_cleanup(handle); - free(ws_connections[conn_id].url); - ws_connections[conn_id].handle = NULL; - ws_connections[conn_id].connected = false; - ws_connections[conn_id].url = NULL; - - return groundCreateValue(BOOL, true); -} + CURLcode curlStatus = curl_easy_perform(curlHandle); + curl_slist_free_all(httpHeaderList); // dont want em' memory leaks :P - spooopy -// WebSocket Send Binary data, returns bytes sent as INT -GroundValue ws_send_binary(GroundScope* scope, List args) { - int conn_id = (int)args.values[0].data.intVal; - if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - ERROR("Invalid WebSocket connection ID", "GenericWSError"); + // check for any curl errors + if (curlStatus != CURLE_OK) { + char curlErrorMsg[255] = {0}; + + snprintf(curlErrorMsg, 255, "%s\n", curl_easy_strerror(curlStatus)); + ERROR(curlErrorMsg, "CurlError"); } - - CURL* handle = ws_connections[conn_id].handle; - const char* data = args.values[1].data.stringVal; - size_t sent; - - CURLcode result = curl_ws_send(handle, data, strlen(data), &sent, 0, CURLWS_BINARY); - - if (result != CURLE_OK) { - char buf[256]; - snprintf(buf, sizeof(buf) - 1, "WebSocket binary send failed: %s\n", curl_easy_strerror(result)); - ERROR(buf, "ActionWSError"); - } - - return groundCreateValue(INT, (int64_t)sent); + + // get the http response code + long httpCode = 0; + curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpCode); + + // create return value and return it + GroundValue value = groundCreateValue(CUSTOM, &template); + + GroundObjectField* status = groundFindField(*value.data.customVal, "statusCode"); + status->value.data.intVal = httpCode; + + GroundObjectField* data = groundFindField(*value.data.customVal, "data"); + data->value.data.stringVal = buffer.data; + + value.type = CUSTOM; + + return value; } void ground_init(GroundScope* scope) { - // HTTP Functions - groundAddNativeFunction(scope, "request_Get", get_request, STRING, 1, STRING, "url"); - groundAddNativeFunction(scope, "request_Post", post_request, STRING, 2, STRING, "url", STRING, "data"); - - // WebSocket Functions - groundAddNativeFunction(scope, "ws_Connect", ws_connect, INT, 1, STRING, "url"); - groundAddNativeFunction(scope, "ws_Send", ws_send, INT, 2, INT, "conn_id", STRING, "message"); - groundAddNativeFunction(scope, "ws_Receive", ws_receive, STRING, 1, INT, "conn_id"); - groundAddNativeFunction(scope, "ws_ReceiveTimeout", ws_receive_timeout, STRING, 2, INT, "conn_id", INT, "timeout_ms"); - groundAddNativeFunction(scope, "ws_Close", ws_close, BOOL, 1, INT, "conn_id"); - groundAddNativeFunction(scope, "ws_SendBinary", ws_send_binary, INT, 2, INT, "conn_id", STRING, "data"); -} + // init libcurl + curl_global_init(CURL_GLOBAL_ALL); + curlHandle = curl_easy_init(); + + groundAddValueToScope(scope, "requestInitSuccess", groundCreateValue(BOOL, curlHandle != NULL)); + if (curlHandle == NULL) { + return; + } + + // create struct + template = groundCreateStruct(); + groundAddFieldToStruct(&template, "statusCode", groundCreateValue(INT, 200)); + groundAddFieldToStruct(&template, "data", groundCreateValue(STRING, "")); + groundAddFieldToStruct(&template, "ok", groundCreateValue(BOOL, 1)); + + // create struct that users can pass to this library + GroundStruct requestStruct = groundCreateStruct(); + groundAddFieldToStruct(&requestStruct, "address", groundCreateValue(STRING, "")); + groundAddFieldToStruct(&requestStruct, "type", groundCreateValue(STRING, "GET")); + groundAddFieldToStruct(&requestStruct, "userAgent", groundCreateValue(STRING, "Ground/1.0")); + groundAddFieldToStruct(&requestStruct, "httpHeaders", groundCreateValue(LIST, 0)); + groundAddFieldToStruct(&requestStruct, "verbose", groundCreateValue(BOOL, 0)); + + groundAddValueToScope(scope, "request", groundCreateValue(STRUCTVAL, requestStruct)); + + // create functions + groundAddNativeFunction(scope, "request_Request", ground_request, CUSTOM, 1, CUSTOM, "requestInfo"); +} \ No newline at end of file