first commit
CI / Build (clang, clang++, ubuntu-22.04, linux, 3.10, master, x86,x86_64) (push) Canceled after 0s
CI / Build (clang, clang++, windows-2022, windows, 3.10, master, x86,x86_64) (push) Canceled after 0s
CI / Release (push) Canceled after 0s

This commit is contained in:
2026-09-13 22:18:11 +01:00
commit 08c03ac533
6701 changed files with 1577314 additions and 0 deletions
+315
View File
@@ -0,0 +1,315 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod REST in Pawn Extension
* Copyright 2017-2026 Erik Minekus
* =============================================================================
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "extension.h"
#include "httpclient.h"
#include "httprequest.h"
#include "queue.h"
// Limit the max processing request per tick
#define MAX_PROCESS 10
RipExt g_RipExt; /**< Global singleton for extension's main interface */
SMEXT_LINK(&g_RipExt);
LockedQueue<IHTTPContext *> g_RequestQueue;
LockedQueue<IHTTPContext *> g_CompletedRequestQueue;
CURLM *g_Curl;
uv_loop_t *g_Loop;
uv_thread_t g_Thread;
uv_timer_t g_Timeout;
uv_async_t g_AsyncPerformRequests;
uv_async_t g_AsyncStopLoop;
HTTPClientHandler g_HTTPClientHandler;
HandleType_t htHTTPClient;
HTTPRequestHandler g_HTTPRequestHandler;
HandleType_t htHTTPRequest;
HTTPResponseHandler g_HTTPResponseHandler;
HandleType_t htHTTPResponse;
JSONHandler g_JSONHandler;
HandleType_t htJSON;
JSONObjectKeysHandler g_JSONObjectKeysHandler;
HandleType_t htJSONObjectKeys;
static void CheckCompletedRequests()
{
CURLMsg *message;
int pending;
while ((message = curl_multi_info_read(g_Curl, &pending)))
{
if (message->msg != CURLMSG_DONE)
{
continue;
}
CURL *curl = message->easy_handle;
curl_multi_remove_handle(g_Curl, curl);
IHTTPContext *context;
curl_easy_getinfo(curl, CURLINFO_PRIVATE, &context);
g_CompletedRequestQueue.Lock();
g_CompletedRequestQueue.Push(context);
g_CompletedRequestQueue.Unlock();
}
}
static void PerformRequests(uv_timer_t *handle)
{
int running;
curl_multi_socket_action(g_Curl, CURL_SOCKET_TIMEOUT, 0, &running);
CheckCompletedRequests();
}
static void CurlSocketActivity(uv_poll_t *handle, int status, int events)
{
CurlContext *context = (CurlContext *)handle->data;
int flags = 0;
if (events & UV_READABLE)
{
flags |= CURL_CSELECT_IN;
}
if (events & UV_WRITABLE)
{
flags |= CURL_CSELECT_OUT;
}
int running;
curl_multi_socket_action(g_Curl, context->socket, flags, &running);
CheckCompletedRequests();
}
static int CurlSocketCallback(CURL *curl, curl_socket_t socket, int action, void *userdata, void *socketdata)
{
CurlContext *context;
int events = 0;
switch (action)
{
case CURL_POLL_IN:
case CURL_POLL_OUT:
case CURL_POLL_INOUT:
context = socketdata ? (CurlContext *)socketdata : new CurlContext(socket);
curl_multi_assign(g_Curl, socket, context);
if (action != CURL_POLL_IN)
{
events |= UV_WRITABLE;
}
if (action != CURL_POLL_OUT)
{
events |= UV_READABLE;
}
uv_poll_start(&context->poll_handle, events, &CurlSocketActivity);
break;
case CURL_POLL_REMOVE:
if (socketdata)
{
context = (CurlContext *)socketdata;
context->Destroy();
curl_multi_assign(g_Curl, socket, NULL);
}
break;
}
return 0;
}
static int CurlTimeoutCallback(CURLM *multi, long timeout_ms, void *userdata)
{
if (timeout_ms == -1)
{
uv_timer_stop(&g_Timeout);
return 0;
}
uv_timer_start(&g_Timeout, &PerformRequests, timeout_ms, 0);
return 0;
}
static void EventLoop(void *data)
{
uv_run(g_Loop, UV_RUN_DEFAULT);
}
static void AsyncPerformRequests(uv_async_t *handle)
{
g_RequestQueue.Lock();
IHTTPContext *context;
// Limiter
int count = 0;
while (!g_RequestQueue.Empty() && count < MAX_PROCESS)
{
context = g_RequestQueue.Pop();
if (!context->InitCurl())
{
delete context;
continue;
}
curl_multi_add_handle(g_Curl, context->curl);
count++;
}
g_RequestQueue.Unlock();
}
static void AsyncStopLoop(uv_async_t *handle)
{
uv_stop(g_Loop);
}
static void FrameHook(bool simulating)
{
if (!g_RequestQueue.Empty())
{
uv_async_send(&g_AsyncPerformRequests);
}
if (!g_CompletedRequestQueue.Empty())
{
g_CompletedRequestQueue.Lock();
IHTTPContext *context = g_CompletedRequestQueue.Pop();
context->OnCompleted();
delete context;
g_CompletedRequestQueue.Unlock();
}
}
bool RipExt::SDK_OnLoad(char *error, size_t maxlength, bool late)
{
sharesys->AddNatives(myself, http_natives);
sharesys->AddNatives(myself, json_natives);
sharesys->RegisterLibrary(myself, "ripext");
/* Initialize cURL */
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
if (res != CURLE_OK)
{
smutils->Format(error, maxlength, "%s", curl_easy_strerror(res));
return false;
}
g_Curl = curl_multi_init();
curl_multi_setopt(g_Curl, CURLMOPT_SOCKETFUNCTION, &CurlSocketCallback);
curl_multi_setopt(g_Curl, CURLMOPT_TIMERFUNCTION, &CurlTimeoutCallback);
/* Initialize libuv */
g_Loop = uv_default_loop();
uv_timer_init(g_Loop, &g_Timeout);
uv_async_init(g_Loop, &g_AsyncPerformRequests, &AsyncPerformRequests);
uv_async_init(g_Loop, &g_AsyncStopLoop, &AsyncStopLoop);
uv_thread_create(&g_Thread, &EventLoop, NULL);
/* Set up access rights for the 'HTTPRequest' handle type */
HandleAccess haHTTPRequest;
handlesys->InitAccessDefaults(NULL, &haHTTPRequest);
haHTTPRequest.access[HandleAccess_Delete] = 0;
/* Set up access rights for the 'HTTPResponse' handle type */
HandleAccess haHTTPResponse;
handlesys->InitAccessDefaults(NULL, &haHTTPResponse);
haHTTPResponse.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY;
/* Set up access rights for the 'JSON' handle type */
HandleAccess haJSON;
handlesys->InitAccessDefaults(NULL, &haJSON);
haJSON.access[HandleAccess_Delete] = 0;
htHTTPClient = handlesys->CreateType("HTTPClient", &g_HTTPClientHandler, 0, NULL, NULL, myself->GetIdentity(), NULL);
htHTTPRequest = handlesys->CreateType("HTTPRequest", &g_HTTPRequestHandler, 0, NULL, &haHTTPRequest, myself->GetIdentity(), NULL);
htHTTPResponse = handlesys->CreateType("HTTPResponse", &g_HTTPResponseHandler, 0, NULL, &haHTTPResponse, myself->GetIdentity(), NULL);
htJSON = handlesys->CreateType("JSON", &g_JSONHandler, 0, NULL, &haJSON, myself->GetIdentity(), NULL);
htJSONObjectKeys = handlesys->CreateType("JSONObjectKeys", &g_JSONObjectKeysHandler, 0, NULL, NULL, myself->GetIdentity(), NULL);
smutils->AddGameFrameHook(&FrameHook);
smutils->BuildPath(Path_SM, caBundlePath, sizeof(caBundlePath), SM_RIPEXT_CA_BUNDLE_PATH);
return true;
}
void RipExt::SDK_OnUnload()
{
uv_async_send(&g_AsyncStopLoop);
uv_thread_join(&g_Thread);
uv_loop_close(g_Loop);
curl_multi_cleanup(&g_Curl);
curl_global_cleanup();
handlesys->RemoveType(htHTTPClient, myself->GetIdentity());
handlesys->RemoveType(htHTTPRequest, myself->GetIdentity());
handlesys->RemoveType(htHTTPResponse, myself->GetIdentity());
handlesys->RemoveType(htJSON, myself->GetIdentity());
handlesys->RemoveType(htJSONObjectKeys, myself->GetIdentity());
smutils->RemoveGameFrameHook(&FrameHook);
}
void RipExt::AddRequestToQueue(IHTTPContext *context)
{
g_RequestQueue.Lock();
g_RequestQueue.Push(context);
g_RequestQueue.Unlock();
}
void HTTPClientHandler::OnHandleDestroy(HandleType_t type, void *object)
{
delete (HTTPClient *)object;
}
void HTTPRequestHandler::OnHandleDestroy(HandleType_t type, void *object)
{
delete (HTTPRequest *)object;
}
void HTTPResponseHandler::OnHandleDestroy(HandleType_t type, void *object)
{
/* Response objects are automatically cleaned up */
}
void JSONHandler::OnHandleDestroy(HandleType_t type, void *object)
{
json_decref((json_t *)object);
}
void JSONObjectKeysHandler::OnHandleDestroy(HandleType_t type, void *object)
{
delete (struct JSONObjectKeys *)object;
}