Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CURL support via scripting. #160

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ find_package(Threads REQUIRED)
find_package(Angelscript)
find_package(jsoncpp REQUIRED)
find_package(SocketW REQUIRED)

find_package(CURL)
cmake_dependent_option(RORSERVER_WITH_ANGELSCRIPT "Adds scripting support" ON "TARGET Angelscript::angelscript" OFF)
cmake_dependent_option(RORSERVER_WITH_CURL "Adds CURL request support (needs AngelScript)" ON "TARGET CURL:libcurl" OFF)
Copy link
Member

@AnotherFoxGuy AnotherFoxGuy Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the cause of CURL not being found

Suggested change
cmake_dependent_option(RORSERVER_WITH_CURL "Adds CURL request support (needs AngelScript)" ON "TARGET CURL:libcurl" OFF)
cmake_dependent_option(RORSERVER_WITH_CURL "Adds CURL request support (needs AngelScript)" ON "TARGET CURL::libcurl" OFF)


# setup paths
SET(RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/")
Expand Down
3 changes: 2 additions & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ def requirements(self):
self.requires("angelscript/2.37.0")
self.requires("jsoncpp/1.9.5")
self.requires("openssl/3.3.2", override=True)
self.requires("socketw/3.11.0@anotherfoxguy/stable")
self.requires("socketw/3.11.0@anotherfoxguy/stable")
self.requires("libcurl/8.10.1")
54 changes: 53 additions & 1 deletion contrib/example-script.as
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ have a single handler function (`setCallback()` replaces the previous).
int streamAdded(int uid, StreamRegister@ reg) ~ executed when player spawns an actor. Returns `broadcastType` which determines how the message is treated.
int playerChat(int uid, const string &in msg) ~ ONLY ONE AT A TIME ~ executed when player sends a chat message. Returns `broadcastType` which determines how the message is treated.
void gameCmd(int uid, const string &in cmd) ~ ONLY ONE AT A TIME ~ invoked when a script running on client calls `game.sendGameCmd()`
void curlStatus(curlStatusType type, int n1, int n2, string displayname, string message) ~ Provides progress and result info, see `server.curlRequestAsync()`; for CURL_STATUS_PROGRESS, n1 = bytes downloaded, n2 = total bytes; otherwise n1 = CURL return code, n2 = HTTP result code.


Constants and enumerations
Expand All @@ -39,6 +40,15 @@ enum broadcastType // This is returned by the `playerChat()/streamAdded()` callb
BROADCAST_BLOCK // no broadcast
};

enum curlStatusType // Used by `curlStatus()` callback.
{
CURL_STATUS_INVALID, //!< Should never be reported.
CURL_STATUS_START, //!< New CURL request started, n1/n2 both 0.
CURL_STATUS_PROGRESS, //!< Download in progress, n1 = bytes downloaded, n2 = total bytes.
CURL_STATUS_SUCCESS, //!< CURL request finished, n1 = CURL return code, n2 = HTTP result code, message = received payload.
CURL_STATUS_FAILURE, //!< CURL request finished, n1 = CURL return code, n2 = HTTP result code, message = CURL error string.
};

TO_ALL = -1 // constant for functions that receive an uid for sending something

*/
Expand All @@ -64,14 +74,20 @@ void main()
server.Log("Example server script loaded!");
}

const float TIME_LOG_CHUNK = 5.f;
float g_totalTime = 0.f;
float g_timeSinceLastMsg = 0.f;

// For CURL progress, don't log each received byte, just
const float CURL_LOG_CHUNK = 0.1; // Log at least in 10% steps.
float g_prevCurlProgress = 0.f;
float g_lastCurlLoggedProgress = 0.f;

// Optional, executed periodically, the parameter is delta time (time since last execution) in milliseconds.
void frameStep(float dt_millis)
{
// reset every 5 sec
if (g_timeSinceLastMsg >= 5.f)
if (g_timeSinceLastMsg >= TIME_LOG_CHUNK)
{
server.say("Example server script: frameStep(): total time is " + g_totalTime + " sec.", TO_ALL, FROM_SERVER);
g_timeSinceLastMsg = 0.f;
Expand Down Expand Up @@ -118,6 +134,38 @@ void gameCmd(int uid, const string &in cmd)
{
server.say("Example server script: gameCmd(): UID: " + uid + ", cmd: '" + cmd + "'.", TO_ALL, FROM_SERVER);
}

void curlStatus(curlStatusType type, int n1, int n2, string displayname, string message)
{
switch (type)
{
case CURL_STATUS_START:
g_prevCurlProgress = 0.f;
g_lastCurlLoggedProgress = 0.f;
server.say("Example server script: curlStatus(): type: CURL_STATUS_START, displayname: '" + displayname + "'", TO_ALL, FROM_SERVER);
break;

case CURL_STATUS_PROGRESS:
g_prevCurlProgress = float(n1)/float(n2);
if (g_prevCurlProgress - g_lastCurlLoggedProgress >= CURL_LOG_CHUNK)
{
server.say("Example server script: curlStatus(): type: CURL_STATUS_PROGRESS (" + (g_prevCurlProgress * 100) + "%)"
+ ", n1(bytes dl): " + n1 + ", n2(bytes total): " + n2 + ", displayname: '" + displayname + "'", TO_ALL, FROM_SERVER);
g_lastCurlLoggedProgress = g_prevCurlProgress;
}
break;

case CURL_STATUS_SUCCESS:
server.say("Example server script: curlStatus(): type: CURL_STATUS_SUCCESS"
+ ", n1(curl result): " + n1 + ", n2(HTTP result): " + n2 + ", displayname: '" + displayname + "', message(payload): '" + message + "'", TO_ALL, FROM_SERVER);
break;

case CURL_STATUS_FAILURE:
server.say("Example server script: curlStatus(): type: CURL_STATUS_FAILURE"
+ ", n1(curl result): " + n1 + ", n2(HTTP result): " + n2 + ", displayname: '" + displayname + "', message(CURL error): '" + message + "'", TO_ALL, FROM_SERVER);
break;
}
}

// ============================================================================
// Callback functions registered manually
Expand Down Expand Up @@ -162,6 +210,10 @@ int myStreamRegisteredCallback(int uid, StreamRegister@ reg)
int myChatMessageCallback(int uid, const string &in msg)
{
server.say("Example server script: myChatMessageCallback(): UID: " + uid + ", msg: '" + msg + "'.", TO_ALL, FROM_SERVER);
if (msg == "CURL test")
{
server.curlRequestAsync("https://www.rigsofrods.org", "rigsofrods.org");
}
return BROADCAST_NORMAL;
}

Expand Down
5 changes: 5 additions & 0 deletions source/server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ if (RORSERVER_WITH_ANGELSCRIPT)
target_link_libraries(${PROJECT_NAME} PRIVATE Angelscript::angelscript angelscript_addons)
endif ()

if (RORSERVER_WITH_CURL)
target_compile_definitions(${PROJECT_NAME} PRIVATE WITH_CURL)
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
endif ()

target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads SocketW::SocketW jsoncpp_lib)

IF (WIN32)
Expand Down
93 changes: 93 additions & 0 deletions source/server/CurlHelpers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
This source file is part of Rigs of Rods
Copyright 2005-2012 Pierre-Michel Ricordel
Copyright 2007-2012 Thomas Fischer
Copyright 2013-2023 Petr Ohlidal

For more information, see http://www.rigsofrods.org/

Rigs of Rods is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3, as
published by the Free Software Foundation.

Rigs of Rods 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 Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef WITH_CURL

#include "CurlHelpers.h"
#include "ScriptEngine.h"

#include <curl/curl.h>
#include <curl/easy.h>

#include <string>

static size_t CurlStringWriteFunc(void *ptr, size_t size, size_t nmemb, std::string* data)
{
data->append((char*)ptr, size * nmemb);
return size * nmemb;
}

static size_t CurlXferInfoFunc(void* ptr, curl_off_t filesize_B, curl_off_t downloaded_B, curl_off_t uploadsize_B, curl_off_t uploaded_B)
{
CurlTaskContext* context = (CurlTaskContext*)ptr;

context->ctc_script_engine->curlStatus(
CURL_STATUS_PROGRESS, (int)downloaded_B, (int)filesize_B, context->ctc_displayname, "");

// If you don't return 0, the transfer will be aborted - see the documentation
return 0;
}

bool GetUrlAsString(const std::string& url, CURLcode& curl_result, long& response_code, std::string& response_payload)
{
std::string response_header;
std::string user_agent = "Rigs of Rods Server";

CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
#ifdef _WIN32
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
#endif // _WIN32
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip");
curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlStringWriteFunc);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, CurlXferInfoFunc);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_payload);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response_header);

curl_result = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);

curl_easy_cleanup(curl);
curl = nullptr;

if (curl_result != CURLE_OK || response_code != 200)
{
return false;
}

return true;
}

bool CurlRequestThreadFunc(CurlTaskContext context)
{
context.ctc_script_engine->curlStatus(CURL_STATUS_START, 0, 0, context.ctc_displayname, "");
std::string data;
CURLcode curl_result = CURLE_OK;
long http_response = 0;
bool result = GetUrlAsString(context.ctc_url, /*out:*/curl_result, /*out:*/http_response, /*out:*/data);
CurlStatusType type = result ? CURL_STATUS_SUCCESS : CURL_STATUS_FAILURE;
context.ctc_script_engine->curlStatus(type, (int)curl_result, (int)http_response, context.ctc_displayname, curl_easy_strerror(curl_result));
return result;
}

#endif // WITH_CURL
57 changes: 57 additions & 0 deletions source/server/CurlHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
This file is part of "Rigs of Rods Server" (Relay mode)

Copyright 2007 Pierre-Michel Ricordel
Copyright 2014+ Rigs of Rods Community

"Rigs of Rods Server" 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.

"Rigs of Rods Server" 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 Foobar. If not, see <http://www.gnu.org/licenses/>.
*/


#pragma once

enum CurlStatusType
{
CURL_STATUS_INVALID, //!< Should never be reported.
CURL_STATUS_START, //!< New CURL request started, n1/n2 both 0.
CURL_STATUS_PROGRESS, //!< Download in progress, n1 = bytes downloaded, n2 = total bytes.
CURL_STATUS_SUCCESS, //!< CURL request finished, n1 = CURL return code, n2 = HTTP result code, message = received payload.
CURL_STATUS_FAILURE, //!< CURL request finished, n1 = CURL return code, n2 = HTTP result code, message = CURL error string.
};

#ifdef WITH_CURL

class ScriptEngine;

#include <curl/curl.h>
#include <curl/easy.h>

#include <string>


struct CurlTaskContext
{
std::string ctc_displayname;
std::string ctc_url;
ScriptEngine* ctc_script_engine;
// Status is reported via new server callback `curlStatus()`
};

bool GetUrlAsString(const std::string& url, CURLcode& curl_result, long& response_code, std::string& response_payload);

bool CurlRequestThreadFunc(CurlTaskContext task);



#endif // WITH_CURL
Loading
Loading