Linux HTTP implementation
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# Find link platform-specific libraries
|
# Find link platform-specific libraries
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
|
find_package(CURL REQUIRED)
|
||||||
|
|
||||||
# Setup endianess at compile time to optimize.
|
# Setup endianess at compile time to optimize.
|
||||||
include(TestBigEndian)
|
include(TestBigEndian)
|
||||||
@@ -22,6 +23,7 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
OpenGL::GL
|
OpenGL::GL
|
||||||
GL
|
GL
|
||||||
m
|
m
|
||||||
|
CURL::libcurl
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define platform-specific macros.
|
# Define platform-specific macros.
|
||||||
@@ -38,4 +40,5 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
DUSK_INPUT_POINTER
|
DUSK_INPUT_POINTER
|
||||||
DUSK_INPUT_GAMEPAD
|
DUSK_INPUT_GAMEPAD
|
||||||
DUSK_TIME_DYNAMIC
|
DUSK_TIME_DYNAMIC
|
||||||
|
THREAD_PTHREAD=1
|
||||||
)
|
)
|
||||||
@@ -74,8 +74,6 @@ add_subdirectory(scene)
|
|||||||
add_subdirectory(script)
|
add_subdirectory(script)
|
||||||
add_subdirectory(time)
|
add_subdirectory(time)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
|
add_subdirectory(network)
|
||||||
add_subdirectory(util)
|
add_subdirectory(util)
|
||||||
|
add_subdirectory(thread)
|
||||||
# if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "psp")
|
|
||||||
# add_subdirectory(thread)
|
|
||||||
# endif()
|
|
||||||
@@ -20,6 +20,9 @@
|
|||||||
#include "entity/component/physics/entityphysics.h"
|
#include "entity/component/physics/entityphysics.h"
|
||||||
#include "game/game.h"
|
#include "game/game.h"
|
||||||
#include "physics/physicsmanager.h"
|
#include "physics/physicsmanager.h"
|
||||||
|
#include "network/networkmanager.h"
|
||||||
|
#include "network/networkhttprequest.h"
|
||||||
|
|
||||||
#include "display/mesh/cube.h"
|
#include "display/mesh/cube.h"
|
||||||
#include "display/mesh/plane.h"
|
#include "display/mesh/plane.h"
|
||||||
|
|
||||||
@@ -29,6 +32,22 @@ engine_t ENGINE;
|
|||||||
static entityid_t phBoxEnt;
|
static entityid_t phBoxEnt;
|
||||||
static componentid_t phBoxPhys;
|
static componentid_t phBoxPhys;
|
||||||
|
|
||||||
|
/* ---- test network callbacks ---- */
|
||||||
|
|
||||||
|
static void onTestResponse(
|
||||||
|
const uint16_t status,
|
||||||
|
const char_t *body,
|
||||||
|
const networkhttpheader_t *responseHeaders,
|
||||||
|
const uint32_t responseHeaderCount,
|
||||||
|
void *user
|
||||||
|
) {
|
||||||
|
printf("HTTP %d: %s\n", status, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onTestError(errorret_t error, void *user) {
|
||||||
|
printf("HTTP error: %s\n", error.state->message);
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||||
memoryZero(&ENGINE, sizeof(engine_t));
|
memoryZero(&ENGINE, sizeof(engine_t));
|
||||||
ENGINE.running = true;
|
ENGINE.running = true;
|
||||||
@@ -46,8 +65,17 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
errorChain(sceneInit());
|
errorChain(sceneInit());
|
||||||
entityManagerInit();
|
entityManagerInit();
|
||||||
physicsManagerInit();
|
physicsManagerInit();
|
||||||
|
errorChain(networkManagerInit());
|
||||||
errorChain(gameInit());
|
errorChain(gameInit());
|
||||||
|
|
||||||
|
networkHTTPRequest(
|
||||||
|
"http://localhost:3000/test",
|
||||||
|
NETWORK_HTTP_REQUEST_METHOD_GET,
|
||||||
|
NULL, NULL, 0, NULL,
|
||||||
|
onTestResponse,
|
||||||
|
onTestError
|
||||||
|
);
|
||||||
|
|
||||||
/* ---- Camera ---- */
|
/* ---- Camera ---- */
|
||||||
entityid_t cam = entityManagerAdd();
|
entityid_t cam = entityManagerAdd();
|
||||||
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
|
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
|
||||||
@@ -107,6 +135,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
errorret_t engineUpdate(void) {
|
errorret_t engineUpdate(void) {
|
||||||
timeUpdate();
|
timeUpdate();
|
||||||
inputUpdate();
|
inputUpdate();
|
||||||
|
errorChain(networkManagerUpdate());
|
||||||
|
|
||||||
uiUpdate();
|
uiUpdate();
|
||||||
errorChain(sceneUpdate());
|
errorChain(sceneUpdate());
|
||||||
@@ -134,6 +163,7 @@ void engineExit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t engineDispose(void) {
|
errorret_t engineDispose(void) {
|
||||||
|
errorChain(networkManagerDispose());
|
||||||
sceneDispose();
|
sceneDispose();
|
||||||
errorChain(gameDispose());
|
errorChain(gameDispose());
|
||||||
entityManagerDispose();
|
entityManagerDispose();
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
networkmanager.c
|
||||||
|
networkhttprequest.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "networkhttprequest.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "network/networkplatform.h"
|
||||||
|
|
||||||
|
void networkHTTPRequest(
|
||||||
|
const char_t *url,
|
||||||
|
const networkhttprequestmethod_t method,
|
||||||
|
const char_t *bodyOrNull,
|
||||||
|
const networkhttpheader_t *headers,
|
||||||
|
const uint32_t headerCount,
|
||||||
|
void *user,
|
||||||
|
networkhttpcallback_t callback,
|
||||||
|
networkhttperrorcallback_t errorCallback
|
||||||
|
) {
|
||||||
|
assertStrLenMin(url, 1, "URL must be non-empty");
|
||||||
|
assertNotNull(callback, "Callback must be non-NULL");
|
||||||
|
assertNotNull(errorCallback, "Error callback must be non-NULL");
|
||||||
|
assertTrue(headerCount == 0 || headers != NULL, "Headers must not be NULL when headerCount > 0");
|
||||||
|
|
||||||
|
uint32_t count = (headers == NULL) ? 0 : headerCount;
|
||||||
|
|
||||||
|
networkPlatformHTTPRequest(
|
||||||
|
url, method, bodyOrNull, headers, count, user, callback, errorCallback
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NETWORK_HTTP_REQUEST_METHOD_GET,
|
||||||
|
NETWORK_HTTP_REQUEST_METHOD_POST,
|
||||||
|
NETWORK_HTTP_REQUEST_METHOD_PUT,
|
||||||
|
NETWORK_HTTP_REQUEST_METHOD_DELETE,
|
||||||
|
NETWORK_HTTP_REQUEST_METHOD_HEAD,
|
||||||
|
NETWORK_HTTP_REQUEST_METHOD_OPTIONS,
|
||||||
|
} networkhttprequestmethod_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char_t *key;
|
||||||
|
const char_t *value;
|
||||||
|
} networkhttpheader_t;
|
||||||
|
|
||||||
|
typedef void (*networkhttpcallback_t)(
|
||||||
|
const uint16_t status,
|
||||||
|
const char_t *body,
|
||||||
|
const networkhttpheader_t *responseHeaders,
|
||||||
|
const uint32_t responseHeaderCount,
|
||||||
|
void *user
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef void (*networkhttperrorcallback_t)(
|
||||||
|
errorret_t error,
|
||||||
|
void *user
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a HTTP Request. The request itself is asynchronous but the callback
|
||||||
|
* will be invoked on the main thread when next available.
|
||||||
|
*
|
||||||
|
* @param url URL to request.
|
||||||
|
* @param method Method to use for the request.
|
||||||
|
* @param bodyOrNull If POST or PUT, custom body string, can be NULL.
|
||||||
|
* @param headers Array of key-value headers.
|
||||||
|
* @param headerCount Count of headers, can be anything if headers is NULL.
|
||||||
|
* @param user Callback pointer received to the callback.
|
||||||
|
* @param callback The callback to invoke when the request completes.
|
||||||
|
* @param errorCallback The callback to invoke if the request fails.
|
||||||
|
* Note, this doesn't count Non-200 status codes, just
|
||||||
|
* network errors.
|
||||||
|
*/
|
||||||
|
void networkHTTPRequest(
|
||||||
|
const char_t *url,
|
||||||
|
const networkhttprequestmethod_t method,
|
||||||
|
const char_t *bodyOrNull,
|
||||||
|
const networkhttpheader_t *headers,
|
||||||
|
const uint32_t headerCount,
|
||||||
|
void *user,
|
||||||
|
networkhttpcallback_t callback,
|
||||||
|
networkhttperrorcallback_t errorCallback
|
||||||
|
);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "networkmanager.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "network/networkplatform.h"
|
||||||
|
|
||||||
|
networkmanager_t NETWORK_MANAGER;
|
||||||
|
|
||||||
|
errorret_t networkManagerInit() {
|
||||||
|
memoryZero(&NETWORK_MANAGER, sizeof(networkmanager_t));
|
||||||
|
errorChain(networkPlatformInit());
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t networkManagerUpdate() {
|
||||||
|
errorChain(networkPlatformUpdate());
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t networkManagerDispose() {
|
||||||
|
errorChain(networkPlatformDispose());
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *nothing;
|
||||||
|
} networkmanager_t;
|
||||||
|
|
||||||
|
extern networkmanager_t NETWORK_MANAGER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the network manager. This will NOT connect to the network.
|
||||||
|
*
|
||||||
|
* @return An error code indicating success or failure.
|
||||||
|
*/
|
||||||
|
errorret_t networkManagerInit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the network manager, dispatching any completed async request
|
||||||
|
* callbacks on the main thread.
|
||||||
|
*
|
||||||
|
* @return An error code indicating success or failure.
|
||||||
|
*/
|
||||||
|
errorret_t networkManagerUpdate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of the network manager. This will NOT disconnect from the network.
|
||||||
|
*
|
||||||
|
* @return An error code indicating success or failure.
|
||||||
|
*/
|
||||||
|
errorret_t networkManagerDispose();
|
||||||
@@ -13,3 +13,4 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
add_subdirectory(asset)
|
add_subdirectory(asset)
|
||||||
add_subdirectory(log)
|
add_subdirectory(log)
|
||||||
add_subdirectory(input)
|
add_subdirectory(input)
|
||||||
|
add_subdirectory(network)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
networklinux.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "networklinux.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
static networklinux_t NETWORK_LINUX;
|
||||||
|
|
||||||
|
static size_t networkLinuxCurlWrite(
|
||||||
|
char *ptr,
|
||||||
|
size_t size,
|
||||||
|
size_t nmemb,
|
||||||
|
void *userdata
|
||||||
|
) {
|
||||||
|
assertNotNull(ptr, "curl write ptr must not be NULL");
|
||||||
|
assertNotNull(userdata, "curl write userdata must not be NULL");
|
||||||
|
networkhttppendingitem_t *item = (networkhttppendingitem_t *)userdata;
|
||||||
|
size_t incoming = size * nmemb;
|
||||||
|
size_t written = strlen(item->responseBody);
|
||||||
|
size_t space = NETWORK_HTTP_RESPONSE_MAX - 1 - written;
|
||||||
|
|
||||||
|
if(incoming > space) incoming = space;
|
||||||
|
if(incoming > 0) {
|
||||||
|
memcpy(item->responseBody + written, ptr, incoming);
|
||||||
|
item->responseBody[written + incoming] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the original byte count — curl treats anything else as an error. */
|
||||||
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void networkLinuxHTTPThread(thread_t *thread) {
|
||||||
|
assertNotNull(thread, "Thread must not be NULL");
|
||||||
|
assertNotNull(thread->data, "Thread data must not be NULL");
|
||||||
|
networkhttppendingitem_t *item = (networkhttppendingitem_t *)thread->data;
|
||||||
|
|
||||||
|
CURL *curl = curl_easy_init();
|
||||||
|
if(!curl) {
|
||||||
|
stringCopy(
|
||||||
|
item->errorMessage, "curl_easy_init failed", NETWORK_ERROR_MESSAGE_MAX - 1
|
||||||
|
);
|
||||||
|
item->isError = true;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, item->url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, networkLinuxCurlWrite);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, item);
|
||||||
|
|
||||||
|
switch(item->method) {
|
||||||
|
case NETWORK_HTTP_REQUEST_METHOD_POST:
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||||
|
curl_easy_setopt(
|
||||||
|
curl, CURLOPT_POSTFIELDS, item->hasBody ? item->body : ""
|
||||||
|
);
|
||||||
|
curl_easy_setopt(
|
||||||
|
curl, CURLOPT_POSTFIELDSIZE,
|
||||||
|
(long)(item->hasBody ? strlen(item->body) : 0)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NETWORK_HTTP_REQUEST_METHOD_PUT:
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||||
|
if(item->hasBody) {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, item->body);
|
||||||
|
curl_easy_setopt(
|
||||||
|
curl, CURLOPT_POSTFIELDSIZE, (long)strlen(item->body)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NETWORK_HTTP_REQUEST_METHOD_DELETE:
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NETWORK_HTTP_REQUEST_METHOD_HEAD:
|
||||||
|
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NETWORK_HTTP_REQUEST_METHOD_OPTIONS:
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NETWORK_HTTP_REQUEST_METHOD_GET:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build header list */
|
||||||
|
struct curl_slist *curlHeaders = NULL;
|
||||||
|
if(item->headerCount > 0) {
|
||||||
|
char_t line[NETWORK_HTTP_HEADER_KEY_MAX + NETWORK_HTTP_HEADER_VAL_MAX + 3];
|
||||||
|
for(uint32_t i = 0; i < item->headerCount; i++) {
|
||||||
|
snprintf(
|
||||||
|
line, sizeof(line), "%s: %s", item->headerKeys[i], item->headerVals[i]
|
||||||
|
);
|
||||||
|
curlHeaders = curl_slist_append(curlHeaders, line);
|
||||||
|
}
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curlHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
if(res != CURLE_OK) {
|
||||||
|
stringCopy(
|
||||||
|
item->errorMessage, curl_easy_strerror(res), NETWORK_ERROR_MESSAGE_MAX - 1
|
||||||
|
);
|
||||||
|
item->isError = true;
|
||||||
|
} else {
|
||||||
|
long code = 0;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||||
|
item->status = (uint16_t)code;
|
||||||
|
item->isError = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(curlHeaders) curl_slist_free_all(curlHeaders);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
done:
|
||||||
|
threadMutexLock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
item->resultReady = true;
|
||||||
|
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t networkLinuxInit() {
|
||||||
|
memoryZero(&NETWORK_LINUX, sizeof(networklinux_t));
|
||||||
|
threadMutexInit(&NETWORK_LINUX.resultsMutex);
|
||||||
|
|
||||||
|
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
|
||||||
|
if(res != CURLE_OK) {
|
||||||
|
errorThrow("curl_global_init failed: %s", curl_easy_strerror(res));
|
||||||
|
}
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t networkLinuxUpdate() {
|
||||||
|
for(int32_t i = 0; i < NETWORK_HTTP_PENDING_MAX; i++) {
|
||||||
|
/* Check under lock whether this slot has a result waiting. */
|
||||||
|
threadMutexLock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
|
||||||
|
networkhttppendingitem_t *item = &NETWORK_LINUX.requests[i];
|
||||||
|
if(!item->used || !item->resultReady) {
|
||||||
|
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Snapshot the small values we need after we drop the lock. */
|
||||||
|
bool_t isError = item->isError;
|
||||||
|
uint16_t status = item->status;
|
||||||
|
networkhttpcallback_t cb = item->callback;
|
||||||
|
networkhttperrorcallback_t errCb = item->errorCallback;
|
||||||
|
void *user = item->user;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep pointers into item's string buffers — valid because item->used is
|
||||||
|
* still true here and only this (main) thread clears it.
|
||||||
|
*/
|
||||||
|
const char_t *responseBody = item->responseBody;
|
||||||
|
const char_t *errorMessage = item->errorMessage;
|
||||||
|
|
||||||
|
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
|
||||||
|
/* Fire the callback without holding the lock. */
|
||||||
|
if(isError) {
|
||||||
|
errorstate_t errState;
|
||||||
|
errState.code = ERROR_NOT_OK;
|
||||||
|
errState.message = (char_t *)errorMessage;
|
||||||
|
errState.lines = (char_t *)"";
|
||||||
|
errorret_t err;
|
||||||
|
err.code = ERROR_NOT_OK;
|
||||||
|
err.state = &errState;
|
||||||
|
errCb(err, user);
|
||||||
|
} else {
|
||||||
|
cb(status, responseBody, NULL, 0, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release the slot now that the callback has consumed it. */
|
||||||
|
threadMutexLock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
item->used = false;
|
||||||
|
item->resultReady = false;
|
||||||
|
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
}
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t networkLinuxDispose() {
|
||||||
|
curl_global_cleanup();
|
||||||
|
threadMutexDispose(&NETWORK_LINUX.resultsMutex);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void networkLinuxHTTPRequest(
|
||||||
|
const char_t *url,
|
||||||
|
const networkhttprequestmethod_t method,
|
||||||
|
const char_t *bodyOrNull,
|
||||||
|
const networkhttpheader_t *headers,
|
||||||
|
const uint32_t headerCount,
|
||||||
|
void *user,
|
||||||
|
networkhttpcallback_t callback,
|
||||||
|
networkhttperrorcallback_t errorCallback
|
||||||
|
) {
|
||||||
|
assertStrLenMin(url, 1, "URL must be non-empty");
|
||||||
|
assertStrLenMax(url, NETWORK_HTTP_URL_MAX, "URL exceeds maximum length");
|
||||||
|
assertNotNull(callback, "Callback must be non-NULL");
|
||||||
|
assertNotNull(errorCallback, "Error callback must be non-NULL");
|
||||||
|
assertTrue(headerCount == 0 || headers != NULL, "Headers must not be NULL when headerCount > 0");
|
||||||
|
|
||||||
|
/* Allocate a slot. */
|
||||||
|
threadMutexLock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
|
||||||
|
networkhttppendingitem_t *item = NULL;
|
||||||
|
for(int32_t i = 0; i < NETWORK_HTTP_PENDING_MAX; i++) {
|
||||||
|
if(!NETWORK_LINUX.requests[i].used) {
|
||||||
|
item = &NETWORK_LINUX.requests[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!item) {
|
||||||
|
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
errorstate_t errState;
|
||||||
|
errState.code = ERROR_NOT_OK;
|
||||||
|
errState.message = (char_t *)"No free HTTP request slots";
|
||||||
|
errState.lines = (char_t *)"";
|
||||||
|
errorret_t err;
|
||||||
|
err.code = ERROR_NOT_OK;
|
||||||
|
err.state = &errState;
|
||||||
|
errorCallback(err, user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryZero(item, sizeof(networkhttppendingitem_t));
|
||||||
|
item->used = true;
|
||||||
|
|
||||||
|
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
|
||||||
|
|
||||||
|
/* Fill the slot (safe: only we have access while used=true, resultReady=false). */
|
||||||
|
stringCopy(item->url, url, NETWORK_HTTP_URL_MAX - 1);
|
||||||
|
item->method = method;
|
||||||
|
|
||||||
|
if(bodyOrNull != NULL) {
|
||||||
|
assertStrLenMax(bodyOrNull, NETWORK_HTTP_BODY_MAX, "Body exceeds maximum length");
|
||||||
|
stringCopy(item->body, bodyOrNull, NETWORK_HTTP_BODY_MAX - 1);
|
||||||
|
item->hasBody = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t hdrCount = headerCount;
|
||||||
|
if(hdrCount > NETWORK_HTTP_HEADER_MAX) hdrCount = NETWORK_HTTP_HEADER_MAX;
|
||||||
|
item->headerCount = hdrCount;
|
||||||
|
for(uint32_t i = 0; i < hdrCount; i++) {
|
||||||
|
assertStrLenMax(headers[i].key, NETWORK_HTTP_HEADER_KEY_MAX, "Header key exceeds maximum length");
|
||||||
|
assertStrLenMax(headers[i].value, NETWORK_HTTP_HEADER_VAL_MAX, "Header value exceeds maximum length");
|
||||||
|
stringCopy(item->headerKeys[i], headers[i].key, NETWORK_HTTP_HEADER_KEY_MAX - 1);
|
||||||
|
stringCopy(item->headerVals[i], headers[i].value, NETWORK_HTTP_HEADER_VAL_MAX - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
item->callback = callback;
|
||||||
|
item->errorCallback = errorCallback;
|
||||||
|
item->user = user;
|
||||||
|
|
||||||
|
/* Kick off the background thread. */
|
||||||
|
threadInit(&item->thread, networkLinuxHTTPThread);
|
||||||
|
item->thread.data = item;
|
||||||
|
threadStartRequest(&item->thread);
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
#include "network/networkhttprequest.h"
|
||||||
|
#include "thread/thread.h"
|
||||||
|
|
||||||
|
#define NETWORK_HTTP_PENDING_MAX 16
|
||||||
|
#define NETWORK_HTTP_URL_MAX 512
|
||||||
|
#define NETWORK_HTTP_BODY_MAX 8192
|
||||||
|
#define NETWORK_HTTP_RESPONSE_MAX 65536
|
||||||
|
#define NETWORK_HTTP_HEADER_MAX 16
|
||||||
|
#define NETWORK_HTTP_HEADER_KEY_MAX 64
|
||||||
|
#define NETWORK_HTTP_HEADER_VAL_MAX 256
|
||||||
|
#define NETWORK_ERROR_MESSAGE_MAX 256
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool_t used;
|
||||||
|
volatile bool_t resultReady;
|
||||||
|
bool_t isError;
|
||||||
|
|
||||||
|
char_t url[NETWORK_HTTP_URL_MAX];
|
||||||
|
networkhttprequestmethod_t method;
|
||||||
|
char_t body[NETWORK_HTTP_BODY_MAX];
|
||||||
|
bool_t hasBody;
|
||||||
|
char_t headerKeys[NETWORK_HTTP_HEADER_MAX][NETWORK_HTTP_HEADER_KEY_MAX];
|
||||||
|
char_t headerVals[NETWORK_HTTP_HEADER_MAX][NETWORK_HTTP_HEADER_VAL_MAX];
|
||||||
|
uint32_t headerCount;
|
||||||
|
|
||||||
|
networkhttpcallback_t callback;
|
||||||
|
networkhttperrorcallback_t errorCallback;
|
||||||
|
void *user;
|
||||||
|
|
||||||
|
uint16_t status;
|
||||||
|
char_t responseBody[NETWORK_HTTP_RESPONSE_MAX];
|
||||||
|
char_t errorMessage[NETWORK_ERROR_MESSAGE_MAX];
|
||||||
|
|
||||||
|
thread_t thread;
|
||||||
|
} networkhttppendingitem_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
networkhttppendingitem_t requests[NETWORK_HTTP_PENDING_MAX];
|
||||||
|
threadmutex_t resultsMutex;
|
||||||
|
} networklinux_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the network manager. Must be called before any other network
|
||||||
|
* functions.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t networkLinuxInit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the network manager, called once per frame to handle completed
|
||||||
|
* HTTP requests.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t networkLinuxUpdate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the network manager.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t networkLinuxDispose();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits an asynchronous HTTP request. The callback will be invoked on the
|
||||||
|
* main thread when next available.
|
||||||
|
*
|
||||||
|
* @param url URL to request.
|
||||||
|
* @param method Method to use for the request.
|
||||||
|
* @param bodyOrNull If POST or PUT, custom body string, can be NULL.
|
||||||
|
* @param headers Array of key-value headers.
|
||||||
|
* @param headerCount Count of headers, can be anything if headers is NULL.
|
||||||
|
* @param user Callback pointer received to the callback.
|
||||||
|
* @param callback The callback to invoke when the request completes.
|
||||||
|
* @param errorCallback The callback to invoke if the request fails.
|
||||||
|
* Note, this doesn't count Non-200 status codes, just
|
||||||
|
* network errors.
|
||||||
|
*/
|
||||||
|
void networkLinuxHTTPRequest(
|
||||||
|
const char_t *url,
|
||||||
|
const networkhttprequestmethod_t method,
|
||||||
|
const char_t *bodyOrNull,
|
||||||
|
const networkhttpheader_t *headers,
|
||||||
|
const uint32_t headerCount,
|
||||||
|
void *user,
|
||||||
|
networkhttpcallback_t callback,
|
||||||
|
networkhttperrorcallback_t errorCallback
|
||||||
|
);
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "networklinux.h"
|
||||||
|
|
||||||
|
#define networkPlatformInit networkLinuxInit
|
||||||
|
#define networkPlatformUpdate networkLinuxUpdate
|
||||||
|
#define networkPlatformDispose networkLinuxDispose
|
||||||
|
#define networkPlatformHTTPRequest networkLinuxHTTPRequest
|
||||||
Reference in New Issue
Block a user