Cleanup CURL impl
This commit is contained in:
parent
f6874dc55a
commit
c66830650f
@ -1,4 +1,4 @@
|
|||||||
// Generated by the DN single header generator 2025-11-08 01:47:36
|
// Generated by the DN single header generator 2025-11-08 17:58:44
|
||||||
|
|
||||||
#define DN_BASE_INC_CPP
|
#define DN_BASE_INC_CPP
|
||||||
|
|
||||||
@ -5047,8 +5047,10 @@ DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args)
|
|||||||
|
|
||||||
QueryPerformanceFrequency(&w32->qpc_frequency);
|
QueryPerformanceFrequency(&w32->qpc_frequency);
|
||||||
HMODULE module = LoadLibraryA("kernel32.dll");
|
HMODULE module = LoadLibraryA("kernel32.dll");
|
||||||
|
if (module) {
|
||||||
w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription");
|
w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription");
|
||||||
FreeLibrary(module);
|
FreeLibrary(module);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: win32 bcrypt
|
// NOTE: win32 bcrypt
|
||||||
wchar_t const BCRYPT_ALGORITHM[] = L"RNG";
|
wchar_t const BCRYPT_ALGORITHM[] = L"RNG";
|
||||||
@ -8656,9 +8658,9 @@ DN_API void DN_OS_Exit(int32_t exit_code)
|
|||||||
|
|
||||||
DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
||||||
char *stdout_buffer,
|
char *stdout_buffer,
|
||||||
size_t *stdout_size,
|
DN_USize *stdout_size,
|
||||||
char *stderr_buffer,
|
char *stderr_buffer,
|
||||||
size_t *stderr_size,
|
DN_USize *stderr_size,
|
||||||
DN_U32 timeout_ms,
|
DN_U32 timeout_ms,
|
||||||
DN_OSErrSink *err)
|
DN_OSErrSink *err)
|
||||||
{
|
{
|
||||||
@ -8704,7 +8706,7 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
|||||||
result.os_error_code = win_error.code;
|
result.os_error_code = win_error.code;
|
||||||
DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_Str8PrintFmt(win_error.msg));
|
DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_Str8PrintFmt(win_error.msg));
|
||||||
} else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) {
|
} else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) {
|
||||||
// NOTE: Read stdout from process //////////////////////////////////////////////////////
|
// NOTE: Read stdout from process
|
||||||
// If the pipes are full, the process will block. We periodically
|
// If the pipes are full, the process will block. We periodically
|
||||||
// flush the pipes to make sure this doesn't happen
|
// flush the pipes to make sure this doesn't happen
|
||||||
char sink[DN_Kilobytes(8)];
|
char sink[DN_Kilobytes(8)];
|
||||||
@ -8713,15 +8715,18 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
|||||||
if (stdout_bytes_available) {
|
if (stdout_bytes_available) {
|
||||||
DWORD bytes_read = 0;
|
DWORD bytes_read = 0;
|
||||||
char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink;
|
char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink;
|
||||||
size_t dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink);
|
DN_USize dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink);
|
||||||
BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL);
|
BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL);
|
||||||
(void)success; // TODO:
|
if (success) {
|
||||||
if (stdout_size)
|
if (stdout_size)
|
||||||
*stdout_size = bytes_read;
|
*stdout_size = bytes_read;
|
||||||
|
} else {
|
||||||
|
DN_OS_ErrSinkAppendF(err, 1, "Failed to read bytes from stdout");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Read stderr from process //////////////////////////////////////////////////////
|
// NOTE: Read stderr from process
|
||||||
stderr_bytes_available = 0;
|
stderr_bytes_available = 0;
|
||||||
if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) {
|
if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) {
|
||||||
if (stderr_bytes_available) {
|
if (stderr_bytes_available) {
|
||||||
@ -8729,9 +8734,12 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
|||||||
size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink);
|
size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink);
|
||||||
DWORD bytes_read = 0;
|
DWORD bytes_read = 0;
|
||||||
BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL);
|
BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL);
|
||||||
(void)success; // TODO:
|
if (success) {
|
||||||
if (stderr_size)
|
if (stderr_size)
|
||||||
*stderr_size = bytes_read;
|
*stderr_size = bytes_read;
|
||||||
|
} else {
|
||||||
|
DN_OS_ErrSinkAppendF(err, 1, "Failed to read bytes from stderr");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8750,11 +8758,16 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
|||||||
DN_Str8PrintFmt(win_error.msg));
|
DN_Str8PrintFmt(win_error.msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Cleanup ///////////////////////////////////////////////////////////////////////////////
|
// NOTE: Cleanup
|
||||||
|
if (handle.stdout_write)
|
||||||
CloseHandle(handle.stdout_write);
|
CloseHandle(handle.stdout_write);
|
||||||
|
if (handle.stderr_write)
|
||||||
CloseHandle(handle.stderr_write);
|
CloseHandle(handle.stderr_write);
|
||||||
|
if (handle.stdout_read)
|
||||||
CloseHandle(handle.stdout_read);
|
CloseHandle(handle.stdout_read);
|
||||||
|
if (handle.stderr_read)
|
||||||
CloseHandle(handle.stderr_read);
|
CloseHandle(handle.stderr_read);
|
||||||
|
if (handle.process)
|
||||||
CloseHandle(handle.process);
|
CloseHandle(handle.process);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Generated by the DN single header generator 2025-11-08 01:47:36
|
// Generated by the DN single header generator 2025-11-08 17:58:44
|
||||||
|
|
||||||
#if !defined(DN_BASE_INC_H)
|
#if !defined(DN_BASE_INC_H)
|
||||||
#define DN_BASE_INC_H
|
#define DN_BASE_INC_H
|
||||||
@ -3691,6 +3691,48 @@ template <typename T> struct DN_List
|
|||||||
#define DN_DLList_ForEach(it, list) \
|
#define DN_DLList_ForEach(it, list) \
|
||||||
auto *it = (list)->next; (it) != (list); (it) = (it)->next
|
auto *it = (list)->next; (it) != (list); (it) = (it)->next
|
||||||
|
|
||||||
|
#define DN_DoublyLLDetach(head, ptr) \
|
||||||
|
do { \
|
||||||
|
if ((head) && (head) == (ptr)) \
|
||||||
|
(head) = (head)->next; \
|
||||||
|
if ((ptr)) { \
|
||||||
|
if ((ptr)->next) \
|
||||||
|
(ptr)->next->prev = (ptr)->prev; \
|
||||||
|
if ((ptr)->prev) \
|
||||||
|
(ptr)->prev->next = (ptr)->next; \
|
||||||
|
(ptr)->prev = (ptr)->next = 0; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DN_DoublyLLAppend(head, ptr) \
|
||||||
|
do { \
|
||||||
|
if ((ptr)) { \
|
||||||
|
DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \
|
||||||
|
(ptr)->prev = (head); \
|
||||||
|
(ptr)->next = 0; \
|
||||||
|
if ((head)) { \
|
||||||
|
(ptr)->next = (head)->next; \
|
||||||
|
(head)->next = (ptr); \
|
||||||
|
} else { \
|
||||||
|
(head) = (ptr); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DN_DoublyLLPrepend(head, ptr) \
|
||||||
|
do { \
|
||||||
|
if ((ptr)) { \
|
||||||
|
DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \
|
||||||
|
(ptr)->prev = nullptr; \
|
||||||
|
(ptr)->next = (head); \
|
||||||
|
if ((head)) { \
|
||||||
|
(ptr)->prev = (head)->prev; \
|
||||||
|
(head)->prev = (ptr); \
|
||||||
|
} else { \
|
||||||
|
(head) = (ptr); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
#define DN_ISLList_Detach(list) (decltype(list))DN_CSLList_Detach((void **)&(list), (void **)&(list)->next)
|
#define DN_ISLList_Detach(list) (decltype(list))DN_CSLList_Detach((void **)&(list), (void **)&(list)->next)
|
||||||
|
|
||||||
|
|||||||
@ -272,6 +272,48 @@ template <typename T> struct DN_List
|
|||||||
#define DN_DLList_ForEach(it, list) \
|
#define DN_DLList_ForEach(it, list) \
|
||||||
auto *it = (list)->next; (it) != (list); (it) = (it)->next
|
auto *it = (list)->next; (it) != (list); (it) = (it)->next
|
||||||
|
|
||||||
|
#define DN_DoublyLLDetach(head, ptr) \
|
||||||
|
do { \
|
||||||
|
if ((head) && (head) == (ptr)) \
|
||||||
|
(head) = (head)->next; \
|
||||||
|
if ((ptr)) { \
|
||||||
|
if ((ptr)->next) \
|
||||||
|
(ptr)->next->prev = (ptr)->prev; \
|
||||||
|
if ((ptr)->prev) \
|
||||||
|
(ptr)->prev->next = (ptr)->next; \
|
||||||
|
(ptr)->prev = (ptr)->next = 0; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DN_DoublyLLAppend(head, ptr) \
|
||||||
|
do { \
|
||||||
|
if ((ptr)) { \
|
||||||
|
DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \
|
||||||
|
(ptr)->prev = (head); \
|
||||||
|
(ptr)->next = 0; \
|
||||||
|
if ((head)) { \
|
||||||
|
(ptr)->next = (head)->next; \
|
||||||
|
(head)->next = (ptr); \
|
||||||
|
} else { \
|
||||||
|
(head) = (ptr); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DN_DoublyLLPrepend(head, ptr) \
|
||||||
|
do { \
|
||||||
|
if ((ptr)) { \
|
||||||
|
DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \
|
||||||
|
(ptr)->prev = nullptr; \
|
||||||
|
(ptr)->next = (head); \
|
||||||
|
if ((head)) { \
|
||||||
|
(ptr)->prev = (head)->prev; \
|
||||||
|
(head)->prev = (ptr); \
|
||||||
|
} else { \
|
||||||
|
(head) = (ptr); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
#define DN_ISLList_Detach(list) (decltype(list))DN_CSLList_Detach((void **)&(list), (void **)&(list)->next)
|
#define DN_ISLList_Detach(list) (decltype(list))DN_CSLList_Detach((void **)&(list), (void **)&(list)->next)
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,33 @@
|
|||||||
#define DN_NET2_CURL_CPP
|
#define DN_NET_CURL_CPP
|
||||||
|
|
||||||
#include "../dn_base_inc.h"
|
#include "../dn_base_inc.h"
|
||||||
#include "../dn_os_inc.h"
|
#include "../dn_os_inc.h"
|
||||||
|
|
||||||
DN_NET2Response DN_NET2_MakeResponseFromFinishedRequest_(DN_NET2Request request, DN_Arena *arena)
|
DN_NETRequestInternal *DN_NET_RequestFromHandle(DN_NETRequest request)
|
||||||
{
|
{
|
||||||
DN_NET2Response result = {};
|
DN_NETRequestInternal *ptr = DN_Cast(DN_NETRequestInternal *) request.handle;
|
||||||
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
|
DN_NETRequestInternal *result = nullptr;
|
||||||
|
if (ptr && ptr->gen == request.gen)
|
||||||
|
result = ptr;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
DN_NETRequest DN_NET_HandleFromRequest(DN_NETRequestInternal *request)
|
||||||
|
{
|
||||||
|
DN_NETRequest result = {};
|
||||||
|
if (request) {
|
||||||
|
result.handle = DN_Cast(DN_UPtr) request;
|
||||||
|
result.gen = request->gen;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
DN_NETResponse DN_NET_MakeResponseFromFinishedRequest_(DN_NETRequest request, DN_Arena *arena)
|
||||||
|
{
|
||||||
|
DN_NETResponse result = {};
|
||||||
|
DN_NETRequestInternal *request_ptr = DN_Cast(DN_NETRequestInternal *) request.handle;
|
||||||
if (request_ptr && request_ptr->gen == request.gen) {
|
if (request_ptr && request_ptr->gen == request.gen) {
|
||||||
DN_NET2ResponseInternal const *response = &request_ptr->response;
|
DN_NETResponseInternal const *response = &request_ptr->response;
|
||||||
|
|
||||||
// NOTE: Construct the response from the request
|
// NOTE: Construct the response from the request
|
||||||
result.request = request;
|
result.request = request;
|
||||||
@ -21,7 +40,7 @@ DN_NET2Response DN_NET2_MakeResponseFromFinishedRequest_(DN_NET2Request request,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DN_NET2_EndFinishedRequest_(DN_NET2Core *net, DN_NET2RequestInternal *request)
|
void DN_NET_EndFinishedRequest_(DN_NETRequestInternal *request)
|
||||||
{
|
{
|
||||||
// NOTE: Deallocate the memory used in the request and reset the string builder
|
// NOTE: Deallocate the memory used in the request and reset the string builder
|
||||||
DN_ArenaPopTo(&request->arena, request->start_response_arena_pos);
|
DN_ArenaPopTo(&request->arena, request->start_response_arena_pos);
|
||||||
@ -31,7 +50,7 @@ void DN_NET2_EndFinishedRequest_(DN_NET2Core *net, DN_NET2RequestInternal *reque
|
|||||||
DN_Assert(request->next == nullptr);
|
DN_Assert(request->next == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DN_NET2_BaseInit_(DN_NET2Core *net, char *base, DN_U64 base_size)
|
void DN_NET_BaseInit_(DN_NETCore *net, char *base, DN_U64 base_size)
|
||||||
{
|
{
|
||||||
net->base = base;
|
net->base = base;
|
||||||
net->base_size = base_size;
|
net->base_size = base_size;
|
||||||
@ -39,11 +58,11 @@ void DN_NET2_BaseInit_(DN_NET2Core *net, char *base, DN_U64 base_size)
|
|||||||
net->completion_sem = DN_OS_SemaphoreInit(0);
|
net->completion_sem = DN_OS_SemaphoreInit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Request DN_NET2_SetupRequest_(DN_NET2RequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type)
|
DN_NETRequest DN_NET_SetupRequest_(DN_NETRequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type)
|
||||||
{
|
{
|
||||||
// NOTE: Setup request
|
// NOTE: Setup request
|
||||||
DN_Assert(request);
|
DN_Assert(request);
|
||||||
DN_NET2Request result = {};
|
DN_NETRequest result = {};
|
||||||
if (request) {
|
if (request) {
|
||||||
if (!request->arena.curr)
|
if (!request->arena.curr)
|
||||||
request->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_ArenaFlags_Nil);
|
request->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_ArenaFlags_Nil);
|
||||||
@ -56,7 +75,7 @@ DN_NET2Request DN_NET2_SetupRequest_(DN_NET2RequestInternal *request, DN_Str8 ur
|
|||||||
request->args.flags = args->flags;
|
request->args.flags = args->flags;
|
||||||
request->args.username = DN_Str8FromStr8Arena(&request->arena, args->username);
|
request->args.username = DN_Str8FromStr8Arena(&request->arena, args->username);
|
||||||
request->args.password = DN_Str8FromStr8Arena(&request->arena, args->password);
|
request->args.password = DN_Str8FromStr8Arena(&request->arena, args->password);
|
||||||
if (type == DN_NET2RequestType_HTTP)
|
if (type == DN_NETRequestType_HTTP)
|
||||||
request->args.payload = DN_Str8FromStr8Arena(&request->arena, args->payload);
|
request->args.payload = DN_Str8FromStr8Arena(&request->arena, args->payload);
|
||||||
|
|
||||||
request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No);
|
request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No);
|
||||||
136
Source/Extra/dn_net.h
Normal file
136
Source/Extra/dn_net.h
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#if !defined(DN_NET_H)
|
||||||
|
#define DN_NET_H
|
||||||
|
|
||||||
|
#include "../dn_base_inc.h"
|
||||||
|
#include "../dn_os_inc.h"
|
||||||
|
|
||||||
|
enum DN_NETRequestType
|
||||||
|
{
|
||||||
|
DN_NETRequestType_Nil,
|
||||||
|
DN_NETRequestType_HTTP,
|
||||||
|
DN_NETRequestType_WS,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DN_NETResponseState
|
||||||
|
{
|
||||||
|
DN_NETResponseState_Nil,
|
||||||
|
DN_NETResponseState_Error,
|
||||||
|
DN_NETResponseState_HTTP,
|
||||||
|
DN_NETResponseState_WSOpen,
|
||||||
|
DN_NETResponseState_WSText,
|
||||||
|
DN_NETResponseState_WSBinary,
|
||||||
|
DN_NETResponseState_WSClose,
|
||||||
|
DN_NETResponseState_WSPing,
|
||||||
|
DN_NETResponseState_WSPong,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DN_NETWSSend
|
||||||
|
{
|
||||||
|
DN_NETWSSend_Text,
|
||||||
|
DN_NETWSSend_Binary,
|
||||||
|
DN_NETWSSend_Close,
|
||||||
|
DN_NETWSSend_Ping,
|
||||||
|
DN_NETWSSend_Pong,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DN_NETDoHTTPFlags
|
||||||
|
{
|
||||||
|
DN_NETDoHTTPFlags_Nil = 0,
|
||||||
|
DN_NETDoHTTPFlags_BasicAuth = 1 << 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DN_NETDoHTTPArgs
|
||||||
|
{
|
||||||
|
// NOTE: WS and HTTP args
|
||||||
|
DN_NETDoHTTPFlags flags;
|
||||||
|
DN_Str8 username;
|
||||||
|
DN_Str8 password;
|
||||||
|
DN_Str8 *headers;
|
||||||
|
DN_U16 headers_size;
|
||||||
|
|
||||||
|
// NOTE: HTTP args only
|
||||||
|
DN_Str8 payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DN_NETRequest
|
||||||
|
{
|
||||||
|
DN_UPtr handle;
|
||||||
|
DN_U64 gen;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DN_NETResponseInternal
|
||||||
|
{
|
||||||
|
DN_NETResponseState state;
|
||||||
|
bool ws_has_more;
|
||||||
|
DN_Str8Builder body;
|
||||||
|
DN_U32 http_status;
|
||||||
|
DN_Str8 error_str8;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DN_NETResponse
|
||||||
|
{
|
||||||
|
// NOTE: Common to WS and HTTP responses
|
||||||
|
DN_NETResponseState state;
|
||||||
|
DN_NETRequest request;
|
||||||
|
DN_Str8 error_str8;
|
||||||
|
DN_Str8 body;
|
||||||
|
|
||||||
|
// NOTE: HTTP responses only
|
||||||
|
DN_U32 http_status;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DN_NETRequestInternal
|
||||||
|
{
|
||||||
|
// NOTE: Initialised in user thread, then read-only and shared to networking thread until reset
|
||||||
|
DN_Arena arena;
|
||||||
|
DN_USize start_response_arena_pos;
|
||||||
|
DN_NETRequestType type;
|
||||||
|
DN_U64 gen;
|
||||||
|
DN_Str8 url;
|
||||||
|
DN_Str8 method;
|
||||||
|
DN_OSSemaphore completion_sem;
|
||||||
|
DN_NETDoHTTPArgs args;
|
||||||
|
DN_NETResponseInternal response;
|
||||||
|
DN_NETRequestInternal *next;
|
||||||
|
DN_NETRequestInternal *prev;
|
||||||
|
DN_U64 context[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DN_NETCore
|
||||||
|
{
|
||||||
|
char *base;
|
||||||
|
DN_U64 base_size;
|
||||||
|
DN_Arena arena;
|
||||||
|
DN_OSSemaphore completion_sem;
|
||||||
|
void *context;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (DN_NETInitFunc) (DN_NETCore *net, char *base, DN_U64 base_size);
|
||||||
|
typedef void (DN_NETDeinitFunc) (DN_NETCore *net);
|
||||||
|
typedef DN_NETRequest (DN_NETDoHTTPFunc) (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args);
|
||||||
|
typedef DN_NETRequest (DN_NETDoWSFunc) (DN_NETCore *net, DN_Str8 url);
|
||||||
|
typedef void (DN_NETDoWSSendFunc) (DN_NETRequest request, DN_Str8 data, DN_NETWSSend send);
|
||||||
|
typedef DN_NETResponse (DN_NETWaitForResponseFunc) (DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms);
|
||||||
|
typedef DN_NETResponse (DN_NETWaitForAnyResponseFunc)(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms);
|
||||||
|
|
||||||
|
struct DN_NETInterface
|
||||||
|
{
|
||||||
|
DN_NETInitFunc* init;
|
||||||
|
DN_NETDeinitFunc* deinit;
|
||||||
|
DN_NETDoHTTPFunc* do_http;
|
||||||
|
DN_NETDoWSFunc* do_ws;
|
||||||
|
DN_NETDoWSSendFunc* do_ws_send;
|
||||||
|
DN_NETWaitForResponseFunc* wait_for_response;
|
||||||
|
DN_NETWaitForAnyResponseFunc* wait_for_any_response;
|
||||||
|
};
|
||||||
|
|
||||||
|
DN_NETRequestInternal *DN_NET_RequestFromHandle (DN_NETRequest request);
|
||||||
|
DN_NETRequest DN_NET_HandleFromRequest (DN_NETRequestInternal *request);
|
||||||
|
|
||||||
|
// NOTE: Internal functions for different networking implementations to use
|
||||||
|
void DN_NET_BaseInit_ (DN_NETCore *net, char *base, DN_U64 base_size);
|
||||||
|
DN_NETRequest DN_NET_SetupRequest_ (DN_NETRequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type);
|
||||||
|
DN_NETResponse DN_NET_MakeResponseFromFinishedRequest_(DN_NETRequest request, DN_Arena *arena);
|
||||||
|
void DN_NET_EndFinishedRequest_ (DN_NETRequestInternal *request);
|
||||||
|
|
||||||
|
#endif // DN_NET_H
|
||||||
@ -1,133 +0,0 @@
|
|||||||
#if !defined(DN_NET2_H)
|
|
||||||
#define DN_NET2_H
|
|
||||||
|
|
||||||
#include "../dn_base_inc.h"
|
|
||||||
#include "../dn_os_inc.h"
|
|
||||||
|
|
||||||
enum DN_NET2RequestType
|
|
||||||
{
|
|
||||||
DN_NET2RequestType_Nil,
|
|
||||||
DN_NET2RequestType_HTTP,
|
|
||||||
DN_NET2RequestType_WS,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DN_NET2ResponseState
|
|
||||||
{
|
|
||||||
DN_NET2ResponseState_Nil,
|
|
||||||
DN_NET2ResponseState_Error,
|
|
||||||
DN_NET2ResponseState_HTTP,
|
|
||||||
DN_NET2ResponseState_WSOpen,
|
|
||||||
DN_NET2ResponseState_WSText,
|
|
||||||
DN_NET2ResponseState_WSBinary,
|
|
||||||
DN_NET2ResponseState_WSClose,
|
|
||||||
DN_NET2ResponseState_WSPing,
|
|
||||||
DN_NET2ResponseState_WSPong,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DN_NET2WSSend
|
|
||||||
{
|
|
||||||
DN_NET2WSSend_Text,
|
|
||||||
DN_NET2WSSend_Binary,
|
|
||||||
DN_NET2WSSend_Close,
|
|
||||||
DN_NET2WSSend_Ping,
|
|
||||||
DN_NET2WSSend_Pong,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DN_NET2DoHTTPFlags
|
|
||||||
{
|
|
||||||
DN_NET2DoHTTPFlags_Nil = 0,
|
|
||||||
DN_NET2DoHTTPFlags_BasicAuth = 1 << 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2DoHTTPArgs
|
|
||||||
{
|
|
||||||
// NOTE: WS and HTTP args
|
|
||||||
DN_NET2DoHTTPFlags flags;
|
|
||||||
DN_Str8 username;
|
|
||||||
DN_Str8 password;
|
|
||||||
DN_Str8 *headers;
|
|
||||||
DN_U16 headers_size;
|
|
||||||
|
|
||||||
// NOTE: HTTP args only
|
|
||||||
DN_Str8 payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2Request
|
|
||||||
{
|
|
||||||
DN_UPtr handle;
|
|
||||||
DN_U64 gen;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2ResponseInternal
|
|
||||||
{
|
|
||||||
DN_NET2ResponseState state;
|
|
||||||
bool ws_has_more;
|
|
||||||
DN_Str8Builder body;
|
|
||||||
DN_U32 http_status;
|
|
||||||
DN_Str8 error_str8;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2Response
|
|
||||||
{
|
|
||||||
// NOTE: Common to WS and HTTP responses
|
|
||||||
DN_NET2ResponseState state;
|
|
||||||
DN_NET2Request request;
|
|
||||||
DN_Str8 error_str8;
|
|
||||||
DN_Str8 body;
|
|
||||||
|
|
||||||
// NOTE: HTTP responses only
|
|
||||||
DN_U32 http_status;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2RequestInternal
|
|
||||||
{
|
|
||||||
// NOTE: Initialised in user thread, then read-only and shared to networking thread until reset
|
|
||||||
DN_Arena arena;
|
|
||||||
DN_USize start_response_arena_pos;
|
|
||||||
DN_NET2RequestType type;
|
|
||||||
DN_U64 gen;
|
|
||||||
DN_Str8 url;
|
|
||||||
DN_Str8 method;
|
|
||||||
DN_OSSemaphore completion_sem;
|
|
||||||
DN_NET2DoHTTPArgs args;
|
|
||||||
DN_NET2ResponseInternal response;
|
|
||||||
DN_NET2RequestInternal *next;
|
|
||||||
DN_NET2RequestInternal *prev;
|
|
||||||
DN_U64 context[2];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2Core
|
|
||||||
{
|
|
||||||
char *base;
|
|
||||||
DN_U64 base_size;
|
|
||||||
DN_Arena arena;
|
|
||||||
DN_OSSemaphore completion_sem;
|
|
||||||
DN_NET2RequestInternal *done_list;
|
|
||||||
DN_NET2RequestInternal *free_list;
|
|
||||||
void *context;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef void (DN_NET2InitFunc) (DN_NET2Core *net, char *base, DN_U64 base_size);
|
|
||||||
typedef DN_NET2Request (DN_NET2DoHTTPFunc) (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args);
|
|
||||||
typedef DN_NET2Request (DN_NET2DoWSFunc) (DN_NET2Core *net, DN_Str8 url);
|
|
||||||
typedef void (DN_NET2DoWSSendFunc) (DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send);
|
|
||||||
typedef DN_NET2Response (DN_NET2WaitForResponseFunc) (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms);
|
|
||||||
typedef DN_NET2Response (DN_NET2WaitForAnyResponseFunc)(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms);
|
|
||||||
|
|
||||||
struct DN_NET2Interface
|
|
||||||
{
|
|
||||||
DN_NET2InitFunc* init;
|
|
||||||
DN_NET2DoHTTPFunc* do_http;
|
|
||||||
DN_NET2DoWSFunc* do_ws;
|
|
||||||
DN_NET2DoWSSendFunc* do_ws_send;
|
|
||||||
DN_NET2WaitForResponseFunc* wait_for_response;
|
|
||||||
DN_NET2WaitForAnyResponseFunc* wait_for_any_response;
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOTE: Internal functions for different networking implementations to use
|
|
||||||
void DN_NET2_BaseInit_ (DN_NET2Core *net, char *base, DN_U64 base_size);
|
|
||||||
DN_NET2Request DN_NET2_SetupRequest_ (DN_NET2RequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type);
|
|
||||||
DN_NET2Response DN_NET2_MakeResponseFromFinishedRequest_(DN_NET2Request request, DN_Arena *arena);
|
|
||||||
void DN_NET2_EndFinishedRequest_ (DN_NET2Core *net, DN_NET2RequestInternal *request);
|
|
||||||
|
|
||||||
#endif // DN_NET2_H
|
|
||||||
@ -1,553 +0,0 @@
|
|||||||
#define DN_NET2_CURL_CPP
|
|
||||||
|
|
||||||
#include "../dn_base_inc.h"
|
|
||||||
#include "../dn_os_inc.h"
|
|
||||||
#include "dn_net2_curl.h"
|
|
||||||
|
|
||||||
static void DN_NET2_MarkRequestDone_(DN_NET2Core *net, DN_NET2RequestInternal *request)
|
|
||||||
{
|
|
||||||
for (DN_OS_MutexScope(&net->free_or_done_mutex))
|
|
||||||
DN_DLList_Append(net->done_list, request);
|
|
||||||
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
|
|
||||||
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static DN_NET2Response DN_NET2_WaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms)
|
|
||||||
{
|
|
||||||
DN_NET2Response result = {};
|
|
||||||
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms);
|
|
||||||
if (wait == DN_OSSemaphoreWaitResult_Success) {
|
|
||||||
for (DN_OS_MutexScope(&net->free_or_done_mutex)) {
|
|
||||||
DN_NET2RequestInternal *request = nullptr;
|
|
||||||
DN_DLList_Dequeue(net->done_list, request);
|
|
||||||
DN_Assert(request);
|
|
||||||
|
|
||||||
// NOTE: Fill in the result
|
|
||||||
DN_NET2ResponseInternal const *response = &request->response;
|
|
||||||
result.request = {DN_Cast(DN_U64) request};
|
|
||||||
result.success = response->status != DN_NET2RequestStatus_Error;
|
|
||||||
result.ws_type = response->ws_type;
|
|
||||||
result.http_status = response->http_status;
|
|
||||||
result.body = DN_Str8BuilderBuild(&response->body, arena);
|
|
||||||
if (response->error.size)
|
|
||||||
result.error = DN_Str8FromStr8Arena(arena, response->error);
|
|
||||||
|
|
||||||
// NOTE: Deallocate the memory used in the request
|
|
||||||
DN_ArenaPopTo(&request->arena, request->start_response_arena_pos);
|
|
||||||
request->response.body = DN_Str8BuilderFromArena(&request->arena);
|
|
||||||
|
|
||||||
// NOTE: For websocket requests, notify the NET thread we've read data from it and it can go
|
|
||||||
// back to polling the socket for more data
|
|
||||||
if (request->type == DN_NET2RequestType_WS && request->response.status != DN_NET2RequestStatus_Error) {
|
|
||||||
DN_NET2RingEvent event = {};
|
|
||||||
event.type = DN_NET2RingEventType_ReceivedWSReceipt;
|
|
||||||
event.request = {DN_Cast(DN_U64)request};
|
|
||||||
for (DN_OS_MutexScope(&net->ring_mutex))
|
|
||||||
DN_Ring_WriteStruct(&net->ring, &event);
|
|
||||||
curl_multi_wakeup(net->curlm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DN_NET2Response DN_NET2_WaitForResponse(DN_NET2Core *net, DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms)
|
|
||||||
{
|
|
||||||
DN_NET2Response result = {};
|
|
||||||
if (request.handle != 0) {
|
|
||||||
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
|
|
||||||
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&request_ptr->completion_sem, timeout_ms);
|
|
||||||
if (wait == DN_OSSemaphoreWaitResult_Success) {
|
|
||||||
// NOTE: Fill in the result
|
|
||||||
DN_NET2ResponseInternal const *response = &request_ptr->response;
|
|
||||||
result.request = request;
|
|
||||||
result.success = response->status != DN_NET2RequestStatus_Error;
|
|
||||||
result.ws_type = response->ws_type;
|
|
||||||
result.http_status = response->http_status;
|
|
||||||
result.body = DN_Str8BuilderBuild(&response->body, arena);
|
|
||||||
if (response->error.size)
|
|
||||||
result.error = DN_Str8FromStr8Arena(arena, response->error);
|
|
||||||
|
|
||||||
// NOTE: Deallocate the memory used in the request
|
|
||||||
DN_ArenaPopTo(&request_ptr->arena, request_ptr->start_response_arena_pos);
|
|
||||||
request_ptr->response.body = DN_Str8BuilderFromArena(&request_ptr->arena);
|
|
||||||
|
|
||||||
// NOTE: Decrement the global completion tracking semaphore (this is so that if you waited on
|
|
||||||
// the net object's semaphore, you don't get a phantom wakeup because this function already
|
|
||||||
// consumed it).
|
|
||||||
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/);
|
|
||||||
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize)net_wait_result);
|
|
||||||
|
|
||||||
// NOTE: Remove the request from the done list
|
|
||||||
for (DN_OS_MutexScope(&net->free_or_done_mutex)) {
|
|
||||||
bool is_in_done_list = false;
|
|
||||||
for (DN_DLList_ForEach(it, net->done_list)) {
|
|
||||||
if (it == request_ptr) {
|
|
||||||
is_in_done_list = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DN_Assert(is_in_done_list);
|
|
||||||
DN_DLList_Detach(request_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: For websocket requests, notify the NET thread we've read data from it and it can go
|
|
||||||
// back to polling the socket for more data
|
|
||||||
if (request_ptr->type == DN_NET2RequestType_WS && request_ptr->response.status != DN_NET2RequestStatus_Error) {
|
|
||||||
DN_NET2RingEvent event = {};
|
|
||||||
event.type = DN_NET2RingEventType_ReceivedWSReceipt;
|
|
||||||
event.request = request;
|
|
||||||
for (DN_OS_MutexScope(&net->ring_mutex))
|
|
||||||
DN_Ring_WriteStruct(&net->ring, &event);
|
|
||||||
curl_multi_wakeup(net->curlm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void DN_NET2_DeinitRequest(DN_NET2Core *net, DN_NET2Request *request)
|
|
||||||
{
|
|
||||||
DN_NET2RingEvent event = {};
|
|
||||||
event.type = DN_NET2RingEventType_DeinitRequest;
|
|
||||||
event.request = *request;
|
|
||||||
*request = {};
|
|
||||||
for (DN_OS_MutexScope(&net->ring_mutex))
|
|
||||||
DN_Ring_WriteStruct(&net->ring, &event);
|
|
||||||
}
|
|
||||||
|
|
||||||
static DN_USize DN_NET2_HTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data)
|
|
||||||
{
|
|
||||||
auto *request = DN_Cast(DN_NET2RequestInternal *) user_data;
|
|
||||||
DN_USize result = 0;
|
|
||||||
DN_USize payload_size = size * count;
|
|
||||||
if (DN_Str8BuilderAppendBytesCopy(&request->response.body, payload, payload_size))
|
|
||||||
result = payload_size;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t DN_NET2_ThreadEntryPoint_(DN_OSThread *thread)
|
|
||||||
{
|
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) thread->user_context;
|
|
||||||
DN_OS_ThreadSetName(DN_Str8FromPtr(net->curl_thread.name.data, net->curl_thread.name.size));
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr);
|
|
||||||
for (bool dequeue_ring = true; dequeue_ring;) {
|
|
||||||
// NOTE: Dequeue user request
|
|
||||||
DN_NET2RingEvent event = {};
|
|
||||||
for (DN_OS_MutexScope(&net->ring_mutex)) {
|
|
||||||
if (DN_Ring_HasData(&net->ring, sizeof(event)))
|
|
||||||
DN_Ring_Read(&net->ring, &event, sizeof(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case DN_NET2RingEventType_Nil: dequeue_ring = false; break;
|
|
||||||
|
|
||||||
case DN_NET2RingEventType_DoHTTP: {
|
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *)event.request.handle;
|
|
||||||
DN_Assert(request->response.status != DN_NET2RequestStatus_Error);
|
|
||||||
switch (request->type) {
|
|
||||||
case DN_NET2RequestType_Nil: {
|
|
||||||
DN_NET2_MarkRequestDone_(net, request);
|
|
||||||
DN_InvalidCodePath;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DN_NET2RequestType_HTTP: {
|
|
||||||
DN_Assert(request->response.status == DN_NET2RequestStatus_Nil);
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context;
|
|
||||||
CURLMcode multi_add = curl_multi_add_handle(net->curlm, conn->curl);
|
|
||||||
DN_Assert(multi_add == CURLM_OK);
|
|
||||||
DN_Assert(request->next == nullptr);
|
|
||||||
DN_Assert(request->prev == nullptr);
|
|
||||||
DN_DLList_Append(net->http_list, request);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DN_NET2RequestType_WS: {
|
|
||||||
DN_Assert(request->response.status == DN_NET2RequestStatus_HTTPReceived ||
|
|
||||||
request->response.status == DN_NET2RequestStatus_WSReceived ||
|
|
||||||
request->response.status == DN_NET2RequestStatus_Nil);
|
|
||||||
|
|
||||||
DN_Assert(request->next == nullptr);
|
|
||||||
DN_Assert(request->prev == nullptr);
|
|
||||||
if (request->response.status == DN_NET2RequestStatus_Nil) {
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context;
|
|
||||||
CURLMcode multi_add = curl_multi_add_handle(net->curlm, conn->curl);
|
|
||||||
DN_Assert(multi_add == CURLM_OK);
|
|
||||||
DN_DLList_Append(net->http_list, request); // Open the WS connection
|
|
||||||
} else {
|
|
||||||
DN_DLList_Append(net->ws_list, request); // Ready to send data through the WS connection
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DN_NET2RingEventType_SendWS: {
|
|
||||||
DN_Str8 payload = {};
|
|
||||||
for (DN_OS_MutexScope(&net->ring_mutex)) {
|
|
||||||
DN_Assert(DN_Ring_HasData(&net->ring, event.send_ws_payload_size));
|
|
||||||
payload = DN_Str8FromArena(tmem.arena, event.send_ws_payload_size, DN_ZMem_No);
|
|
||||||
DN_Ring_Read(&net->ring, payload.data, payload.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
DN_U32 curlws_flag = 0;
|
|
||||||
switch (event.send_ws_type) {
|
|
||||||
case DN_NET2WSType_Nil: DN_InvalidCodePath; break;
|
|
||||||
case DN_NET2WSType_Text: curlws_flag = CURLWS_TEXT; break;
|
|
||||||
case DN_NET2WSType_Binary: curlws_flag = CURLWS_BINARY; break;
|
|
||||||
case DN_NET2WSType_Close: curlws_flag = CURLWS_CLOSE; break;
|
|
||||||
case DN_NET2WSType_Ping: curlws_flag = CURLWS_PING; break;
|
|
||||||
case DN_NET2WSType_Pong: curlws_flag = CURLWS_PONG; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
|
|
||||||
DN_Assert(request->type == DN_NET2RequestType_WS);
|
|
||||||
DN_Assert(request->response.status == DN_NET2RequestStatus_HTTPReceived || request->response.status == DN_NET2RequestStatus_WSReceived);
|
|
||||||
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context;
|
|
||||||
DN_USize sent = 0;
|
|
||||||
CURLcode send_result = curl_ws_send(conn->curl, payload.data, payload.size, &sent, 0, curlws_flag);
|
|
||||||
DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result));
|
|
||||||
DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DN_NET2RingEventType_ReceivedWSReceipt: {
|
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
|
|
||||||
DN_Assert(request->type == DN_NET2RequestType_WS);
|
|
||||||
DN_Assert(request->response.status == DN_NET2RequestStatus_WSReceived ||
|
|
||||||
request->response.status == DN_NET2RequestStatus_HTTPReceived);
|
|
||||||
DN_Assert(request->next == nullptr);
|
|
||||||
DN_Assert(request->prev == nullptr);
|
|
||||||
DN_DLList_Append(net->ws_list, request);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DN_NET2RingEventType_DeinitRequest: {
|
|
||||||
if (event.request.handle != 0) {
|
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
|
|
||||||
request->response = {};
|
|
||||||
|
|
||||||
DN_ArenaClear(&request->arena);
|
|
||||||
DN_OS_SemaphoreDeinit(&request->completion_sem);
|
|
||||||
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context;
|
|
||||||
curl_multi_remove_handle(net->curlm, conn->curl);
|
|
||||||
curl_easy_reset(conn->curl);
|
|
||||||
curl_slist_free_all(conn->curl_slist);
|
|
||||||
|
|
||||||
for (DN_OS_MutexScope(&net->free_or_done_mutex))
|
|
||||||
DN_DLList_Append(net->free_list, request);
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Pump handles
|
|
||||||
int running_handles = 0;
|
|
||||||
CURLMcode perform_result = curl_multi_perform(net->curlm, &running_handles);
|
|
||||||
if (perform_result != CURLM_OK)
|
|
||||||
DN_InvalidCodePath;
|
|
||||||
|
|
||||||
// NOTE: Check pump result
|
|
||||||
for (;;) {
|
|
||||||
int msgs_in_queue = 0;
|
|
||||||
CURLMsg *msg = curl_multi_info_read(net->curlm, &msgs_in_queue);
|
|
||||||
if (msg) {
|
|
||||||
// NOTE: Get request handle
|
|
||||||
DN_NET2RequestInternal *request = nullptr;
|
|
||||||
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & request);
|
|
||||||
DN_Assert(request);
|
|
||||||
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *)request->context;
|
|
||||||
DN_Assert(conn->curl == msg->easy_handle);
|
|
||||||
|
|
||||||
if (msg->data.result == CURLE_OK) {
|
|
||||||
// NOTE: Get HTTP response code
|
|
||||||
CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &request->response.http_status);
|
|
||||||
if (get_result == CURLE_OK) {
|
|
||||||
request->response.status = DN_NET2RequestStatus_HTTPReceived;
|
|
||||||
} else {
|
|
||||||
request->response.error = DN_Str8FromFmtArena(&request->arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result));
|
|
||||||
request->response.status = DN_NET2RequestStatus_Error;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
request->response.status = DN_NET2RequestStatus_Error;
|
|
||||||
request->response.error = DN_Str8FromFmtArena(&request->arena,
|
|
||||||
"Net request to '%.*s' failed (CURL %d): %s",
|
|
||||||
DN_Str8PrintFmt(request->url),
|
|
||||||
msg->data.result,
|
|
||||||
curl_easy_strerror(msg->data.result));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request->type == DN_NET2RequestType_HTTP || request->response.status == DN_NET2RequestStatus_Error) {
|
|
||||||
// NOTE: Remove the request from the multi handle if we're a HTTP request
|
|
||||||
// because it typically terminates the connection. In websockets the
|
|
||||||
// connection remains in the multi-handle to allow you to send and
|
|
||||||
// receive WS data from it.
|
|
||||||
//
|
|
||||||
// If there's an error (either websocket or HTTP) we will also remove the
|
|
||||||
// connection from the multi handle as it failed. One a connection has
|
|
||||||
// failed, curl will not poll that connection so there's no point keeping
|
|
||||||
// it attached to the multi handle.
|
|
||||||
curl_multi_remove_handle(net->curlm, msg->easy_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
DN_NET2_MarkRequestDone_(net, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msgs_in_queue == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Check websockets
|
|
||||||
DN_I32 sleep_time_ms = DN_DLList_HasItems(net->ws_list) ? 100 : INT32_MAX;
|
|
||||||
for (DN_DLList_ForEach(request, net->ws_list)) {
|
|
||||||
DN_Assert(request->response.status == DN_NET2RequestStatus_HTTPReceived ||
|
|
||||||
request->response.status == DN_NET2RequestStatus_WSReceived);
|
|
||||||
|
|
||||||
CURLcode receive_result = CURLE_OK;
|
|
||||||
const curl_ws_frame *meta = nullptr;
|
|
||||||
size_t bytes_read_this_frame = {};
|
|
||||||
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context;
|
|
||||||
for (;;) {
|
|
||||||
// NOTE: Determine WS payload size received
|
|
||||||
DN_USize bytes_read = 0;
|
|
||||||
receive_result = curl_ws_recv(conn->curl, nullptr, 0, &bytes_read, &meta);
|
|
||||||
if (receive_result != CURLE_OK || meta->bytesleft == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// NOTE: Allocate and read
|
|
||||||
DN_Str8 buffer = DN_Str8FromArena(&request->arena, meta->bytesleft, DN_ZMem_No);
|
|
||||||
receive_result = curl_ws_recv(conn->curl, buffer.data, buffer.size, &buffer.size, &meta);
|
|
||||||
bytes_read_this_frame += buffer.size;
|
|
||||||
DN_Str8BuilderAppendRef(&request->response.body, buffer);
|
|
||||||
|
|
||||||
if (meta->flags & CURLWS_TEXT)
|
|
||||||
request->response.ws_type = DN_NET2WSType_Text;
|
|
||||||
|
|
||||||
if (meta->flags & CURLWS_BINARY)
|
|
||||||
request->response.ws_type = DN_NET2WSType_Binary;
|
|
||||||
|
|
||||||
if (meta->flags & CURLWS_PING)
|
|
||||||
request->response.ws_type = DN_NET2WSType_Ping;
|
|
||||||
|
|
||||||
if (meta->flags & CURLWS_PONG)
|
|
||||||
request->response.ws_type = DN_NET2WSType_Pong;
|
|
||||||
|
|
||||||
if (meta->flags & CURLWS_CLOSE)
|
|
||||||
request->response.ws_type = DN_NET2WSType_Close;
|
|
||||||
|
|
||||||
request->response.ws_has_more = meta->flags & CURLWS_CONT || meta->bytesleft > 0;
|
|
||||||
if (request->response.ws_has_more) {
|
|
||||||
if (meta->flags & CURLWS_CONT) {
|
|
||||||
bool is_text_or_binary = request->response.ws_type == DN_NET2WSType_Binary ||
|
|
||||||
request->response.ws_type == DN_NET2WSType_Text;
|
|
||||||
DN_Assert(is_text_or_binary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (receive_result != CURLE_OK || bytes_read_this_frame == meta->len)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: We read all the possible bytes that CURL has received for this
|
|
||||||
// socket, but, there are more bytes left that we will receive on subsequent
|
|
||||||
// calls. We will continue to the next request and return back to this one
|
|
||||||
// when PumpRequests is called again where hopefully that data has arrived.
|
|
||||||
if (request->response.ws_has_more || receive_result == CURLE_AGAIN) {
|
|
||||||
if (receive_result == CURLE_AGAIN)
|
|
||||||
sleep_time_ms = 100;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
request->response.status = DN_NET2RequestStatus_WSReceived;
|
|
||||||
if (receive_result != CURLE_OK) {
|
|
||||||
request->response.status = DN_NET2RequestStatus_Error;
|
|
||||||
request->response.error = DN_Str8FromFmtArena(&request->arena,
|
|
||||||
"Websocket failed to receive data for '%.*s' (CURL %d): %s",
|
|
||||||
DN_Str8PrintFmt(request->url),
|
|
||||||
receive_result,
|
|
||||||
curl_easy_strerror(receive_result));
|
|
||||||
}
|
|
||||||
|
|
||||||
DN_NET2RequestInternal *request_copy = request;
|
|
||||||
request = request->prev;
|
|
||||||
DN_NET2_MarkRequestDone_(net, request_copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_multi_poll(net->curlm, nullptr, 0, sleep_time_ms, nullptr);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void DN_NET2_Init(DN_NET2Core *net, char *ring_base, DN_USize ring_size, char *base, DN_U64 base_size)
|
|
||||||
{
|
|
||||||
net->base = base;
|
|
||||||
net->base_size = base_size;
|
|
||||||
net->arena = DN_ArenaFromBuffer(net->base, net->base_size, DN_ArenaFlags_Nil);
|
|
||||||
net->ring.base = ring_base;
|
|
||||||
net->ring.size = ring_size;
|
|
||||||
net->ring_mutex = DN_OS_MutexInit();
|
|
||||||
net->completion_sem = DN_OS_SemaphoreInit(0);
|
|
||||||
net->free_or_done_mutex = DN_OS_MutexInit();
|
|
||||||
net->curlm = DN_Cast(CURLM *)curl_multi_init();
|
|
||||||
DN_DLList_InitArena(net->free_list, DN_NET2RequestInternal, &net->arena);
|
|
||||||
DN_DLList_InitArena(net->http_list, DN_NET2RequestInternal, &net->arena);
|
|
||||||
DN_DLList_InitArena(net->ws_list, DN_NET2RequestInternal, &net->arena);
|
|
||||||
DN_DLList_InitArena(net->done_list, DN_NET2RequestInternal, &net->arena);
|
|
||||||
|
|
||||||
DN_FmtAppend(net->curl_thread.name.data, &net->curl_thread.name.size, sizeof(net->curl_thread.name.data), "NET (CURL)");
|
|
||||||
DN_OS_ThreadInit(&net->curl_thread, DN_NET2_ThreadEntryPoint_, net);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void DN_NET2_SetupCurlRequest_(DN_NET2RequestInternal *request)
|
|
||||||
{
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context;
|
|
||||||
CURL *curl = conn->curl;
|
|
||||||
curl_easy_setopt(curl, CURLOPT_PRIVATE, request);
|
|
||||||
|
|
||||||
// NOTE: Perform request and read all response headers before handing
|
|
||||||
// control back to app.
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, request->url.data);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
|
|
||||||
|
|
||||||
// NOTE: Setup response handler
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET2_HTTPCallback_);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, request);
|
|
||||||
|
|
||||||
// NOTE: Assign HTTP headers
|
|
||||||
for (DN_ForItSize(it, DN_Str8, request->args.headers, request->args.headers_size))
|
|
||||||
conn->curl_slist = curl_slist_append(conn->curl_slist, it.data->data);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, conn->curl_slist);
|
|
||||||
|
|
||||||
// NOTE: Setup handle for protocol /////////////////////////////////////////////////////////
|
|
||||||
switch (request->type) {
|
|
||||||
case DN_NET2RequestType_Nil: DN_InvalidCodePath; break;
|
|
||||||
|
|
||||||
case DN_NET2RequestType_WS: {
|
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DN_NET2RequestType_HTTP: {
|
|
||||||
request->method = DN_Str8TrimWhitespaceAround(request->method);
|
|
||||||
DN_Str8 const GET = DN_Str8Lit("GET");
|
|
||||||
DN_Str8 const POST = DN_Str8Lit("POST");
|
|
||||||
|
|
||||||
if (DN_Str8EqInsensitive(request->method, GET)) {
|
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
|
|
||||||
} else if (DN_Str8EqInsensitive(request->method, POST)) {
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
|
||||||
if (request->args.payload.size > DN_Gigabytes(2))
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, request->args.payload.size);
|
|
||||||
else
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request->args.payload.size);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request->args.payload.data);
|
|
||||||
} else {
|
|
||||||
DN_InvalidCodePathF("Unimplemented");
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Handle basic auth
|
|
||||||
if (request->args.flags & DN_NET2DoHTTPFlags_BasicAuth) {
|
|
||||||
if (request->args.username.size && request->args.password.size) {
|
|
||||||
DN_Assert(request->args.username.data[request->args.username.size] == 0);
|
|
||||||
DN_Assert(request->args.password.data[request->args.password.size] == 0);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_USERNAME, request->args.username.data);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_PASSWORD, request->args.password.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static DN_NET2Request DN_NET2_DoRequest_(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type)
|
|
||||||
{
|
|
||||||
DN_Assert(net);
|
|
||||||
|
|
||||||
// NOTE: Allocate request
|
|
||||||
DN_NET2RequestInternal *request = nullptr;
|
|
||||||
for (DN_OS_MutexScope(&net->free_or_done_mutex))
|
|
||||||
DN_DLList_Dequeue(net->free_list, request);
|
|
||||||
|
|
||||||
if (!request) {
|
|
||||||
request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes);
|
|
||||||
DN_NET2CurlConn *conn = new (request->context) DN_NET2CurlConn;
|
|
||||||
conn->curl = DN_Cast(CURL *)curl_easy_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Setup request
|
|
||||||
DN_NET2Request result = {};
|
|
||||||
if (request) {
|
|
||||||
result.handle = DN_Cast(DN_U64) request;
|
|
||||||
if (!request->arena.curr)
|
|
||||||
request->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_ArenaFlags_Nil);
|
|
||||||
|
|
||||||
request->type = type;
|
|
||||||
request->gen = DN_Max(request->gen + 1, 1);
|
|
||||||
request->url = DN_Str8FromStr8Arena(&request->arena, url);
|
|
||||||
request->method = DN_Str8FromStr8Arena(&request->arena, method);
|
|
||||||
|
|
||||||
if (args) {
|
|
||||||
request->args.flags = args->flags;
|
|
||||||
request->args.username = DN_Str8FromStr8Arena(&request->arena, args->username);
|
|
||||||
request->args.password = DN_Str8FromStr8Arena(&request->arena, args->password);
|
|
||||||
if (type == DN_NET2RequestType_HTTP)
|
|
||||||
request->args.payload = DN_Str8FromStr8Arena(&request->arena, args->payload);
|
|
||||||
|
|
||||||
request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No);
|
|
||||||
DN_Assert(request->args.headers);
|
|
||||||
if (request->args.headers) {
|
|
||||||
for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size))
|
|
||||||
request->args.headers[it.index] = DN_Str8FromStr8Arena(&request->arena, *it.data);
|
|
||||||
request->args.headers_size = args->headers_size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request->response.body = DN_Str8BuilderFromArena(&request->arena);
|
|
||||||
request->completion_sem = DN_OS_SemaphoreInit(0);
|
|
||||||
request->start_response_arena_pos = DN_ArenaPos(&request->arena);
|
|
||||||
|
|
||||||
DN_NET2_SetupCurlRequest_(request);
|
|
||||||
|
|
||||||
// NOTE: Submit request to the networking thread's queue
|
|
||||||
DN_NET2RingEvent event = {};
|
|
||||||
event.type = DN_NET2RingEventType_DoHTTP;
|
|
||||||
event.request = result;
|
|
||||||
for (DN_OS_MutexScope(&net->ring_mutex))
|
|
||||||
DN_Ring_WriteStruct(&net->ring, &event);
|
|
||||||
|
|
||||||
curl_multi_wakeup(net->curlm);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DN_NET2Request DN_NET2_DoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args)
|
|
||||||
{
|
|
||||||
DN_NET2Request result = DN_NET2_DoRequest_(net, url, method, args, DN_NET2RequestType_HTTP);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DN_NET2Request DN_NET2_OpenWS(DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args)
|
|
||||||
{
|
|
||||||
DN_NET2Request result = DN_NET2_DoRequest_(net, url, DN_Str8Lit(""), args, DN_NET2RequestType_WS);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void DN_NET2_SendWS(DN_NET2Core *net, DN_NET2Request request, DN_Str8 payload, DN_NET2WSType type)
|
|
||||||
{
|
|
||||||
DN_Assert(type != DN_NET2WSType_Nil);
|
|
||||||
DN_NET2RingEvent event = {};
|
|
||||||
event.type = DN_NET2RingEventType_SendWS;
|
|
||||||
event.request = request;
|
|
||||||
event.send_ws_payload_size = payload.size;
|
|
||||||
event.send_ws_type = type;
|
|
||||||
|
|
||||||
for (DN_OS_MutexScope(&net->ring_mutex)) {
|
|
||||||
DN_Assert(DN_Ring_HasSpace(&net->ring, payload.size));
|
|
||||||
DN_Ring_WriteStruct(&net->ring, &event);
|
|
||||||
DN_Ring_Write(&net->ring, payload.data, payload.size);
|
|
||||||
}
|
|
||||||
curl_multi_wakeup(net->curlm);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
#if !defined(DN_NET2_CURL_H)
|
|
||||||
#define DN_NET2_CURL_H
|
|
||||||
|
|
||||||
#include "../dn_base_inc.h"
|
|
||||||
#include "../dn_os_inc.h"
|
|
||||||
|
|
||||||
enum DN_NET2RequestType
|
|
||||||
{
|
|
||||||
DN_NET2RequestType_Nil,
|
|
||||||
DN_NET2RequestType_HTTP,
|
|
||||||
DN_NET2RequestType_WS,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DN_NET2RequestStatus
|
|
||||||
{
|
|
||||||
DN_NET2RequestStatus_Nil,
|
|
||||||
DN_NET2RequestStatus_Error,
|
|
||||||
DN_NET2RequestStatus_HTTPReceived,
|
|
||||||
DN_NET2RequestStatus_WSReceived,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DN_NET2WSType
|
|
||||||
{
|
|
||||||
DN_NET2WSType_Nil,
|
|
||||||
DN_NET2WSType_Text,
|
|
||||||
DN_NET2WSType_Binary,
|
|
||||||
DN_NET2WSType_Close,
|
|
||||||
DN_NET2WSType_Ping,
|
|
||||||
DN_NET2WSType_Pong,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2CurlConn
|
|
||||||
{
|
|
||||||
void *curl;
|
|
||||||
struct curl_slist *curl_slist;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DN_NET2DoHTTPFlags
|
|
||||||
{
|
|
||||||
DN_NET2DoHTTPFlags_Nil = 0,
|
|
||||||
DN_NET2DoHTTPFlags_BasicAuth = 1 << 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2DoHTTPArgs
|
|
||||||
{
|
|
||||||
// NOTE: WS and HTTP args
|
|
||||||
DN_NET2DoHTTPFlags flags;
|
|
||||||
DN_Str8 username;
|
|
||||||
DN_Str8 password;
|
|
||||||
DN_Str8 *headers;
|
|
||||||
DN_U16 headers_size;
|
|
||||||
|
|
||||||
// NOTE: HTTP args only
|
|
||||||
DN_Str8 payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2ResponseInternal
|
|
||||||
{
|
|
||||||
DN_NET2RequestStatus status;
|
|
||||||
DN_NET2WSType ws_type;
|
|
||||||
bool ws_has_more;
|
|
||||||
DN_Str8Builder body;
|
|
||||||
DN_U32 http_status;
|
|
||||||
DN_Str8 error;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2Request
|
|
||||||
{
|
|
||||||
DN_U64 handle;
|
|
||||||
DN_U64 gen;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2Response
|
|
||||||
{
|
|
||||||
DN_NET2Request request;
|
|
||||||
bool success;
|
|
||||||
DN_NET2WSType ws_type;
|
|
||||||
DN_U32 http_status;
|
|
||||||
DN_Str8 error;
|
|
||||||
DN_Str8 body;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2RequestInternal
|
|
||||||
{
|
|
||||||
// NOTE: Initialised in user thread, then read-only and shared to networking thread until reset
|
|
||||||
DN_Arena arena;
|
|
||||||
DN_USize start_response_arena_pos;
|
|
||||||
DN_NET2RequestType type;
|
|
||||||
DN_U64 gen;
|
|
||||||
DN_Str8 url;
|
|
||||||
DN_Str8 method;
|
|
||||||
DN_OSSemaphore completion_sem;
|
|
||||||
DN_NET2DoHTTPArgs args;
|
|
||||||
DN_NET2ResponseInternal response;
|
|
||||||
DN_NET2RequestInternal *next;
|
|
||||||
DN_NET2RequestInternal *prev;
|
|
||||||
|
|
||||||
// NOTE: Networking thread only
|
|
||||||
char context[sizeof(void *) * 2];
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DN_NET2RingEventType
|
|
||||||
{
|
|
||||||
DN_NET2RingEventType_Nil,
|
|
||||||
DN_NET2RingEventType_DoHTTP,
|
|
||||||
DN_NET2RingEventType_SendWS,
|
|
||||||
DN_NET2RingEventType_ReceivedWSReceipt,
|
|
||||||
DN_NET2RingEventType_DeinitRequest,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2RingEvent
|
|
||||||
{
|
|
||||||
DN_NET2RingEventType type;
|
|
||||||
DN_NET2Request request;
|
|
||||||
DN_USize send_ws_payload_size;
|
|
||||||
DN_NET2WSType send_ws_type;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DN_NET2Core
|
|
||||||
{
|
|
||||||
// NOTE: User thread only
|
|
||||||
char *base; // Backing memory for arena
|
|
||||||
DN_U64 base_size;
|
|
||||||
DN_Arena arena; // Allocates DN_NET2Request for appending to the user submission queue
|
|
||||||
|
|
||||||
// NOTE: Shared w/ user and networking thread
|
|
||||||
DN_Ring ring;
|
|
||||||
DN_OSMutex ring_mutex;
|
|
||||||
|
|
||||||
DN_OSSemaphore completion_sem;
|
|
||||||
DN_OSMutex free_or_done_mutex; // Lock for free list and done list
|
|
||||||
DN_NET2RequestInternal *done_list;
|
|
||||||
DN_NET2RequestInternal *free_list;
|
|
||||||
|
|
||||||
DN_OSThread curl_thread;
|
|
||||||
|
|
||||||
// NOTE: Networking thread only
|
|
||||||
void *curlm;
|
|
||||||
DN_NET2RequestInternal *http_list;
|
|
||||||
DN_NET2RequestInternal *ws_list;
|
|
||||||
};
|
|
||||||
|
|
||||||
static DN_NET2Response DN_NET2_WaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms);
|
|
||||||
static DN_NET2Response DN_NET2_WaitForResponse (DN_NET2Core *net, DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms);
|
|
||||||
static void DN_NET2_DeinitRequest (DN_NET2Core *net, DN_NET2Request *request);
|
|
||||||
static void DN_NET2_Init (DN_NET2Core *net, char *ring_base, DN_USize ring_size, char *base, DN_U64 base_size);
|
|
||||||
static DN_NET2Request DN_NET2_DoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args);
|
|
||||||
static DN_NET2Request DN_NET2_OpenWS (DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args);
|
|
||||||
static void DN_NET2_SendWS (DN_NET2Core *net, DN_NET2Request request, DN_Str8 payload, DN_NET2WSType type);
|
|
||||||
|
|
||||||
#endif // DN_NET2_CURL_H
|
|
||||||
@ -1,59 +1,62 @@
|
|||||||
#include "dn_net2.h"
|
#include "dn_net.h"
|
||||||
#include "dn_net_curl.h"
|
#include "dn_net_curl.h"
|
||||||
|
|
||||||
struct DN_NET2CurlConn
|
struct DN_NETCurlConn
|
||||||
{
|
{
|
||||||
void *curl;
|
void *curl;
|
||||||
struct curl_slist *curl_slist;
|
struct curl_slist *curl_slist;
|
||||||
char error[CURL_ERROR_SIZE];
|
char error[CURL_ERROR_SIZE];
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DN_NET2CurlRingEventType
|
enum DN_NETCurlRingEventType
|
||||||
{
|
{
|
||||||
DN_NET2CurlRingEventType_Nil,
|
DN_NETCurlRingEventType_Nil,
|
||||||
DN_NET2CurlRingEventType_DoRequest,
|
DN_NETCurlRingEventType_DoRequest,
|
||||||
DN_NET2CurlRingEventType_SendWS,
|
DN_NETCurlRingEventType_SendWS,
|
||||||
DN_NET2CurlRingEventType_ReceivedWSReceipt,
|
DN_NETCurlRingEventType_ReceivedWSReceipt,
|
||||||
DN_NET2CurlRingEventType_DeinitRequest,
|
DN_NETCurlRingEventType_DeinitRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DN_NET2CurlRingEvent
|
struct DN_NETCurlRingEvent
|
||||||
{
|
{
|
||||||
DN_NET2CurlRingEventType type;
|
DN_NETCurlRingEventType type;
|
||||||
DN_NET2Request request;
|
DN_NETRequest request;
|
||||||
DN_USize ws_send_size;
|
DN_USize ws_send_size;
|
||||||
DN_NET2WSSend ws_send;
|
DN_NETWSSend ws_send;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DN_NET2CurlCore
|
struct DN_NETCurlCore
|
||||||
{
|
{
|
||||||
// NOTE: Shared w/ user and networking thread
|
// NOTE: Shared w/ user and networking thread
|
||||||
DN_Ring ring;
|
DN_Ring ring;
|
||||||
DN_OSMutex ring_mutex;
|
DN_OSMutex ring_mutex;
|
||||||
|
bool kill_thread;
|
||||||
|
|
||||||
DN_OSMutex free_or_done_mutex; // Lock for free list and done list
|
DN_OSMutex list_mutex; // Lock for request, response, deinit, free list
|
||||||
DN_OSThread thread;
|
DN_NETRequestInternal *request_list; // Current requests submitted by the user thread awaiting to move into the thread request list
|
||||||
|
DN_NETRequestInternal *thread_request_list; // Current requests being executed by the CURL thread.
|
||||||
|
// This list is exclusively owned by the CURL thread so no locking is needed
|
||||||
|
DN_NETRequestInternal *response_list; // Finished requests that are to be deqeued by the user via wait for response
|
||||||
|
DN_NETRequestInternal *deinit_list; // Requests that are finished and are awaiting to be de-initialised by the CURL thread
|
||||||
|
DN_NETRequestInternal *free_list; // Request pool that new requests will use before allocating
|
||||||
|
|
||||||
// NOTE: Networking thread only
|
// NOTE: Networking thread only
|
||||||
void *curlm;
|
DN_OSThread thread;
|
||||||
DN_NET2RequestInternal *http_list;
|
void *thread_curlm;
|
||||||
DN_NET2RequestInternal *ws_list;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DN_NET2Interface DN_NET2_CurlInterface()
|
static bool DN_NET_CurlRequestIsInList(DN_NETRequestInternal const *first, DN_NETRequestInternal const *find)
|
||||||
{
|
{
|
||||||
DN_NET2Interface result = {};
|
bool result = false;
|
||||||
result.init = DN_NET2_CurlInit;
|
for (DN_NETRequestInternal const *it = first; !result && it; it = it->next)
|
||||||
result.do_http = DN_NET2_CurlDoHTTP;
|
result = find == it;
|
||||||
result.do_ws = DN_NET2_CurlDoWS;
|
|
||||||
result.do_ws_send = DN_NET2_CurlDoWSSend;
|
|
||||||
result.wait_for_response = DN_NET2_CurlWaitForResponse;
|
|
||||||
result.wait_for_any_response = DN_NET2_CurlWaitForAnyResponse;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DN_NET2_CurlMarkRequestDone_(DN_NET2Core *net, DN_NET2RequestInternal *request)
|
static void DN_NET_CurlMarkRequestDone_(DN_NETCore *net, DN_NETRequestInternal *request)
|
||||||
{
|
{
|
||||||
|
DN_Assert(request);
|
||||||
|
DN_Assert(net);
|
||||||
// NOTE: The done list in CURL is also used as a place to put websocket requests after removing it
|
// NOTE: The done list in CURL is also used as a place to put websocket requests after removing it
|
||||||
// from the 'ws_list'. By doing this we are stopping the CURL thread from receiving more data on
|
// from the 'ws_list'. By doing this we are stopping the CURL thread from receiving more data on
|
||||||
// the socket as that thread ticks the list of 'ws_list' sockets for data.
|
// the socket as that thread ticks the list of 'ws_list' sockets for data.
|
||||||
@ -62,16 +65,19 @@ static void DN_NET2_CurlMarkRequestDone_(DN_NET2Core *net, DN_NET2RequestInterna
|
|||||||
// into the 'ws_list' which then lets the CURL thread start receiving more data for that socket.
|
// into the 'ws_list' which then lets the CURL thread start receiving more data for that socket.
|
||||||
//
|
//
|
||||||
// Since CURL uses a background thread, we do this behind a mutex
|
// Since CURL uses a background thread, we do this behind a mutex
|
||||||
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *)net->context;
|
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *)net->context;
|
||||||
for (DN_OS_MutexScope(&curl->free_or_done_mutex))
|
for (DN_OS_MutexScope(&curl->list_mutex)) {
|
||||||
DN_DLList_Append(net->done_list, request);
|
DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request));
|
||||||
|
DN_DoublyLLDetach(curl->thread_request_list, request);
|
||||||
|
DN_DoublyLLAppend(curl->response_list, request);
|
||||||
|
}
|
||||||
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
|
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
|
||||||
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
|
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DN_USize DN_NET2_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data)
|
static DN_USize DN_NET_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data)
|
||||||
{
|
{
|
||||||
auto *request = DN_Cast(DN_NET2RequestInternal *) user_data;
|
auto *request = DN_Cast(DN_NETRequestInternal *) user_data;
|
||||||
DN_USize result = 0;
|
DN_USize result = 0;
|
||||||
DN_USize payload_size = size * count;
|
DN_USize payload_size = size * count;
|
||||||
if (DN_Str8BuilderAppendBytesCopy(&request->response.body, payload, payload_size))
|
if (DN_Str8BuilderAppendBytesCopy(&request->response.body, payload, payload_size))
|
||||||
@ -79,61 +85,46 @@ static DN_USize DN_NET2_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
|
static int32_t DN_NET_CurlThreadEntryPoint_(DN_OSThread *thread)
|
||||||
{
|
{
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) thread->user_context;
|
DN_NETCore *net = DN_Cast(DN_NETCore *) thread->user_context;
|
||||||
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context;
|
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
|
||||||
DN_OS_ThreadSetName(DN_Str8FromPtr(curl->thread.name.data, curl->thread.name.size));
|
DN_OS_ThreadSetName(DN_Str8FromPtr(curl->thread.name.data, curl->thread.name.size));
|
||||||
|
|
||||||
for (;;) {
|
while (!curl->kill_thread) {
|
||||||
DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr);
|
DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr);
|
||||||
|
|
||||||
|
// NOTE: Handle events sitting in the ring queue
|
||||||
for (bool dequeue_ring = true; dequeue_ring;) {
|
for (bool dequeue_ring = true; dequeue_ring;) {
|
||||||
// NOTE: Dequeue user request
|
DN_NETCurlRingEvent event = {};
|
||||||
DN_NET2CurlRingEvent event = {};
|
|
||||||
for (DN_OS_MutexScope(&curl->ring_mutex)) {
|
for (DN_OS_MutexScope(&curl->ring_mutex)) {
|
||||||
if (DN_Ring_HasData(&curl->ring, sizeof(event)))
|
if (DN_Ring_HasData(&curl->ring, sizeof(event)))
|
||||||
DN_Ring_Read(&curl->ring, &event, sizeof(event));
|
DN_Ring_Read(&curl->ring, &event, sizeof(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case DN_NET2CurlRingEventType_Nil: dequeue_ring = false; break;
|
case DN_NETCurlRingEventType_Nil: dequeue_ring = false; break;
|
||||||
|
|
||||||
case DN_NET2CurlRingEventType_DoRequest: {
|
case DN_NETCurlRingEventType_DoRequest: {
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *)event.request.handle;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *)event.request.handle;
|
||||||
DN_Assert(request->response.state != DN_NET2ResponseState_Error);
|
DN_Assert(request->response.state == DN_NETResponseState_Nil);
|
||||||
switch (request->type) {
|
DN_Assert(request->type != DN_NETRequestType_Nil);
|
||||||
case DN_NET2RequestType_Nil: {
|
|
||||||
DN_NET2_CurlMarkRequestDone_(net, request);
|
|
||||||
DN_InvalidCodePath;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DN_NET2RequestType_HTTP: {
|
// NOTE: Attach it to the CURL thread's request list
|
||||||
DN_Assert(request->response.state == DN_NET2ResponseState_Nil);
|
for (DN_OS_MutexScope(&curl->list_mutex)) {
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
|
DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, request));
|
||||||
CURLMcode multi_add = curl_multi_add_handle(curl->curlm, conn->curl);
|
DN_DoublyLLDetach(curl->request_list, request);
|
||||||
DN_Assert(multi_add == CURLM_OK);
|
|
||||||
DN_Assert(request->next == nullptr);
|
|
||||||
DN_Assert(request->prev == nullptr);
|
|
||||||
DN_DLList_Append(curl->http_list, request);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DN_NET2RequestType_WS: {
|
|
||||||
DN_Assert(request->response.state == DN_NET2ResponseState_Nil);
|
|
||||||
DN_Assert(request->next == nullptr);
|
|
||||||
DN_Assert(request->prev == nullptr);
|
|
||||||
if (request->response.state == DN_NET2ResponseState_Nil) {
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
|
|
||||||
CURLMcode multi_add = curl_multi_add_handle(curl->curlm, conn->curl);
|
|
||||||
DN_Assert(multi_add == CURLM_OK);
|
|
||||||
DN_DLList_Append(curl->http_list, request); // Open the WS connection
|
|
||||||
} else {
|
|
||||||
DN_DLList_Append(curl->ws_list, request); // Ready to send data through the WS connection
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
}
|
||||||
|
DN_DoublyLLAppend(curl->thread_request_list, request);
|
||||||
|
|
||||||
|
// NOTE: Add the connection to CURLM and start ticking it once we finish handling all the
|
||||||
|
// ring events
|
||||||
|
DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0];
|
||||||
|
CURLMcode multi_add = curl_multi_add_handle(curl->thread_curlm, conn->curl);
|
||||||
|
DN_Assert(multi_add == CURLM_OK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DN_NET2CurlRingEventType_SendWS: {
|
case DN_NETCurlRingEventType_SendWS: {
|
||||||
DN_Str8 payload = {};
|
DN_Str8 payload = {};
|
||||||
for (DN_OS_MutexScope(&curl->ring_mutex)) {
|
for (DN_OS_MutexScope(&curl->ring_mutex)) {
|
||||||
DN_Assert(DN_Ring_HasData(&curl->ring, event.ws_send_size));
|
DN_Assert(DN_Ring_HasData(&curl->ring, event.ws_send_size));
|
||||||
@ -143,56 +134,63 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
|
|||||||
|
|
||||||
DN_U32 curlws_flag = 0;
|
DN_U32 curlws_flag = 0;
|
||||||
switch (event.ws_send) {
|
switch (event.ws_send) {
|
||||||
case DN_NET2WSSend_Text: curlws_flag = CURLWS_TEXT; break;
|
case DN_NETWSSend_Text: curlws_flag = CURLWS_TEXT; break;
|
||||||
case DN_NET2WSSend_Binary: curlws_flag = CURLWS_BINARY; break;
|
case DN_NETWSSend_Binary: curlws_flag = CURLWS_BINARY; break;
|
||||||
case DN_NET2WSSend_Close: curlws_flag = CURLWS_CLOSE; break;
|
case DN_NETWSSend_Close: curlws_flag = CURLWS_CLOSE; break;
|
||||||
case DN_NET2WSSend_Ping: curlws_flag = CURLWS_PING; break;
|
case DN_NETWSSend_Ping: curlws_flag = CURLWS_PING; break;
|
||||||
case DN_NET2WSSend_Pong: curlws_flag = CURLWS_PONG; break;
|
case DN_NETWSSend_Pong: curlws_flag = CURLWS_PONG; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) event.request.handle;
|
||||||
DN_Assert(request->type == DN_NET2RequestType_WS);
|
DN_Assert(request->type == DN_NETRequestType_WS);
|
||||||
DN_Assert(request->response.state == DN_NET2ResponseState_WSOpen);
|
DN_Assert(request->response.state == DN_NETResponseState_WSOpen);
|
||||||
|
DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request));
|
||||||
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
|
DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0];
|
||||||
DN_USize sent = 0;
|
DN_USize sent = 0;
|
||||||
CURLcode send_result = curl_ws_send(conn->curl, payload.data, payload.size, &sent, 0, curlws_flag);
|
CURLcode send_result = curl_ws_send(conn->curl, payload.data, payload.size, &sent, 0, curlws_flag);
|
||||||
DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result));
|
DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result));
|
||||||
DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size);
|
DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DN_NET2CurlRingEventType_ReceivedWSReceipt: {
|
case DN_NETCurlRingEventType_ReceivedWSReceipt: {
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) event.request.handle;
|
||||||
DN_Assert(request->type == DN_NET2RequestType_WS);
|
DN_Assert(request->type == DN_NETRequestType_WS);
|
||||||
DN_Assert(request->response.state >= DN_NET2ResponseState_WSOpen && request->response.state <= DN_NET2ResponseState_WSPong);
|
DN_Assert(request->response.state >= DN_NETResponseState_WSOpen && request->response.state <= DN_NETResponseState_WSPong);
|
||||||
DN_Assert(request->next == nullptr);
|
request->response.state = DN_NETResponseState_WSOpen;
|
||||||
DN_Assert(request->prev == nullptr);
|
|
||||||
request->response.state = DN_NET2ResponseState_WSOpen;
|
for (DN_OS_MutexScope(&curl->list_mutex)) {
|
||||||
DN_DLList_Append(curl->ws_list, request);
|
DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, request));
|
||||||
|
DN_DoublyLLDetach(curl->request_list, request);
|
||||||
|
}
|
||||||
|
DN_DoublyLLAppend(curl->thread_request_list, request);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DN_NET2CurlRingEventType_DeinitRequest: {
|
case DN_NETCurlRingEventType_DeinitRequest: {
|
||||||
if (event.request.handle != 0) {
|
DN_Assert(event.request.handle != 0);
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) event.request.handle;
|
||||||
|
|
||||||
// NOTE: Release resources
|
// NOTE: Release resources
|
||||||
DN_ArenaClear(&request->arena);
|
DN_ArenaClear(&request->arena);
|
||||||
DN_OS_SemaphoreDeinit(&request->completion_sem);
|
DN_OS_SemaphoreDeinit(&request->completion_sem);
|
||||||
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
|
DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0];
|
||||||
curl_multi_remove_handle(curl->curlm, conn->curl);
|
curl_multi_remove_handle(curl->thread_curlm, conn->curl);
|
||||||
curl_easy_reset(conn->curl);
|
curl_easy_reset(conn->curl);
|
||||||
curl_slist_free_all(conn->curl_slist);
|
curl_slist_free_all(conn->curl_slist);
|
||||||
|
|
||||||
// NOTE: Zero the struct preserving just the data we need to retain
|
// NOTE: Zero the struct preserving just the data we need to retain
|
||||||
DN_NET2RequestInternal resetter = {};
|
DN_NETRequestInternal resetter = {};
|
||||||
resetter.arena = request->arena;
|
resetter.arena = request->arena;
|
||||||
resetter.gen = request->gen;
|
resetter.gen = request->gen;
|
||||||
DN_Memcpy(resetter.context, request->context, sizeof(resetter.context));
|
DN_Memcpy(resetter.context, request->context, sizeof(resetter.context));
|
||||||
*request = resetter;
|
*request = resetter;
|
||||||
|
|
||||||
for (DN_OS_MutexScope(&curl->free_or_done_mutex))
|
// NOTE: Add it to the free list
|
||||||
DN_DLList_Append(net->free_list, request);
|
for (DN_OS_MutexScope(&curl->list_mutex)) {
|
||||||
|
DN_Assert(DN_NET_CurlRequestIsInList(curl->deinit_list, request));
|
||||||
|
DN_DoublyLLDetach(curl->deinit_list, request);
|
||||||
|
DN_DoublyLLAppend(curl->free_list, request);
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
@ -200,40 +198,41 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
|
|||||||
|
|
||||||
// NOTE: Pump handles
|
// NOTE: Pump handles
|
||||||
int running_handles = 0;
|
int running_handles = 0;
|
||||||
CURLMcode perform_result = curl_multi_perform(curl->curlm, &running_handles);
|
CURLMcode perform_result = curl_multi_perform(curl->thread_curlm, &running_handles);
|
||||||
if (perform_result != CURLM_OK)
|
if (perform_result != CURLM_OK)
|
||||||
DN_InvalidCodePath;
|
DN_InvalidCodePath;
|
||||||
|
|
||||||
// NOTE: Check pump result
|
// NOTE: Check pump result
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int msgs_in_queue = 0;
|
int msgs_in_queue = 0;
|
||||||
CURLMsg *msg = curl_multi_info_read(curl->curlm, &msgs_in_queue);
|
CURLMsg *msg = curl_multi_info_read(curl->thread_curlm, &msgs_in_queue);
|
||||||
if (msg) {
|
if (msg) {
|
||||||
// NOTE: Get request handle
|
// NOTE: Get request handle
|
||||||
DN_NET2RequestInternal *request = nullptr;
|
DN_NETRequestInternal *request = nullptr;
|
||||||
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & request);
|
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & request);
|
||||||
DN_Assert(request);
|
DN_Assert(request);
|
||||||
|
DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request));
|
||||||
|
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *)request->context[0];
|
DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *)request->context[0];
|
||||||
DN_Assert(conn->curl == msg->easy_handle);
|
DN_Assert(conn->curl == msg->easy_handle);
|
||||||
|
|
||||||
if (msg->data.result == CURLE_OK) {
|
if (msg->data.result == CURLE_OK) {
|
||||||
// NOTE: Get HTTP response code
|
// NOTE: Get HTTP response code
|
||||||
CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &request->response.http_status);
|
CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &request->response.http_status);
|
||||||
if (get_result == CURLE_OK) {
|
if (get_result == CURLE_OK) {
|
||||||
if (request->type == DN_NET2RequestType_HTTP) {
|
if (request->type == DN_NETRequestType_HTTP) {
|
||||||
request->response.state = DN_NET2ResponseState_HTTP;
|
request->response.state = DN_NETResponseState_HTTP;
|
||||||
} else {
|
} else {
|
||||||
DN_Assert(request->type == DN_NET2RequestType_WS);
|
DN_Assert(request->type == DN_NETRequestType_WS);
|
||||||
request->response.state = DN_NET2ResponseState_WSOpen;
|
request->response.state = DN_NETResponseState_WSOpen;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result));
|
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result));
|
||||||
request->response.state = DN_NET2ResponseState_Error;
|
request->response.state = DN_NETResponseState_Error;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DN_USize curl_extended_error_size = DN_CStr8Size(conn->error);
|
DN_USize curl_extended_error_size = DN_CStr8Size(conn->error);
|
||||||
request->response.state = DN_NET2ResponseState_Error;
|
request->response.state = DN_NETResponseState_Error;
|
||||||
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena,
|
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena,
|
||||||
"HTTP request '%.*s' failed (CURL %d): %s%s%s%s",
|
"HTTP request '%.*s' failed (CURL %d): %s%s%s%s",
|
||||||
DN_Str8PrintFmt(request->url),
|
DN_Str8PrintFmt(request->url),
|
||||||
@ -244,7 +243,7 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
|
|||||||
curl_extended_error_size ? ")" : "");
|
curl_extended_error_size ? ")" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request->type == DN_NET2RequestType_HTTP || request->response.state == DN_NET2ResponseState_Error) {
|
if (request->type == DN_NETRequestType_HTTP || request->response.state == DN_NETResponseState_Error) {
|
||||||
// NOTE: Remove the request from the multi handle if we're a HTTP request
|
// NOTE: Remove the request from the multi handle if we're a HTTP request
|
||||||
// because it typically terminates the connection. In websockets the
|
// because it typically terminates the connection. In websockets the
|
||||||
// connection remains in the multi-handle to allow you to send and
|
// connection remains in the multi-handle to allow you to send and
|
||||||
@ -254,10 +253,10 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
|
|||||||
// connection from the multi handle as it failed. One a connection has
|
// connection from the multi handle as it failed. One a connection has
|
||||||
// failed, curl will not poll that connection so there's no point keeping
|
// failed, curl will not poll that connection so there's no point keeping
|
||||||
// it attached to the multi handle.
|
// it attached to the multi handle.
|
||||||
curl_multi_remove_handle(curl->curlm, msg->easy_handle);
|
curl_multi_remove_handle(curl->thread_curlm, msg->easy_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2_CurlMarkRequestDone_(net, request);
|
DN_NET_CurlMarkRequestDone_(net, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msgs_in_queue == 0)
|
if (msgs_in_queue == 0)
|
||||||
@ -265,41 +264,43 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Check websockets
|
// NOTE: Check websockets
|
||||||
DN_I32 sleep_time_ms = DN_DLList_HasItems(curl->ws_list) ? 100 : INT32_MAX;
|
DN_USize ws_count = 0;
|
||||||
for (DN_DLList_ForEach(request, curl->ws_list)) {
|
for (DN_NETRequestInternal *request = curl->thread_request_list; request; request = request->next) {
|
||||||
DN_Assert(request->type == DN_NET2RequestType_WS);
|
DN_Assert(request->type == DN_NETRequestType_WS || request->type == DN_NETRequestType_HTTP);
|
||||||
DN_Assert(request->response.state == DN_NET2ResponseState_WSOpen);
|
if (request->type != DN_NETRequestType_WS || !(request->response.state >= DN_NETResponseState_WSOpen && request->response.state <= DN_NETResponseState_WSPong))
|
||||||
CURLcode receive_result = CURLE_OK;
|
continue;
|
||||||
|
ws_count++;
|
||||||
const curl_ws_frame *meta = nullptr;
|
const curl_ws_frame *meta = nullptr;
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
|
DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0];
|
||||||
for (;;) {
|
CURLcode receive_result = CURLE_OK;
|
||||||
|
while (receive_result == CURLE_OK) {
|
||||||
// NOTE: Determine WS payload size received. Note that since we pass in a null pointer CURL
|
// NOTE: Determine WS payload size received. Note that since we pass in a null pointer CURL
|
||||||
// will set meta->len to 0 and say that there's meta->bytesleft in the next chunk.
|
// will set meta->len to 0 and say that there's meta->bytesleft in the next chunk.
|
||||||
DN_USize bytes_read = 0;
|
DN_USize bytes_read = 0;
|
||||||
receive_result = curl_ws_recv(conn->curl, nullptr, 0, &bytes_read, &meta);
|
receive_result = curl_ws_recv(conn->curl, nullptr, 0, &bytes_read, &meta);
|
||||||
if (receive_result != CURLE_OK)
|
if (receive_result != CURLE_OK)
|
||||||
break;
|
continue;
|
||||||
DN_Assert(meta->len == 0);
|
DN_Assert(meta->len == 0);
|
||||||
|
|
||||||
if (meta->flags & CURLWS_TEXT)
|
if (meta->flags & CURLWS_TEXT)
|
||||||
request->response.state = DN_NET2ResponseState_WSText;
|
request->response.state = DN_NETResponseState_WSText;
|
||||||
|
|
||||||
if (meta->flags & CURLWS_BINARY)
|
if (meta->flags & CURLWS_BINARY)
|
||||||
request->response.state = DN_NET2ResponseState_WSBinary;
|
request->response.state = DN_NETResponseState_WSBinary;
|
||||||
|
|
||||||
if (meta->flags & CURLWS_PING)
|
if (meta->flags & CURLWS_PING)
|
||||||
request->response.state = DN_NET2ResponseState_WSPing;
|
request->response.state = DN_NETResponseState_WSPing;
|
||||||
|
|
||||||
if (meta->flags & CURLWS_PONG)
|
if (meta->flags & CURLWS_PONG)
|
||||||
request->response.state = DN_NET2ResponseState_WSPong;
|
request->response.state = DN_NETResponseState_WSPong;
|
||||||
|
|
||||||
if (meta->flags & CURLWS_CLOSE)
|
if (meta->flags & CURLWS_CLOSE)
|
||||||
request->response.state = DN_NET2ResponseState_WSClose;
|
request->response.state = DN_NETResponseState_WSClose;
|
||||||
|
|
||||||
request->response.ws_has_more = meta->flags & CURLWS_CONT;
|
request->response.ws_has_more = meta->flags & CURLWS_CONT;
|
||||||
if (request->response.ws_has_more) {
|
if (request->response.ws_has_more) {
|
||||||
bool is_text_or_binary = request->response.state == DN_NET2ResponseState_WSText ||
|
bool is_text_or_binary = request->response.state == DN_NETResponseState_WSText ||
|
||||||
request->response.state == DN_NET2ResponseState_WSBinary;
|
request->response.state == DN_NETResponseState_WSBinary;
|
||||||
DN_Assert(is_text_or_binary);
|
DN_Assert(is_text_or_binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,22 +319,41 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
|
|||||||
// > If this is not a complete fragment, the bytesleft field informs about how many
|
// > If this is not a complete fragment, the bytesleft field informs about how many
|
||||||
// additional bytes are expected to arrive before this fragment is complete.
|
// additional bytes are expected to arrive before this fragment is complete.
|
||||||
request->response.ws_has_more |= meta && meta->bytesleft > 0;
|
request->response.ws_has_more |= meta && meta->bytesleft > 0;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We read all the possible bytes that CURL has received for this
|
// NOTE: curl_ws_recv returns CURLE_GOT_NOTHING if the associated connection is closed.
|
||||||
// socket, but, there are more bytes left that we will receive on subsequent
|
if (receive_result == CURLE_GOT_NOTHING)
|
||||||
// calls. We will continue to the next request and return back to this one
|
request->response.ws_has_more = false;
|
||||||
// when PumpRequests is called again where hopefully that data has arrived.
|
|
||||||
if (request->response.ws_has_more || receive_result == CURLE_AGAIN) {
|
// NOTE: We read all the possible bytes that CURL has received for this message, but, there are
|
||||||
if (receive_result == CURLE_AGAIN)
|
// more bytes left that we will receive on subsequent calls. We will continue to the next
|
||||||
sleep_time_ms = 100;
|
// request and return back to this one when PumpRequests is called again where hopefully that
|
||||||
|
// data has arrived.
|
||||||
|
if (request->response.ws_has_more)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (receive_result != CURLE_OK) {
|
// For CURLE_AGAIN
|
||||||
|
//
|
||||||
|
// > Instead of blocking, the function returns CURLE_AGAIN. The correct behavior is then to
|
||||||
|
// > wait for the socket to signal readability before calling this function again.
|
||||||
|
//
|
||||||
|
// In which case we continue ticking the other sockets and eventually exit once all ticked.
|
||||||
|
// Right after this we wait on the CURLM instance which will wake us up again when there's
|
||||||
|
// data to be read.
|
||||||
|
//
|
||||||
|
// if we received data, e.g. state was set to Text, Binary ... e.t.c we bypass this and
|
||||||
|
// report it to the user first. When the user waits for the response, they consume the data
|
||||||
|
// and then that will reinsert it into request list for CURL to read from the socket again.
|
||||||
|
bool received_data = (request->response.state >= DN_NETResponseState_WSText && request->response.state <= DN_NETResponseState_WSPong);
|
||||||
|
if (receive_result == CURLE_AGAIN && !received_data)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!received_data) {
|
||||||
|
if (receive_result == CURLE_GOT_NOTHING) {
|
||||||
|
request->response.state = DN_NETResponseState_WSClose;
|
||||||
|
} else if (receive_result != CURLE_OK) {
|
||||||
DN_USize curl_extended_error_size = DN_CStr8Size(conn->error);
|
DN_USize curl_extended_error_size = DN_CStr8Size(conn->error);
|
||||||
request->response.state = DN_NET2ResponseState_Error;
|
request->response.state = DN_NETResponseState_Error;
|
||||||
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena,
|
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena,
|
||||||
"Websocket receive '%.*s' failed (CURL %d): %s%s%s%s",
|
"Websocket receive '%.*s' failed (CURL %d): %s%s%s%s",
|
||||||
DN_Str8PrintFmt(request->url),
|
DN_Str8PrintFmt(request->url),
|
||||||
@ -343,22 +363,39 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
|
|||||||
curl_extended_error_size ? conn->error : "",
|
curl_extended_error_size ? conn->error : "",
|
||||||
curl_extended_error_size ? ")" : "");
|
curl_extended_error_size ? ")" : "");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DN_NET2RequestInternal *request_copy = request;
|
DN_NETRequestInternal *request_copy = request;
|
||||||
request = request->prev;
|
request = request->prev;
|
||||||
DN_NET2_CurlMarkRequestDone_(net, request_copy);
|
DN_NET_CurlMarkRequestDone_(net, request_copy);
|
||||||
|
if (!request)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
curl_multi_poll(curl->curlm, nullptr, 0, sleep_time_ms, nullptr);
|
DN_I32 sleep_time_ms = ws_count > 0 ? 16 : INT32_MAX;
|
||||||
|
curl_multi_poll(curl->thread_curlm, nullptr, 0, sleep_time_ms, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DN_NETInterface DN_NET_CurlInterface()
|
||||||
void DN_NET2_CurlInit(DN_NET2Core *net, char *base, DN_U64 base_size)
|
|
||||||
{
|
{
|
||||||
DN_NET2_BaseInit_(net, base, base_size);
|
DN_NETInterface result = {};
|
||||||
DN_NET2CurlCore *curl = DN_ArenaNew(&net->arena, DN_NET2CurlCore, DN_ZMem_Yes);
|
result.init = DN_NET_CurlInit;
|
||||||
|
result.deinit = DN_NET_CurlDeinit;
|
||||||
|
result.do_http = DN_NET_CurlDoHTTP;
|
||||||
|
result.do_ws = DN_NET_CurlDoWS;
|
||||||
|
result.do_ws_send = DN_NET_CurlDoWSSend;
|
||||||
|
result.wait_for_response = DN_NET_CurlWaitForResponse;
|
||||||
|
result.wait_for_any_response = DN_NET_CurlWaitForAnyResponse;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DN_NET_CurlInit(DN_NETCore *net, char *base, DN_U64 base_size)
|
||||||
|
{
|
||||||
|
DN_NET_BaseInit_(net, base, base_size);
|
||||||
|
DN_NETCurlCore *curl = DN_ArenaNew(&net->arena, DN_NETCurlCore, DN_ZMem_Yes);
|
||||||
net->context = curl;
|
net->context = curl;
|
||||||
|
|
||||||
DN_USize arena_bytes_avail = (net->arena.curr->reserve - net->arena.curr->used);
|
DN_USize arena_bytes_avail = (net->arena.curr->reserve - net->arena.curr->used);
|
||||||
@ -367,36 +404,40 @@ void DN_NET2_CurlInit(DN_NET2Core *net, char *base, DN_U64 base_size)
|
|||||||
DN_Assert(curl->ring.base);
|
DN_Assert(curl->ring.base);
|
||||||
|
|
||||||
curl->ring_mutex = DN_OS_MutexInit();
|
curl->ring_mutex = DN_OS_MutexInit();
|
||||||
curl->free_or_done_mutex = DN_OS_MutexInit();
|
curl->list_mutex = DN_OS_MutexInit();
|
||||||
curl->curlm = DN_Cast(CURLM *) curl_multi_init();
|
curl->thread_curlm = DN_Cast(CURLM *) curl_multi_init();
|
||||||
|
|
||||||
// TODO: Free list should be initialised in the base
|
|
||||||
DN_DLList_InitArena(net->free_list, DN_NET2RequestInternal, &net->arena);
|
|
||||||
DN_DLList_InitArena(net->done_list, DN_NET2RequestInternal, &net->arena);
|
|
||||||
DN_DLList_InitArena(curl->http_list, DN_NET2RequestInternal, &net->arena);
|
|
||||||
DN_DLList_InitArena(curl->ws_list, DN_NET2RequestInternal, &net->arena);
|
|
||||||
|
|
||||||
DN_FmtAppend(curl->thread.name.data, &curl->thread.name.size, sizeof(curl->thread.name.data), "NET (CURL)");
|
DN_FmtAppend(curl->thread.name.data, &curl->thread.name.size, sizeof(curl->thread.name.data), "NET (CURL)");
|
||||||
DN_OS_ThreadInit(&curl->thread, DN_NET2_CurlThreadEntryPoint_, net);
|
DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, net);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type)
|
void DN_NET_CurlDeinit(DN_NETCore *net)
|
||||||
|
{
|
||||||
|
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
|
||||||
|
curl->kill_thread = true;
|
||||||
|
curl_multi_wakeup(curl->thread_curlm);
|
||||||
|
DN_OS_ThreadDeinit(&curl->thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DN_NETRequest DN_NET_CurlDoRequest_(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type)
|
||||||
{
|
{
|
||||||
// NOTE: Allocate the request
|
// NOTE: Allocate the request
|
||||||
DN_NET2CurlCore *curl_core = DN_Cast(DN_NET2CurlCore *) net->context;
|
DN_NETCurlCore *curl_core = DN_Cast(DN_NETCurlCore *) net->context;
|
||||||
DN_NET2RequestInternal *request = nullptr;
|
DN_NETRequestInternal *request = nullptr;
|
||||||
DN_NET2Request result = {};
|
DN_NETRequest result = {};
|
||||||
{
|
{
|
||||||
// NOTE: The free list is modified by both the calling thread and the CURLM thread (which ticks
|
// NOTE: The free list is modified by both the calling thread and the CURLM thread (which ticks
|
||||||
// all the requests in the background for us)
|
// all the requests in the background for us)
|
||||||
for (DN_OS_MutexScope(&curl_core->free_or_done_mutex))
|
for (DN_OS_MutexScope(&curl_core->list_mutex)) {
|
||||||
DN_DLList_Dequeue(net->free_list, request);
|
request = curl_core->free_list;
|
||||||
|
DN_DoublyLLDetach(curl_core->free_list, request);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE None if the free list so allocate one
|
// NOTE None in the free list so allocate one
|
||||||
if (!request) {
|
if (!request) {
|
||||||
DN_U64 arena_pos = DN_ArenaPos(&net->arena);
|
DN_U64 arena_pos = DN_ArenaPos(&net->arena);
|
||||||
request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes);
|
request = DN_ArenaNew(&net->arena, DN_NETRequestInternal, DN_ZMem_Yes);
|
||||||
DN_NET2CurlConn *conn = DN_ArenaNew(&net->arena, DN_NET2CurlConn, DN_ZMem_Yes);
|
DN_NETCurlConn *conn = DN_ArenaNew(&net->arena, DN_NETCurlConn, DN_ZMem_Yes);
|
||||||
if (!request || !conn) {
|
if (!request || !conn) {
|
||||||
DN_ArenaPopTo(&net->arena, arena_pos);
|
DN_ArenaPopTo(&net->arena, arena_pos);
|
||||||
return result;
|
return result;
|
||||||
@ -408,12 +449,12 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Setup the request
|
// NOTE: Setup the request
|
||||||
result = DN_NET2_SetupRequest_(request, url, method, args, type);
|
result = DN_NET_SetupRequest_(request, url, method, args, type);
|
||||||
request->context[1] = DN_Cast(DN_UPtr) net;
|
request->context[1] = DN_Cast(DN_UPtr) net;
|
||||||
|
|
||||||
// NOTE: Setup the request for curl
|
// NOTE: Setup the request for curl
|
||||||
{
|
{
|
||||||
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
|
DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0];
|
||||||
CURL *curl = conn->curl;
|
CURL *curl = conn->curl;
|
||||||
curl_easy_setopt(curl, CURLOPT_PRIVATE, request);
|
curl_easy_setopt(curl, CURLOPT_PRIVATE, request);
|
||||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, conn->error);
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, conn->error);
|
||||||
@ -424,7 +465,7 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S
|
|||||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
|
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
|
||||||
|
|
||||||
// NOTE: Setup response handler
|
// NOTE: Setup response handler
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET2_CurlHTTPCallback_);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET_CurlHTTPCallback_);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, request);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, request);
|
||||||
|
|
||||||
// NOTE: Assign HTTP headers
|
// NOTE: Assign HTTP headers
|
||||||
@ -434,13 +475,13 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S
|
|||||||
|
|
||||||
// NOTE: Setup handle for protocol
|
// NOTE: Setup handle for protocol
|
||||||
switch (request->type) {
|
switch (request->type) {
|
||||||
case DN_NET2RequestType_Nil: DN_InvalidCodePath; break;
|
case DN_NETRequestType_Nil: DN_InvalidCodePath; break;
|
||||||
|
|
||||||
case DN_NET2RequestType_WS: {
|
case DN_NETRequestType_WS: {
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
|
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DN_NET2RequestType_HTTP: {
|
case DN_NETRequestType_HTTP: {
|
||||||
DN_Str8 const GET = DN_Str8Lit("GET");
|
DN_Str8 const GET = DN_Str8Lit("GET");
|
||||||
DN_Str8 const POST = DN_Str8Lit("POST");
|
DN_Str8 const POST = DN_Str8Lit("POST");
|
||||||
|
|
||||||
@ -460,7 +501,7 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Handle basic auth
|
// NOTE: Handle basic auth
|
||||||
if (request->args.flags & DN_NET2DoHTTPFlags_BasicAuth) {
|
if (request->args.flags & DN_NETDoHTTPFlags_BasicAuth) {
|
||||||
if (request->args.username.size && request->args.password.size) {
|
if (request->args.username.size && request->args.password.size) {
|
||||||
DN_Assert(request->args.username.data[request->args.username.size] == 0);
|
DN_Assert(request->args.username.data[request->args.username.size] == 0);
|
||||||
DN_Assert(request->args.password.data[request->args.password.size] == 0);
|
DN_Assert(request->args.password.data[request->args.password.size] == 0);
|
||||||
@ -470,50 +511,59 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Dispatch the request to the CURL thread. It will append to the CURLM
|
// NOTE: Dispatch the request to the CURL thread
|
||||||
// instance and tick it in the background for us
|
|
||||||
{
|
{
|
||||||
DN_NET2CurlRingEvent event = {};
|
// NOTE: Immediately add the request to the request list so it happens "atomically" in the
|
||||||
event.type = DN_NET2CurlRingEventType_DoRequest;
|
// calling thread. If the calling thread deinitialises this layer before the CURL thread can be
|
||||||
|
// pre-empted, we can lose track of this request.
|
||||||
|
for (DN_OS_MutexScope(&curl_core->list_mutex))
|
||||||
|
DN_DoublyLLAppend(curl_core->request_list, request);
|
||||||
|
|
||||||
|
// NOTE: Enqueue request to go into CURL's ring queue. The CURL thread will sleep and wait for
|
||||||
|
// bytes to come in for the request and then dump the response into the done list to be consumed
|
||||||
|
// via wait for response
|
||||||
|
DN_NETCurlRingEvent event = {};
|
||||||
|
event.type = DN_NETCurlRingEventType_DoRequest;
|
||||||
event.request = result;
|
event.request = result;
|
||||||
for (DN_OS_MutexScope(&curl_core->ring_mutex))
|
for (DN_OS_MutexScope(&curl_core->ring_mutex))
|
||||||
DN_Ring_WriteStruct(&curl_core->ring, &event);
|
DN_Ring_WriteStruct(&curl_core->ring, &event);
|
||||||
curl_multi_wakeup(curl_core->curlm);
|
|
||||||
|
curl_multi_wakeup(curl_core->thread_curlm);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Request DN_NET2_CurlDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args)
|
DN_NETRequest DN_NET_CurlDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args)
|
||||||
{
|
{
|
||||||
DN_NET2Request result = DN_NET2_CurlDoRequest_(net, url, method, args, DN_NET2RequestType_HTTP);
|
DN_NETRequest result = DN_NET_CurlDoRequest_(net, url, method, args, DN_NETRequestType_HTTP);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Request DN_NET2_CurlDoWSArgs(DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args)
|
DN_NETRequest DN_NET_CurlDoWSArgs(DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args)
|
||||||
{
|
{
|
||||||
DN_NET2Request result = DN_NET2_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NET2RequestType_WS);
|
DN_NETRequest result = DN_NET_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NETRequestType_WS);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Request DN_NET2_CurlDoWS(DN_NET2Core *net, DN_Str8 url)
|
DN_NETRequest DN_NET_CurlDoWS(DN_NETCore *net, DN_Str8 url)
|
||||||
{
|
{
|
||||||
DN_NET2Request result = DN_NET2_CurlDoWSArgs(net, url, nullptr);
|
DN_NETRequest result = DN_NET_CurlDoWSArgs(net, url, nullptr);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DN_NET2_CurlDoWSSend(DN_NET2Request request, DN_Str8 payload, DN_NET2WSSend send)
|
void DN_NET_CurlDoWSSend(DN_NETRequest request, DN_Str8 payload, DN_NETWSSend send)
|
||||||
{
|
{
|
||||||
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
|
DN_NETRequestInternal *request_ptr = DN_NET_RequestFromHandle(request);
|
||||||
if (!request_ptr || request_ptr->gen != request.gen)
|
if (!request_ptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[1];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[1];
|
||||||
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context;
|
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
|
||||||
DN_Assert(curl);
|
DN_Assert(curl);
|
||||||
|
|
||||||
DN_NET2CurlRingEvent event = {};
|
DN_NETCurlRingEvent event = {};
|
||||||
event.type = DN_NET2CurlRingEventType_SendWS;
|
event.type = DN_NETCurlRingEventType_SendWS;
|
||||||
event.request = request;
|
event.request = request;
|
||||||
event.ws_send_size = payload.size;
|
event.ws_send_size = payload.size;
|
||||||
event.ws_send = send;
|
event.ws_send = send;
|
||||||
@ -523,116 +573,107 @@ void DN_NET2_CurlDoWSSend(DN_NET2Request request, DN_Str8 payload, DN_NET2WSSend
|
|||||||
DN_Ring_WriteStruct(&curl->ring, &event);
|
DN_Ring_WriteStruct(&curl->ring, &event);
|
||||||
DN_Ring_Write(&curl->ring, payload.data, payload.size);
|
DN_Ring_Write(&curl->ring, payload.data, payload.size);
|
||||||
}
|
}
|
||||||
curl_multi_wakeup(curl->curlm);
|
curl_multi_wakeup(curl->thread_curlm);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DN_NET2Response DN_NET2_CurlHandleFinishedRequest_(DN_NET2Core *net, DN_NET2CurlCore *curl, DN_NET2Request request, DN_NET2RequestInternal *request_ptr, DN_Arena *arena)
|
static DN_NETResponse DN_NET_CurlHandleFinishedRequest_(DN_NETCurlCore *curl, DN_NETRequest request, DN_NETRequestInternal *request_ptr, DN_Arena *arena)
|
||||||
{
|
{
|
||||||
// NOTE: Process the response
|
// NOTE: Process the response
|
||||||
DN_NET2Response result = DN_NET2_MakeResponseFromFinishedRequest_(request, arena);
|
DN_NETResponse result = DN_NET_MakeResponseFromFinishedRequest_(request, arena);
|
||||||
DN_NET2_EndFinishedRequest_(net, request_ptr);
|
DN_NET_EndFinishedRequest_(request_ptr);
|
||||||
|
|
||||||
// NOTE: For websocket requests, notify the CURL thread we've read data from it and it can go
|
bool continue_ws_request = false;
|
||||||
// back to polling the socket for more data. Over on the CURL thread when it receives the event
|
if (request_ptr->type == DN_NETRequestType_WS &&
|
||||||
// it will append the request onto the 'ws_list' which it iterates to poll for data.
|
request_ptr->response.state != DN_NETResponseState_Error &&
|
||||||
//
|
request_ptr->response.state != DN_NETResponseState_WSClose) {
|
||||||
// The request was _just_ sitting in the done list (see above) so it was not being polled but
|
continue_ws_request = true;
|
||||||
// it will be polled after this step.
|
|
||||||
//
|
|
||||||
// TODO: How do we unsticky the error? Right now we just deallocate/close the request entirely and move on
|
|
||||||
bool end_the_request = true;
|
|
||||||
if (request_ptr->type == DN_NET2RequestType_WS &&
|
|
||||||
request_ptr->response.state != DN_NET2ResponseState_Error &&
|
|
||||||
request_ptr->response.state != DN_NET2ResponseState_WSClose) {
|
|
||||||
DN_NET2CurlRingEvent event = {};
|
|
||||||
event.type = DN_NET2CurlRingEventType_ReceivedWSReceipt;
|
|
||||||
event.request = request;
|
|
||||||
for (DN_OS_MutexScope(&curl->ring_mutex))
|
|
||||||
DN_Ring_WriteStruct(&curl->ring, &event);
|
|
||||||
curl_multi_wakeup(curl->curlm);
|
|
||||||
end_the_request = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end_the_request) {
|
// NOTE: Put the request into the requisite list
|
||||||
// NOTE: This _has_ to be sent to our CURL thread because we need to remove the CURL handle from
|
for (DN_OS_MutexScope(&curl->list_mutex)) {
|
||||||
// the CURLM instance and the CURL thread uses the CURLM instance (e.g. not thread safe to do
|
// NOTE: Dequeue the request, it _must_ have been in the response list at this point for it to
|
||||||
// here)
|
// have ben waitable in the first place.
|
||||||
DN_NET2CurlRingEvent event = {};
|
DN_AssertF(DN_NET_CurlRequestIsInList(curl->response_list, request_ptr),
|
||||||
event.type = DN_NET2CurlRingEventType_DeinitRequest;
|
"A completed response should only signal the completion semaphore when it's in the response list");
|
||||||
|
DN_DoublyLLDetach(curl->response_list, request_ptr);
|
||||||
|
|
||||||
|
// NOTE: A websocket that is continuing to get data should go back into the request list because
|
||||||
|
// there's more data to be received. All other requests need to go into the deinit list (so that
|
||||||
|
// we keep track of it in the time inbetween it takes for the CURL thread to be scheduled and
|
||||||
|
// release the CURL handle from CURLM and release resources e.t.c.)
|
||||||
|
if (continue_ws_request)
|
||||||
|
DN_DoublyLLAppend(curl->request_list, request_ptr);
|
||||||
|
else
|
||||||
|
DN_DoublyLLAppend(curl->deinit_list, request_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Submit the post-request event to the CURL thread
|
||||||
|
DN_NETCurlRingEvent event = {};
|
||||||
event.request = request;
|
event.request = request;
|
||||||
|
if (continue_ws_request) {
|
||||||
|
event.type = DN_NETCurlRingEventType_ReceivedWSReceipt;
|
||||||
|
} else {
|
||||||
|
// NOTE: Deinit _has_ to be sent to the CURL thread because we need to remove the CURL handle
|
||||||
|
// from the CURLM instance and the CURL thread uses the CURLM instance (e.g. CURLM is not thread
|
||||||
|
// safe)
|
||||||
|
event.type = DN_NETCurlRingEventType_DeinitRequest;
|
||||||
|
}
|
||||||
|
|
||||||
for (DN_OS_MutexScope(&curl->ring_mutex))
|
for (DN_OS_MutexScope(&curl->ring_mutex))
|
||||||
DN_Ring_WriteStruct(&curl->ring, &event);
|
DN_Ring_WriteStruct(&curl->ring, &event);
|
||||||
}
|
curl_multi_wakeup(curl->thread_curlm);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Response DN_NET2_CurlWaitForResponse(DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms)
|
DN_NETResponse DN_NET_CurlWaitForResponse(DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms)
|
||||||
{
|
{
|
||||||
DN_NET2Response result = {};
|
DN_NETResponse result = {};
|
||||||
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
|
DN_NETRequestInternal *request_ptr = DN_NET_RequestFromHandle(request);
|
||||||
if (!request_ptr || request_ptr->gen != request.gen)
|
if (!request_ptr)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[1];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[1];
|
||||||
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context;
|
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
|
||||||
DN_Assert(curl);
|
DN_Assert(curl);
|
||||||
|
|
||||||
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&request_ptr->completion_sem, timeout_ms);
|
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&request_ptr->completion_sem, timeout_ms);
|
||||||
if (wait != DN_OSSemaphoreWaitResult_Success)
|
if (wait != DN_OSSemaphoreWaitResult_Success)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// NOTE: Remove the request from the done list. Note it _has_ to be in the done list, anything
|
|
||||||
// sitting in the done list is guaranteed to not be modified by the CURL thread.
|
|
||||||
for (DN_OS_MutexScope(&curl->free_or_done_mutex)) {
|
|
||||||
bool is_in_done_list = false;
|
|
||||||
for (DN_DLList_ForEach(it, net->done_list)) {
|
|
||||||
if (it == request_ptr) {
|
|
||||||
is_in_done_list = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DN_AssertF(is_in_done_list, "Any request that has signalled completion should be inserted into the done list");
|
|
||||||
DN_DLList_Detach(request_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Finish handling the response
|
|
||||||
result = DN_NET2_CurlHandleFinishedRequest_(net, curl, request, request_ptr, arena);
|
|
||||||
|
|
||||||
// NOTE: Decrement the global 'request done' completion semaphore since the user consumed the
|
// NOTE: Decrement the global 'request done' completion semaphore since the user consumed the
|
||||||
// request individually.
|
// request individually.
|
||||||
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/);
|
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/);
|
||||||
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
|
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
|
||||||
|
|
||||||
|
// NOTE: Finish handling the response
|
||||||
|
result = DN_NET_CurlHandleFinishedRequest_(curl, request, request_ptr, arena);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Response DN_NET2_CurlWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms)
|
DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms)
|
||||||
{
|
{
|
||||||
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context;
|
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
|
||||||
DN_Assert(curl);
|
DN_Assert(curl);
|
||||||
|
|
||||||
DN_NET2Response result = {};
|
DN_NETResponse result = {};
|
||||||
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms);
|
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms);
|
||||||
if (wait != DN_OSSemaphoreWaitResult_Success)
|
if (wait != DN_OSSemaphoreWaitResult_Success)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// NOTE: Dequeue the request that is done from the done list
|
// NOTE: Just grab the handle, handle finished request will dequeue for us
|
||||||
DN_NET2RequestInternal *request_ptr = nullptr;
|
DN_NETRequest request = {};
|
||||||
for (DN_OS_MutexScope(&curl->free_or_done_mutex))
|
for (DN_OS_MutexScope(&curl->list_mutex)) {
|
||||||
DN_DLList_Dequeue(net->done_list, request_ptr);
|
DN_Assert(curl->response_list);
|
||||||
DN_Assert(request_ptr);
|
request = DN_NET_HandleFromRequest(curl->response_list);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore
|
// NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore
|
||||||
|
DN_NETRequestInternal *request_ptr = DN_NET_RequestFromHandle(request);
|
||||||
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/);
|
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/);
|
||||||
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
|
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
|
||||||
|
|
||||||
// NOTE: Finish handling the response
|
// NOTE: Finish handling the response
|
||||||
DN_NET2Request request = {};
|
result = DN_NET_CurlHandleFinishedRequest_(curl, request, request_ptr, arena);
|
||||||
request.handle = DN_Cast(DN_UPtr) request_ptr;
|
|
||||||
request.gen = request_ptr->gen;
|
|
||||||
result = DN_NET2_CurlHandleFinishedRequest_(net, curl, request, request_ptr, arena);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
#if !defined(DN_NET_CURL_H)
|
#if !defined(DN_NET_CURL_H)
|
||||||
#define DN_NET_CURL_H
|
#define DN_NET_CURL_H
|
||||||
|
|
||||||
#include "dn_net2.h"
|
#include "dn_net.h"
|
||||||
|
|
||||||
DN_NET2Interface DN_NET2_CurlInterface();
|
DN_NETInterface DN_NET_CurlInterface();
|
||||||
void DN_NET2_CurlInit (DN_NET2Core *net, char *base, DN_U64 base_size);
|
void DN_NET_CurlInit (DN_NETCore *net, char *base, DN_U64 base_size);
|
||||||
DN_NET2Request DN_NET2_CurlDoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args);
|
void DN_NET_CurlDeinit (DN_NETCore *net);
|
||||||
DN_NET2Request DN_NET2_CurlDoWSArgs (DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args);
|
DN_NETRequest DN_NET_CurlDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args);
|
||||||
DN_NET2Request DN_NET2_CurlDoWS (DN_NET2Core *net, DN_Str8 url);
|
DN_NETRequest DN_NET_CurlDoWSArgs (DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args);
|
||||||
void DN_NET2_CurlDoWSSend (DN_NET2Request request, DN_Str8 payload, DN_NET2WSSend send);
|
DN_NETRequest DN_NET_CurlDoWS (DN_NETCore *net, DN_Str8 url);
|
||||||
DN_NET2Response DN_NET2_CurlWaitForResponse (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms);
|
void DN_NET_CurlDoWSSend (DN_NETRequest request, DN_Str8 payload, DN_NETWSSend send);
|
||||||
DN_NET2Response DN_NET2_CurlWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms);
|
DN_NETResponse DN_NET_CurlWaitForResponse (DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms);
|
||||||
|
DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms);
|
||||||
|
|
||||||
#endif // !defined(DN_NET_CURL_H)
|
#endif // !defined(DN_NET_CURL_H)
|
||||||
|
|||||||
@ -2,45 +2,49 @@
|
|||||||
#include <emscripten/fetch.h>
|
#include <emscripten/fetch.h>
|
||||||
#include <emscripten/websocket.h>
|
#include <emscripten/websocket.h>
|
||||||
|
|
||||||
#include "dn_net2.h"
|
#include "dn_net.h"
|
||||||
#include "dn_net_emscripten.h"
|
#include "dn_net_emscripten.h"
|
||||||
|
|
||||||
struct DN_NET2EmcWSEvent
|
struct DN_NETEmcWSEvent
|
||||||
{
|
{
|
||||||
DN_NET2ResponseState state;
|
DN_NETResponseState state;
|
||||||
DN_Str8 payload;
|
DN_Str8 payload;
|
||||||
DN_NET2EmcWSEvent *next;
|
DN_NETEmcWSEvent *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DN_NET2EmcCore
|
struct DN_NETEmcCore
|
||||||
{
|
{
|
||||||
DN_Pool pool;
|
DN_Pool pool;
|
||||||
|
DN_NETRequestInternal *request_list; // Current requests being executed
|
||||||
|
DN_NETRequestInternal *response_list; // Responses received that are to be deqeued via wait for response
|
||||||
|
DN_NETRequestInternal *free_list; // Request pool that new requests will use before allocating
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DN_NET2EmcRequest
|
struct DN_NETEmcRequest
|
||||||
{
|
{
|
||||||
int socket;
|
int socket;
|
||||||
DN_NET2EmcWSEvent *first_event;
|
DN_NETEmcWSEvent *first_event;
|
||||||
DN_NET2EmcWSEvent *last_event;
|
DN_NETEmcWSEvent *last_event;
|
||||||
};
|
};
|
||||||
|
|
||||||
DN_NET2Interface DN_NET2_EmcInterface()
|
DN_NETInterface DN_NET_EmcInterface()
|
||||||
{
|
{
|
||||||
DN_NET2Interface result = {};
|
DN_NETInterface result = {};
|
||||||
result.init = DN_NET2_EmcInit;
|
result.init = DN_NET_EmcInit;
|
||||||
result.do_http = DN_NET2_EmcDoHTTP;
|
result.deinit = DN_NET_EmcDeinit;
|
||||||
result.do_ws = DN_NET2_EmcDoWS;
|
result.do_http = DN_NET_EmcDoHTTP;
|
||||||
result.do_ws_send = DN_NET2_EmcDoWSSend;
|
result.do_ws = DN_NET_EmcDoWS;
|
||||||
result.wait_for_response = DN_NET2_EmcWaitForResponse;
|
result.do_ws_send = DN_NET_EmcDoWSSend;
|
||||||
result.wait_for_any_response = DN_NET2_EmcWaitForAnyResponse;
|
result.wait_for_response = DN_NET_EmcWaitForResponse;
|
||||||
|
result.wait_for_any_response = DN_NET_EmcWaitForAnyResponse;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DN_NET2EmcWSEvent *DN_NET2_EmcAllocWSEvent_(DN_NET2RequestInternal *request)
|
static DN_NETEmcWSEvent *DN_NET_EmcAllocWSEvent_(DN_NETRequestInternal *request)
|
||||||
{
|
{
|
||||||
// NOTE: Allocate the event and attach to the request
|
// NOTE: Allocate the event and attach to the request
|
||||||
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request->context[1];
|
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1];
|
||||||
DN_NET2EmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NET2EmcWSEvent, DN_ZMem_Yes);
|
DN_NETEmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NETEmcWSEvent, DN_ZMem_Yes);
|
||||||
if (result) {
|
if (result) {
|
||||||
if (!emc_request->first_event)
|
if (!emc_request->first_event)
|
||||||
emc_request->first_event = result;
|
emc_request->first_event = result;
|
||||||
@ -51,110 +55,116 @@ static DN_NET2EmcWSEvent *DN_NET2_EmcAllocWSEvent_(DN_NET2RequestInternal *reque
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DN_NET2_EmcOnRequestDone_(DN_NET2Core *net, DN_NET2RequestInternal *request)
|
static void DN_NET_EmcOnRequestDone_(DN_NETCore *net, DN_NETRequestInternal *request)
|
||||||
{
|
{
|
||||||
// NOTE: This may be call multiple times if we get multiple responses when we yield to the javascript event loop
|
// NOTE: This may be call multiple times if we get multiple responses when we yield to the javascript event loop
|
||||||
if (!request->next) {
|
if (!request->next) {
|
||||||
request->next = net->done_list;
|
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
|
||||||
net->done_list = request;
|
request->next = emc->response_list;
|
||||||
|
emc->response_list = request;
|
||||||
}
|
}
|
||||||
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
|
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
|
||||||
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
|
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Need to enqueue the results since they can accumulate when you yield to the javascript event loop
|
// TODO: Need to enqueue the results since they can accumulate when you yield to the javascript event loop
|
||||||
static bool DN_NET2_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data)
|
static bool DN_NET_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data)
|
||||||
{
|
{
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) user_data;
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0];
|
||||||
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
|
DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(request);
|
||||||
DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request);
|
net_event->state = DN_NETResponseState_WSOpen;
|
||||||
net_event->state = DN_NET2ResponseState_WSOpen;
|
DN_NET_EmcOnRequestDone_(net, request);
|
||||||
DN_NET2_EmcOnRequestDone_(net, request);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool DN_NET2_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data)
|
static bool DN_NET_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data)
|
||||||
{
|
{
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) user_data;
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0];
|
||||||
DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request);
|
DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(request);
|
||||||
net_event->state = event->isText ? DN_NET2ResponseState_WSText : DN_NET2ResponseState_WSBinary;
|
net_event->state = event->isText ? DN_NETResponseState_WSText : DN_NETResponseState_WSBinary;
|
||||||
if (event->numBytes > 0) {
|
if (event->numBytes > 0) {
|
||||||
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
|
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
|
||||||
net_event->payload = DN_Str8FromPtrPool(&emc->pool, event->data, event->numBytes);
|
net_event->payload = DN_Str8FromPtrPool(&emc->pool, event->data, event->numBytes);
|
||||||
}
|
}
|
||||||
DN_NET2_EmcOnRequestDone_(net, request);
|
DN_NET_EmcOnRequestDone_(net, request);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool DN_NET2_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data)
|
static bool DN_NET_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data)
|
||||||
{
|
{
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) user_data;
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0];
|
||||||
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
|
DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(request);
|
||||||
DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request);
|
net_event->state = DN_NETResponseState_Error;
|
||||||
net_event->state = DN_NET2ResponseState_Error;
|
DN_NET_EmcOnRequestDone_(net, request);
|
||||||
DN_NET2_EmcOnRequestDone_(net, request);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool DN_NET2_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data)
|
static bool DN_NET_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data)
|
||||||
{
|
{
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) user_data;
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0];
|
||||||
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
|
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
|
||||||
DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request);
|
DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(request);
|
||||||
net_event->state = DN_NET2ResponseState_WSClose;
|
net_event->state = DN_NETResponseState_WSClose;
|
||||||
net_event->payload = DN_Str8FromFmtPool(&emc->pool, "Websocket closed '%.*s': (%u) %s (was %s close)", DN_Str8PrintFmt(request->url), event->code, event->reason, event->wasClean ? "clean" : "unclean");
|
net_event->payload = DN_Str8FromFmtPool(&emc->pool, "Websocket closed '%.*s': (%u) %s (was %s close)", DN_Str8PrintFmt(request->url), event->code, event->reason, event->wasClean ? "clean" : "unclean");
|
||||||
DN_NET2_EmcOnRequestDone_(net, request);
|
DN_NET_EmcOnRequestDone_(net, request);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DN_NET2_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch)
|
static void DN_NET_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch)
|
||||||
{
|
{
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) fetch->userData;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) fetch->userData;
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0];
|
||||||
request->response.http_status = fetch->status;
|
request->response.http_status = fetch->status;
|
||||||
request->response.state = DN_NET2ResponseState_HTTP;
|
request->response.state = DN_NETResponseState_HTTP;
|
||||||
DN_Str8BuilderAppendCopy(&request->response.body, DN_Str8FromPtr(fetch->data, fetch->numBytes - 1));
|
DN_Str8BuilderAppendCopy(&request->response.body, DN_Str8FromPtr(fetch->data, fetch->numBytes - 1));
|
||||||
DN_NET2_EmcOnRequestDone_(net, request);
|
DN_NET_EmcOnRequestDone_(net, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DN_NET2_EmcHTTPFailCallback(emscripten_fetch_t *fetch)
|
static void DN_NET_EmcHTTPFailCallback(emscripten_fetch_t *fetch)
|
||||||
{
|
{
|
||||||
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) fetch->userData;
|
DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) fetch->userData;
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0];
|
||||||
|
|
||||||
request->response.http_status = fetch->status;
|
request->response.http_status = fetch->status;
|
||||||
request->response.state = DN_NET2ResponseState_Error;
|
request->response.state = DN_NETResponseState_Error;
|
||||||
DN_NET2_EmcOnRequestDone_(net, request);
|
DN_NET_EmcOnRequestDone_(net, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DN_NET2_EmcHTTPProgressCallback(emscripten_fetch_t *fetch)
|
static void DN_NET_EmcHTTPProgressCallback(emscripten_fetch_t *fetch)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void DN_NET2_EmcInit(DN_NET2Core *net, char *base, DN_U64 base_size)
|
void DN_NET_EmcInit(DN_NETCore *net, char *base, DN_U64 base_size)
|
||||||
{
|
{
|
||||||
DN_NET2_BaseInit_(net, base, base_size);
|
DN_NET_BaseInit_(net, base, base_size);
|
||||||
DN_NET2EmcCore *emc = DN_ArenaNew(&net->arena, DN_NET2EmcCore, DN_ZMem_Yes);
|
DN_NETEmcCore *emc = DN_ArenaNew(&net->arena, DN_NETEmcCore, DN_ZMem_Yes);
|
||||||
emc->pool = DN_PoolFromArena(&net->arena, 0);
|
emc->pool = DN_PoolFromArena(&net->arena, 0);
|
||||||
net->context = emc;
|
net->context = emc;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args)
|
void DN_NET_EmcDeinit(DN_NETCore *net)
|
||||||
{
|
{
|
||||||
// NOTE: Allocate request
|
(void)net;
|
||||||
DN_NET2RequestInternal *request = net->free_list;
|
// TODO: Track all the request handles and clean it up
|
||||||
if (request) {
|
|
||||||
net->free_list = net->free_list->next;
|
|
||||||
request->next = nullptr;
|
|
||||||
} else {
|
|
||||||
request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Request result = DN_NET2_SetupRequest_(request, url, method, args, DN_NET2RequestType_HTTP);
|
DN_NETRequest DN_NET_EmcDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args)
|
||||||
|
{
|
||||||
|
// NOTE: Allocate request
|
||||||
|
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
|
||||||
|
DN_NETRequestInternal *request = emc->free_list;
|
||||||
|
if (request) {
|
||||||
|
emc->free_list = emc->free_list->next;
|
||||||
|
request->next = nullptr;
|
||||||
|
} else {
|
||||||
|
request = DN_ArenaNew(&net->arena, DN_NETRequestInternal, DN_ZMem_Yes);
|
||||||
|
}
|
||||||
|
|
||||||
|
DN_NETRequest result = DN_NET_SetupRequest_(request, url, method, args, DN_NETRequestType_HTTP);
|
||||||
|
|
||||||
// NOTE: Setup some emscripten specific data into our request context
|
// NOTE: Setup some emscripten specific data into our request context
|
||||||
request->context[0] = DN_Cast(DN_UPtr) net;
|
request->context[0] = DN_Cast(DN_UPtr) net;
|
||||||
@ -185,7 +195,7 @@ DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Handle basic auth
|
// NOTE: Handle basic auth
|
||||||
if (request->args.flags & DN_NET2DoHTTPFlags_BasicAuth) {
|
if (request->args.flags & DN_NETDoHTTPFlags_BasicAuth) {
|
||||||
if (request->args.username.size && request->args.password.size) {
|
if (request->args.username.size && request->args.password.size) {
|
||||||
DN_Assert(request->args.username.data[request->args.username.size] == 0);
|
DN_Assert(request->args.username.data[request->args.username.size] == 0);
|
||||||
DN_Assert(request->args.password.data[request->args.password.size] == 0);
|
DN_Assert(request->args.password.data[request->args.password.size] == 0);
|
||||||
@ -204,9 +214,9 @@ DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method,
|
|||||||
// > be called, but without data bytes. Note: Firefox only as it depends on
|
// > be called, but without data bytes. Note: Firefox only as it depends on
|
||||||
// > 'moz-chunked-arraybuffer'.
|
// > 'moz-chunked-arraybuffer'.
|
||||||
fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
|
fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
|
||||||
fetch_attribs.onsuccess = DN_NET2_EmcHTTPSuccessCallback;
|
fetch_attribs.onsuccess = DN_NET_EmcHTTPSuccessCallback;
|
||||||
fetch_attribs.onerror = DN_NET2_EmcHTTPFailCallback;
|
fetch_attribs.onerror = DN_NET_EmcHTTPFailCallback;
|
||||||
fetch_attribs.onprogress = DN_NET2_EmcHTTPProgressCallback;
|
fetch_attribs.onprogress = DN_NET_EmcHTTPProgressCallback;
|
||||||
fetch_attribs.userData = request;
|
fetch_attribs.userData = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,27 +228,27 @@ DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Request DN_NET2_EmcDoWS(DN_NET2Core *net, DN_Str8 url)
|
DN_NETRequest DN_NET_EmcDoWS(DN_NETCore *net, DN_Str8 url)
|
||||||
{
|
{
|
||||||
DN_Assert(emscripten_websocket_is_supported());
|
DN_Assert(emscripten_websocket_is_supported());
|
||||||
|
|
||||||
// NOTE: Allocate request
|
// NOTE: Allocate request
|
||||||
DN_NET2RequestInternal *request = net->free_list;
|
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
|
||||||
|
DN_NETRequestInternal *request = emc->free_list;
|
||||||
if (request) {
|
if (request) {
|
||||||
net->free_list = net->free_list->next;
|
emc->free_list = emc->free_list->next;
|
||||||
request->next = nullptr;
|
request->next = nullptr;
|
||||||
} else {
|
} else {
|
||||||
request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes);
|
request = DN_ArenaNew(&net->arena, DN_NETRequestInternal, DN_ZMem_Yes);
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Request result = DN_NET2_SetupRequest_(request, url, /*method=*/ DN_Str8Lit(""), /*args=*/nullptr, DN_NET2RequestType_WS);
|
DN_NETRequest result = DN_NET_SetupRequest_(request, url, /*method=*/ DN_Str8Lit(""), /*args=*/nullptr, DN_NETRequestType_WS);
|
||||||
if (!request)
|
if (!request)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// NOTE: Setup some emscripten specific data into our request context
|
// NOTE: Setup some emscripten specific data into our request context
|
||||||
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
|
|
||||||
request->context[0] = DN_Cast(DN_UPtr) net;
|
request->context[0] = DN_Cast(DN_UPtr) net;
|
||||||
request->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&request->arena, DN_NET2EmcRequest, DN_ZMem_Yes);
|
request->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&request->arena, DN_NETEmcRequest, DN_ZMem_Yes);
|
||||||
request->start_response_arena_pos = DN_ArenaPos(&request->arena);
|
request->start_response_arena_pos = DN_ArenaPos(&request->arena);
|
||||||
|
|
||||||
// NOTE: Create the websocket request and dispatch it via emscripten
|
// NOTE: Create the websocket request and dispatch it via emscripten
|
||||||
@ -246,62 +256,63 @@ DN_NET2Request DN_NET2_EmcDoWS(DN_NET2Core *net, DN_Str8 url)
|
|||||||
emscripten_websocket_init_create_attributes(&attr);
|
emscripten_websocket_init_create_attributes(&attr);
|
||||||
attr.url = request->url.data;
|
attr.url = request->url.data;
|
||||||
|
|
||||||
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request->context[1];
|
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1];
|
||||||
emc_request->socket = emscripten_websocket_new(&attr);
|
emc_request->socket = emscripten_websocket_new(&attr);
|
||||||
DN_Assert(emc_request->socket > 0);
|
DN_Assert(emc_request->socket > 0);
|
||||||
emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnOpen);
|
emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/ request, DN_NET_EmcWSOnOpen);
|
||||||
emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnMessage);
|
emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/ request, DN_NET_EmcWSOnMessage);
|
||||||
emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnError);
|
emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/ request, DN_NET_EmcWSOnError);
|
||||||
emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnClose);
|
emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/ request, DN_NET_EmcWSOnClose);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DN_NET2_EmcDoWSSend(DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send)
|
void DN_NET_EmcDoWSSend(DN_NETRequest request, DN_Str8 data, DN_NETWSSend send)
|
||||||
{
|
{
|
||||||
DN_AssertF(send == DN_NET2WSSend_Binary || send == DN_NET2WSSend_Text || send == DN_NET2WSSend_Close,
|
DN_AssertF(send == DN_NETWSSend_Binary || send == DN_NETWSSend_Text || send == DN_NETWSSend_Close,
|
||||||
"Unimplemented, Emscripten only supports some of the available operations");
|
"Unimplemented, Emscripten only supports some of the available operations");
|
||||||
|
|
||||||
bool result = false;
|
int result = 0;
|
||||||
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
|
DN_NETRequestInternal *request_ptr = DN_Cast(DN_NETRequestInternal *) request.handle;
|
||||||
if (request_ptr && request_ptr->gen == request.gen) {
|
if (request_ptr && request_ptr->gen == request.gen) {
|
||||||
DN_Assert(request_ptr->type == DN_NET2RequestType_WS);
|
DN_Assert(request_ptr->type == DN_NETRequestType_WS);
|
||||||
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1];
|
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1];
|
||||||
switch (send) {
|
switch (send) {
|
||||||
default: DN_InvalidCodePath; break;
|
default: DN_InvalidCodePath; break;
|
||||||
case DN_NET2WSSend_Text: {
|
case DN_NETWSSend_Text: {
|
||||||
DN_U64 pos = DN_ArenaPos(&request_ptr->arena);
|
DN_U64 pos = DN_ArenaPos(&request_ptr->arena);
|
||||||
DN_Str8 data_null_terminated = DN_Str8FromStr8Arena(&request_ptr->arena, data);
|
DN_Str8 data_null_terminated = DN_Str8FromStr8Arena(&request_ptr->arena, data);
|
||||||
result = emscripten_websocket_send_utf8_text(emc_request->socket, data_null_terminated.data);
|
result = emscripten_websocket_send_utf8_text(emc_request->socket, data_null_terminated.data);
|
||||||
DN_ArenaPopTo(&request_ptr->arena, pos);
|
DN_ArenaPopTo(&request_ptr->arena, pos);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DN_NET2WSSend_Binary: {
|
case DN_NETWSSend_Binary: {
|
||||||
result = emscripten_websocket_send_binary(emc_request->socket, data.data, data.size);
|
result = emscripten_websocket_send_binary(emc_request->socket, data.data, data.size);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DN_NET2WSSend_Close: {
|
case DN_NETWSSend_Close: {
|
||||||
result = emscripten_websocket_close(emc_request->socket, 0, nullptr);
|
result = emscripten_websocket_close(emc_request->socket, 0, nullptr);
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Handle result
|
// TODO: Handle result, the header file doesn't really elucidate what this result value is
|
||||||
|
(void)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DN_NET2Response DN_NET2_EmcHandleFinishedRequest_(DN_NET2Core *net, DN_NET2EmcCore *emc, DN_NET2Request request, DN_NET2RequestInternal *request_ptr, DN_Arena *arena)
|
static DN_NETResponse DN_NET_EmcHandleFinishedRequest_(DN_NETCore *net, DN_NETEmcCore *emc, DN_NETRequest request, DN_NETRequestInternal *request_ptr, DN_Arena *arena)
|
||||||
{
|
{
|
||||||
DN_NET2Response result = {};
|
DN_NETResponse result = {};
|
||||||
bool end_request = true;
|
bool end_request = true;
|
||||||
if (request_ptr->type == DN_NET2RequestType_HTTP) {
|
if (request_ptr->type == DN_NETRequestType_HTTP) {
|
||||||
result = DN_NET2_MakeResponseFromFinishedRequest_(request, arena);
|
result = DN_NET_MakeResponseFromFinishedRequest_(request, arena);
|
||||||
} else {
|
} else {
|
||||||
// NOTE: Get emscripten contexts
|
// NOTE: Get emscripten contexts
|
||||||
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1];
|
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1];
|
||||||
DN_NET2EmcWSEvent *emc_event = emc_request->first_event;
|
DN_NETEmcWSEvent *emc_event = emc_request->first_event;
|
||||||
emc_request->first_event = emc_event->next; // Advance the list pointer
|
emc_request->first_event = emc_event->next; // Advance the list pointer
|
||||||
DN_Assert(emc_event);
|
DN_Assert(emc_event);
|
||||||
DN_Assert((emc_event->state >= DN_NET2ResponseState_WSOpen && emc_event->state <= DN_NET2ResponseState_WSPong) ||
|
DN_Assert((emc_event->state >= DN_NETResponseState_WSOpen && emc_event->state <= DN_NETResponseState_WSPong) ||
|
||||||
emc_event->state == DN_NET2ResponseState_Error);
|
emc_event->state == DN_NETResponseState_Error);
|
||||||
|
|
||||||
// NOTE: Build the result
|
// NOTE: Build the result
|
||||||
result.state = emc_event->state;
|
result.state = emc_event->state;
|
||||||
@ -315,7 +326,7 @@ static DN_NET2Response DN_NET2_EmcHandleFinishedRequest_(DN_NET2Core *net, DN_NE
|
|||||||
emc_event->payload = {};
|
emc_event->payload = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.state != DN_NET2ResponseState_WSClose)
|
if (result.state != DN_NETResponseState_WSClose)
|
||||||
end_request = false;
|
end_request = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,18 +335,19 @@ static DN_NET2Response DN_NET2_EmcHandleFinishedRequest_(DN_NET2Core *net, DN_NE
|
|||||||
request_ptr->response.body = DN_Str8BuilderFromArena(&request_ptr->arena);
|
request_ptr->response.body = DN_Str8BuilderFromArena(&request_ptr->arena);
|
||||||
|
|
||||||
if (end_request) {
|
if (end_request) {
|
||||||
DN_NET2_EndFinishedRequest_(net, request_ptr);
|
DN_NET_EndFinishedRequest_(request_ptr);
|
||||||
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
|
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1];
|
||||||
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1];
|
|
||||||
emscripten_websocket_delete(emc_request->socket);
|
emscripten_websocket_delete(emc_request->socket);
|
||||||
request_ptr->next = net->free_list;
|
|
||||||
net->free_list = request_ptr;
|
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
|
||||||
|
request_ptr->next = emc->free_list;
|
||||||
|
emc->free_list = request_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DN_OSSemaphoreWaitResult DN_NET2_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms)
|
static DN_OSSemaphoreWaitResult DN_NET_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms)
|
||||||
{
|
{
|
||||||
// NOTE: In emscripten you can't just block on the semaphore with 'timeout_ms' because it needs
|
// NOTE: In emscripten you can't just block on the semaphore with 'timeout_ms' because it needs
|
||||||
// to yield to the javascript's event loop otherwise the fetching step cannot progress. Instead
|
// to yield to the javascript's event loop otherwise the fetching step cannot progress. Instead
|
||||||
@ -363,22 +375,22 @@ static DN_OSSemaphoreWaitResult DN_NET2_EmcSemaphoreWait_(DN_OSSemaphore *sem, D
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Response DN_NET2_EmcWaitForResponse(DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms)
|
DN_NETResponse DN_NET_EmcWaitForResponse(DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms)
|
||||||
{
|
{
|
||||||
DN_NET2Response result = {};
|
DN_NETResponse result = {};
|
||||||
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
|
DN_NETRequestInternal *request_ptr = DN_Cast(DN_NETRequestInternal *) request.handle;
|
||||||
if (request_ptr && request_ptr->gen == request.gen) {
|
if (request_ptr && request_ptr->gen == request.gen) {
|
||||||
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[0];
|
DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[0];
|
||||||
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
|
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
|
||||||
DN_Assert(emc);
|
DN_Assert(emc);
|
||||||
DN_OSSemaphoreWaitResult wait = DN_NET2_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms);
|
DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms);
|
||||||
if (wait != DN_OSSemaphoreWaitResult_Success)
|
if (wait != DN_OSSemaphoreWaitResult_Success)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// NOTE: Remove request from the done list
|
// NOTE: Remove request from the done list
|
||||||
request_ptr->next = nullptr;
|
request_ptr->next = nullptr;
|
||||||
net->done_list = net->done_list->next;
|
emc->response_list = emc->response_list->next;
|
||||||
result = DN_NET2_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena);
|
result = DN_NET_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena);
|
||||||
|
|
||||||
// NOTE: Decrement the global 'request done' completion semaphore since the user consumed the
|
// NOTE: Decrement the global 'request done' completion semaphore since the user consumed the
|
||||||
// request individually.
|
// request individually.
|
||||||
@ -388,33 +400,33 @@ DN_NET2Response DN_NET2_EmcWaitForResponse(DN_NET2Request request, DN_Arena *are
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DN_NET2Response DN_NET2_EmcWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms)
|
DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms)
|
||||||
{
|
{
|
||||||
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
|
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
|
||||||
DN_Assert(emc);
|
DN_Assert(emc);
|
||||||
|
|
||||||
DN_NET2Response result = {};
|
DN_NETResponse result = {};
|
||||||
DN_OSSemaphoreWaitResult wait = DN_NET2_EmcSemaphoreWait_(&net->completion_sem, timeout_ms);
|
DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&net->completion_sem, timeout_ms);
|
||||||
if (wait != DN_OSSemaphoreWaitResult_Success)
|
if (wait != DN_OSSemaphoreWaitResult_Success)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// NOTE: Dequeue the request that is done from the done list
|
// NOTE: Dequeue the request that is done from the done list
|
||||||
DN_AssertF(net->done_list,
|
DN_AssertF(emc->response_list,
|
||||||
"This should be set otherwise we bumped the completion sem without queueing into the "
|
"This should be set otherwise we bumped the completion sem without queueing into the "
|
||||||
"done list or we forgot to wait on the global semaphore after a request finished");
|
"done list or we forgot to wait on the global semaphore after a request finished");
|
||||||
DN_NET2RequestInternal *request_ptr = net->done_list;
|
DN_NETRequestInternal *request_ptr = emc->response_list;
|
||||||
DN_Assert(request_ptr == net->done_list);
|
DN_Assert(request_ptr == emc->response_list);
|
||||||
request_ptr->next = nullptr;
|
request_ptr->next = nullptr;
|
||||||
net->done_list = net->done_list->next;
|
emc->response_list = emc->response_list->next;
|
||||||
|
|
||||||
// NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore
|
// NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore
|
||||||
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/);
|
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/);
|
||||||
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
|
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
|
||||||
|
|
||||||
DN_NET2Request request = {};
|
DN_NETRequest request = {};
|
||||||
request.handle = DN_Cast(DN_UPtr) request_ptr;
|
request.handle = DN_Cast(DN_UPtr) request_ptr;
|
||||||
request.gen = request_ptr->gen;
|
request.gen = request_ptr->gen;
|
||||||
result = DN_NET2_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena);
|
result = DN_NET_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
#if !defined(DN_NET_EMSCRIPTEN_H)
|
#if !defined(DN_NET_EMSCRIPTEN_H)
|
||||||
#define DN_NET_EMSCRIPTEN_H
|
#define DN_NET_EMSCRIPTEN_H
|
||||||
|
|
||||||
#include "dn_net2.h"
|
#include "dn_net.h"
|
||||||
|
|
||||||
DN_NET2Interface DN_NET2_EmcInterface();
|
DN_NETInterface DN_NET_EmcInterface();
|
||||||
void DN_NET2_EmcInit (DN_NET2Core *net, char *base, DN_U64 base_size);
|
void DN_NET_EmcInit (DN_NETCore *net, char *base, DN_U64 base_size);
|
||||||
DN_NET2Request DN_NET2_EmcDoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args);
|
void DN_NET_EmcDeinit (DN_NETCore *net);
|
||||||
DN_NET2Request DN_NET2_EmcDoWS (DN_NET2Core *net, DN_Str8 url);
|
DN_NETRequest DN_NET_EmcDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args);
|
||||||
void DN_NET2_EmcDoWSSend (DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send);
|
DN_NETRequest DN_NET_EmcDoWS (DN_NETCore *net, DN_Str8 url);
|
||||||
DN_NET2Response DN_NET2_EmcWaitForResponse (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms);
|
void DN_NET_EmcDoWSSend (DN_NETRequest request, DN_Str8 data, DN_NETWSSend send);
|
||||||
DN_NET2Response DN_NET2_EmcWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms);
|
DN_NETResponse DN_NET_EmcWaitForResponse (DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms);
|
||||||
|
DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms);
|
||||||
|
|
||||||
#endif // DN_NET_EMSCRIPTEN_H
|
#endif // DN_NET_EMSCRIPTEN_H
|
||||||
|
|||||||
@ -2489,12 +2489,12 @@ static DN_UTCore DN_Tests_Win()
|
|||||||
static DN_UTCore DN_Tests_Net()
|
static DN_UTCore DN_Tests_Net()
|
||||||
{
|
{
|
||||||
DN_Str8 label = {};
|
DN_Str8 label = {};
|
||||||
DN_NET2Interface net_interface = {};
|
DN_NETInterface net_interface = {};
|
||||||
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
#if defined(DN_PLATFORM_EMSCRIPTEN)
|
||||||
net_interface = DN_NET2_EmcInterface();
|
net_interface = DN_NET_EmcInterface();
|
||||||
label = DN_Str8Lit("Emscripten");
|
label = DN_Str8Lit("Emscripten");
|
||||||
#elif defined(DN_UNIT_TESTS_WITH_CURL)
|
#elif defined(DN_UNIT_TESTS_WITH_CURL)
|
||||||
net_interface = DN_NET2_CurlInterface();
|
net_interface = DN_NET_CurlInterface();
|
||||||
label = DN_Str8Lit("CURL");
|
label = DN_Str8Lit("CURL");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -2506,59 +2506,66 @@ static DN_UTCore DN_Tests_Net()
|
|||||||
DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org");
|
DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org");
|
||||||
DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com");
|
DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com");
|
||||||
|
|
||||||
char net_base[DN_Kilobytes(16)] = {};
|
DN_USize net_base_size = DN_Megabytes(1);
|
||||||
DN_NET2Core net = {};
|
char *net_base = DN_ArenaNewArray(&arena, char, net_base_size, DN_ZMem_Yes);
|
||||||
net_interface.init(&net, net_base, sizeof(net_base));
|
DN_NETCore net = {};
|
||||||
|
net_interface.init(&net, net_base, net_base_size);
|
||||||
|
|
||||||
|
DN_U64 arena_reset_p = DN_ArenaPos(&arena);
|
||||||
for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP GET request", DN_Str8PrintFmt(label))) {
|
for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP GET request", DN_Str8PrintFmt(label))) {
|
||||||
DN_NET2Request request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr);
|
DN_NETRequest request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr);
|
||||||
DN_NET2Response response = net_interface.wait_for_response(request, &arena, UINT32_MAX);
|
DN_NETResponse response = net_interface.wait_for_response(request, &arena, UINT32_MAX);
|
||||||
DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status);
|
DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status);
|
||||||
DN_UT_AssertF(&result, response.state == DN_NET2ResponseState_HTTP, "state=%u", response.state);
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state);
|
||||||
DN_UT_AssertF(&result, response.error_str8.size == 0, "%.*s", DN_Str8PrintFmt(response.error_str8));
|
DN_UT_AssertF(&result, response.error_str8.size == 0, "%.*s", DN_Str8PrintFmt(response.error_str8));
|
||||||
DN_UT_Assert(&result, response.body.size);
|
DN_UT_Assert(&result, response.body.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP POST request", DN_Str8PrintFmt(label))) {
|
for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP POST request", DN_Str8PrintFmt(label))) {
|
||||||
DN_NET2Request request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr);
|
net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr);
|
||||||
DN_NET2Response response = net_interface.wait_for_any_response(&net, &arena, UINT32_MAX);
|
DN_NETResponse response = net_interface.wait_for_any_response(&net, &arena, UINT32_MAX);
|
||||||
DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status);
|
DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status);
|
||||||
DN_UT_AssertF(&result, response.state == DN_NET2ResponseState_HTTP, "state=%u", response.state);
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state);
|
||||||
DN_UT_AssertF(&result, response.error_str8.size == 0, "error=%.*s", DN_Str8PrintFmt(response.error_str8));
|
DN_UT_AssertF(&result, response.error_str8.size == 0, "error=%.*s", DN_Str8PrintFmt(response.error_str8));
|
||||||
DN_UT_Assert(&result, response.body.size);
|
DN_UT_Assert(&result, response.body.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DN_UT_Test(&result, "%.*s WaitForResponse WS request", DN_Str8PrintFmt(label))) {
|
for (DN_UT_Test(&result, "%.*s WaitForResponse WS request", DN_Str8PrintFmt(label))) {
|
||||||
DN_NET2Request request = net_interface.do_ws(&net, remote_ws_server_url);
|
DN_NETRequest request = net_interface.do_ws(&net, remote_ws_server_url);
|
||||||
DN_USize const WS_TIMEOUT_MS = 2000;
|
DN_USize const WS_TIMEOUT_MS = 16;
|
||||||
|
|
||||||
// NOTE: Wait for WS connection to open
|
// NOTE: Wait for WS connection to open
|
||||||
for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) {
|
for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_ArenaPopTo(&arena, arena_reset_p)) {
|
||||||
DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
||||||
if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout
|
if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout
|
||||||
continue;
|
continue;
|
||||||
DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSOpen);
|
if (response.state == DN_NETResponseState_Error)
|
||||||
|
DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8));
|
||||||
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSOpen, "state=%d", response.state);
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Receive the initial text from the echo server
|
// NOTE: Receive the initial text from the echo server
|
||||||
for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) {
|
for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_ArenaPopTo(&arena, arena_reset_p)) {
|
||||||
DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
||||||
if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout
|
if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout
|
||||||
continue;
|
continue;
|
||||||
DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSText);
|
if (response.state == DN_NETResponseState_Error)
|
||||||
|
DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8));
|
||||||
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSText, "state=%d", response.state);
|
||||||
// NOTE: Send the close signal
|
// NOTE: Send the close signal
|
||||||
net_interface.do_ws_send(request, DN_Str8Lit(""), DN_NET2WSSend_Close);
|
net_interface.do_ws_send(request, DN_Str8Lit(""), DN_NETWSSend_Close);
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Expect to hear the close
|
// NOTE: Expect to hear the close
|
||||||
for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) {
|
for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_ArenaPopTo(&arena, arena_reset_p)) {
|
||||||
DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
|
||||||
if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout
|
if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout
|
||||||
continue;
|
continue;
|
||||||
DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSClose);
|
if (response.state == DN_NETResponseState_Error)
|
||||||
|
DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8));
|
||||||
|
DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSClose, "state=%d");
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,8 @@
|
|||||||
#include "../Extra/dn_math.cpp"
|
#include "../Extra/dn_math.cpp"
|
||||||
#include "../Extra/dn_helpers.cpp"
|
#include "../Extra/dn_helpers.cpp"
|
||||||
|
|
||||||
#include "../Extra/dn_net2.h"
|
#include "../Extra/dn_net.h"
|
||||||
#include "../Extra/dn_net2.cpp"
|
#include "../Extra/dn_net.cpp"
|
||||||
|
|
||||||
#if defined(DN_UNIT_TESTS_WITH_CURL)
|
#if defined(DN_UNIT_TESTS_WITH_CURL)
|
||||||
#define CURL_STATICLIB
|
#define CURL_STATICLIB
|
||||||
|
|||||||
@ -131,8 +131,10 @@ DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args)
|
|||||||
|
|
||||||
QueryPerformanceFrequency(&w32->qpc_frequency);
|
QueryPerformanceFrequency(&w32->qpc_frequency);
|
||||||
HMODULE module = LoadLibraryA("kernel32.dll");
|
HMODULE module = LoadLibraryA("kernel32.dll");
|
||||||
|
if (module) {
|
||||||
w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription");
|
w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription");
|
||||||
FreeLibrary(module);
|
FreeLibrary(module);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: win32 bcrypt
|
// NOTE: win32 bcrypt
|
||||||
wchar_t const BCRYPT_ALGORITHM[] = L"RNG";
|
wchar_t const BCRYPT_ALGORITHM[] = L"RNG";
|
||||||
|
|||||||
@ -688,9 +688,9 @@ DN_API void DN_OS_Exit(int32_t exit_code)
|
|||||||
|
|
||||||
DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
||||||
char *stdout_buffer,
|
char *stdout_buffer,
|
||||||
size_t *stdout_size,
|
DN_USize *stdout_size,
|
||||||
char *stderr_buffer,
|
char *stderr_buffer,
|
||||||
size_t *stderr_size,
|
DN_USize *stderr_size,
|
||||||
DN_U32 timeout_ms,
|
DN_U32 timeout_ms,
|
||||||
DN_OSErrSink *err)
|
DN_OSErrSink *err)
|
||||||
{
|
{
|
||||||
@ -736,7 +736,7 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
|||||||
result.os_error_code = win_error.code;
|
result.os_error_code = win_error.code;
|
||||||
DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_Str8PrintFmt(win_error.msg));
|
DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_Str8PrintFmt(win_error.msg));
|
||||||
} else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) {
|
} else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) {
|
||||||
// NOTE: Read stdout from process //////////////////////////////////////////////////////
|
// NOTE: Read stdout from process
|
||||||
// If the pipes are full, the process will block. We periodically
|
// If the pipes are full, the process will block. We periodically
|
||||||
// flush the pipes to make sure this doesn't happen
|
// flush the pipes to make sure this doesn't happen
|
||||||
char sink[DN_Kilobytes(8)];
|
char sink[DN_Kilobytes(8)];
|
||||||
@ -745,15 +745,18 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
|||||||
if (stdout_bytes_available) {
|
if (stdout_bytes_available) {
|
||||||
DWORD bytes_read = 0;
|
DWORD bytes_read = 0;
|
||||||
char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink;
|
char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink;
|
||||||
size_t dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink);
|
DN_USize dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink);
|
||||||
BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL);
|
BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL);
|
||||||
(void)success; // TODO:
|
if (success) {
|
||||||
if (stdout_size)
|
if (stdout_size)
|
||||||
*stdout_size = bytes_read;
|
*stdout_size = bytes_read;
|
||||||
|
} else {
|
||||||
|
DN_OS_ErrSinkAppendF(err, 1, "Failed to read bytes from stdout");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Read stderr from process //////////////////////////////////////////////////////
|
// NOTE: Read stderr from process
|
||||||
stderr_bytes_available = 0;
|
stderr_bytes_available = 0;
|
||||||
if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) {
|
if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) {
|
||||||
if (stderr_bytes_available) {
|
if (stderr_bytes_available) {
|
||||||
@ -761,9 +764,12 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
|||||||
size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink);
|
size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink);
|
||||||
DWORD bytes_read = 0;
|
DWORD bytes_read = 0;
|
||||||
BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL);
|
BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL);
|
||||||
(void)success; // TODO:
|
if (success) {
|
||||||
if (stderr_size)
|
if (stderr_size)
|
||||||
*stderr_size = bytes_read;
|
*stderr_size = bytes_read;
|
||||||
|
} else {
|
||||||
|
DN_OS_ErrSinkAppendF(err, 1, "Failed to read bytes from stderr");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -782,11 +788,16 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
|
|||||||
DN_Str8PrintFmt(win_error.msg));
|
DN_Str8PrintFmt(win_error.msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Cleanup ///////////////////////////////////////////////////////////////////////////////
|
// NOTE: Cleanup
|
||||||
|
if (handle.stdout_write)
|
||||||
CloseHandle(handle.stdout_write);
|
CloseHandle(handle.stdout_write);
|
||||||
|
if (handle.stderr_write)
|
||||||
CloseHandle(handle.stderr_write);
|
CloseHandle(handle.stderr_write);
|
||||||
|
if (handle.stdout_read)
|
||||||
CloseHandle(handle.stdout_read);
|
CloseHandle(handle.stdout_read);
|
||||||
|
if (handle.stderr_read)
|
||||||
CloseHandle(handle.stderr_read);
|
CloseHandle(handle.stderr_read);
|
||||||
|
if (handle.process)
|
||||||
CloseHandle(handle.process);
|
CloseHandle(handle.process);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
project.rdbg
Normal file
2
project.rdbg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// raddbg 0.9.25 project file
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user