diff --git a/curl_natives.cpp b/curl_natives.cpp index e7ce21ad2..aa696d162 100644 --- a/curl_natives.cpp +++ b/curl_natives.cpp @@ -227,6 +227,36 @@ static cell_t GetClientTimeout(IPluginContext *pContext, const cell_t *params) return client->timeout; } +static cell_t GetClientConnectTimeout(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + struct HTTPClient *client; + Handle_t hndlClient = static_cast(params[1]); + if ((err=handlesys->ReadHandle(hndlClient, htHTTPClientObject, &sec, (void **)&client)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid HTTP client handle %x (error %d)", hndlClient, err); + } + + return client->connect_timeout; +} + +static cell_t GetClientFollowLocation(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + struct HTTPClient *client; + Handle_t hndlClient = static_cast(params[1]); + if ((err=handlesys->ReadHandle(hndlClient, htHTTPClientObject, &sec, (void **)&client)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid HTTP client handle %x (error %d)", hndlClient, err); + } + + return client->follow_location; +} + static cell_t SetClientTimeout(IPluginContext *pContext, const cell_t *params) { HandleError err; @@ -243,11 +273,42 @@ static cell_t SetClientTimeout(IPluginContext *pContext, const cell_t *params) return 1; } -static cell_t GetResponseData(IPluginContext *pContext, const cell_t *params) +static cell_t SetClientConnectTimeout(IPluginContext *pContext, const cell_t *params) { HandleError err; HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + struct HTTPClient *client; + Handle_t hndlClient = static_cast(params[1]); + if ((err=handlesys->ReadHandle(hndlClient, htHTTPClientObject, &sec, (void **)&client)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid HTTP client handle %x (error %d)", hndlClient, err); + } + + client->connect_timeout = params[2]; + return 1; +} + +static cell_t SetClientFollowLocation(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + struct HTTPClient *client; + Handle_t hndlClient = static_cast(params[1]); + if ((err=handlesys->ReadHandle(hndlClient, htHTTPClientObject, &sec, (void **)&client)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid HTTP client handle %x (error %d)", hndlClient, err); + } + + client->follow_location = (params[2] == 0 ? 0L : 1L); + return 1; +} + +static cell_t GetResponseData(IPluginContext *pContext, const cell_t *params) { + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + struct HTTPResponse *response; Handle_t hndlResponse = static_cast(params[1]); if ((err=handlesys->ReadHandle(hndlResponse, htHTTPResponseObject, &sec, (void **)&response)) != HandleError_None) @@ -302,6 +363,10 @@ const sp_nativeinfo_t curl_natives[] = {"HTTPClient.Timeout.set", SetClientTimeout}, {"HTTPClient.Timeout.get", GetClientTimeout}, + {"HTTPClient.ConnectTimeout.set", SetClientConnectTimeout}, + {"HTTPClient.ConnectTimeout.get", GetClientConnectTimeout}, + {"HTTPClient.FollowLocation.set", SetClientFollowLocation}, + {"HTTPClient.FollowLocation.get", GetClientFollowLocation}, {"HTTPClient.Delete", DeleteRequest}, {"HTTPResponse.Data.get", GetResponseData}, diff --git a/curlapi.h b/curlapi.h index 58fc609f0..e5342fdac 100644 --- a/curlapi.h +++ b/curlapi.h @@ -39,6 +39,8 @@ class HTTPClient void SetHeader(const char *name, const char *value); long timeout = 30L; + long follow_location = 1L; + long connect_timeout = 10L; private: const ke::AString baseURL; HTTPHeaderMap headers; diff --git a/curlthread.cpp b/curlthread.cpp index afe9d2625..760618bd9 100644 --- a/curlthread.cpp +++ b/curlthread.cpp @@ -54,10 +54,13 @@ static size_t WriteResponseBody(void *body, size_t size, size_t nmemb, void *use void HTTPRequestThread::RunThread(IThreadHandle *pHandle) { + char* szError = new char[256]; CURL *curl = curl_easy_init(); if (curl == NULL) { - smutils->LogError(myself, "Could not initialize cURL session."); + // smutils->LogError(myself, "Could not initialize cURL session."); + smutils->Format(szError, 256, "Could not initialize cURL session."); + g_RipExt.AddCallbackToQueue(HTTPRequestCallback(this->function, this->value, szError)); return; } @@ -90,9 +93,9 @@ void HTTPRequestThread::RunThread(IThreadHandle *pHandle) curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); curl_easy_setopt(curl, CURLOPT_CAINFO, caBundlePath); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, this->client->connect_timeout); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, this->client->follow_location); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curl, CURLOPT_READDATA, &this->request); @@ -109,7 +112,9 @@ void HTTPRequestThread::RunThread(IThreadHandle *pHandle) curl_slist_free_all(headers); free(this->request.body); - smutils->LogError(myself, "HTTP request failed: %s", error); + // smutils->LogError(myself, "HTTP request failed: %s", error); + smutils->Format(szError, 256, "%s", error); + g_RipExt.AddCallbackToQueue(HTTPRequestCallback(this->function, this->value, szError)); return; } @@ -121,4 +126,5 @@ void HTTPRequestThread::RunThread(IThreadHandle *pHandle) free(this->request.body); g_RipExt.AddCallbackToQueue(HTTPRequestCallback(this->function, response, this->value)); + delete []szError; } diff --git a/extension.cpp b/extension.cpp index 741a6d4a8..7d00273c3 100644 --- a/extension.cpp +++ b/extension.cpp @@ -110,21 +110,37 @@ void RipExt::RunFrame() IPluginFunction *function = callback.function; struct HTTPResponse response = callback.response; cell_t value = callback.value; + char* szError = callback.error; /* Function can be a null or not callable after processed request. */ if (function == NULL || !function->IsRunnable()) { smutils->LogError(myself, "Invalid function after processed request."); json_decref(response.data); + if (szError != nullptr) delete []szError; this->callbackMutex->Unlock(); return; } + /* Error handling from cURL Thread */ + if (szError != nullptr) { + function->PushCell(BAD_HANDLE); + function->PushCell(value); + function->PushString(szError); + function->Execute(NULL); + + json_decref(response.data); + delete []szError; + this->callbackMutex->Unlock(); + return; + } + HandleSecurity sec(NULL, myself->GetIdentity()); Handle_t hndlResponse = handlesys->CreateHandleEx(htHTTPResponseObject, &response, &sec, NULL, NULL); if (hndlResponse == BAD_HANDLE) { + json_decref(response.data); this->callbackMutex->Unlock(); smutils->LogError(myself, "Could not create HTTP response handle."); @@ -133,6 +149,7 @@ void RipExt::RunFrame() function->PushCell(hndlResponse); function->PushCell(value); + function->PushString(""); function->Execute(NULL); handlesys->FreeHandle(hndlResponse, &sec); diff --git a/extension.h b/extension.h index 985c1aa1f..80541bf16 100644 --- a/extension.h +++ b/extension.h @@ -67,11 +67,15 @@ struct HTTPResponse { struct HTTPRequestCallback { HTTPRequestCallback(IPluginFunction *function, struct HTTPResponse response, cell_t value) - : function(function), response(response), value(value) {} + : function(function), response(response), value(value), error(nullptr) {} + + HTTPRequestCallback(IPluginFunction *function, cell_t value, char* error) + : function(function), response(), value(value), error(error) {} IPluginFunction *function; struct HTTPResponse response; cell_t value; + char* error; }; diff --git a/pawn/scripting/include/ripext/http.inc b/pawn/scripting/include/ripext/http.inc index 422d7953d..108c1cebf 100644 --- a/pawn/scripting/include/ripext/http.inc +++ b/pawn/scripting/include/ripext/http.inc @@ -56,6 +56,7 @@ enum HTTPStatus typeset HTTPRequestCallback { function void (HTTPResponse response, any value); + function void (HTTPResponse response, any value, const char[] szError); }; methodmap HTTPResponse @@ -122,9 +123,21 @@ methodmap HTTPClient < Handle // @param value Optional value to pass to the callback function. public native void Delete(const char[] endpoint, HTTPRequestCallback callback, any value = 0); - // Connection timeout in seconds. + // Timeout in seconds. property int Timeout { public native get(); public native set(int iTimeout); } + + // Connection timeout in seconds. + property int ConnectTimeout { + public native get(); + public native set(int iConnectTimeout); + } + + // Follow location? (HTTP response code 30X) + property bool FollowLocation { + public native get(); + public native set(bool bFollowLocation); + } }; diff --git a/smsdk_config.h b/smsdk_config.h index 5e56aadf5..09235e0c7 100644 --- a/smsdk_config.h +++ b/smsdk_config.h @@ -30,7 +30,7 @@ /* Basic information exposed publicly */ #define SMEXT_CONF_NAME "REST in Pawn" #define SMEXT_CONF_DESCRIPTION "Provides HTTP and JSON natives for plugins" -#define SMEXT_CONF_VERSION "1.0.4" +#define SMEXT_CONF_VERSION "1.0.5" #define SMEXT_CONF_AUTHOR "Tsunami, CrazyHackGUT aka Kruzya" #define SMEXT_CONF_URL "https://kruzefag.ru/" #define SMEXT_CONF_LOGTAG "RIPEXT"