spacebox/src/sb.cpp
Cocktail Frank 01d9fc61fc Add ability to make HTTP requests
Add an HTTP library to the `sb::cloud` namespace, mainly consisting of
an HTTP class and Request class. The HTTP class launches and manages a
queue of Request objects. Request objects are used to send HTTP GET and
POST requests. The library is implemented using lower level libraries -
Emscripten's Fetch API for WASM builds and cURL for all other builds.

Initialize both HTTP and Steam with a single function at
`sb:☁️:init`, and quit both HTTP and Steam with `sb:☁️:quit`.

Add an HTTP test program. For Emscripten HTTP tests, there is a
separate program built without Catch2 because Catch2 requires Asyncify,
which isn't compatible with Emscripten's Fetch API.

Add a cURL section to the test program's Makefile which handles
including cURL headers and linking to the cURL library on each
platform.

Pass version string into Ubuntu Docker builds of the test program, and
remove the connection between the container and the host's Git
directory because it is not necessary anymore.

Separate flags for building the test program for Emscripten with
Asyncify+Catch2 and Emscripten with the Fetch API.

Add documentation for the HTTP library and tests to the README.

Update the Ubuntu 18 Docker file to install libcurl with SSL.

Pack the Linux debug build into a ZIP archive like the regular build
after building, primarily so the Docker debug build can be pulled from
the container.

Add remote X connection to the Docker container build of the test
program, so the test program can access the display when run from
within the container.
2025-04-08 19:54:38 -04:00

256 lines
7.8 KiB
C++

/* +=======================================================+
____/ \____ /: Open source game framework licensed to freely use, :
\ / / : copy, and modify - created for dank.game :
+==\ ^__^ /==+ : :
: ~/ \~ : : Download at https://open.shampoo.ooo/shampoo/spacebox :
: ~~~~~~~~~~~~ : +=======================================================+
: SPACE ~~~~~ : /
: ~~~~~~~ BOX :/
+=============*/
#include "sb.hpp"
CLI::App sb::cli::_app {
"The options below can be used to configure the application at launch.\n"
};
#if defined(STEAM_ENABLED)
bool sb::initialize_steam = true;
#else
bool sb::initialize_steam = false;
#endif
/*!
* Because this is an unnamed namespace, this property is not directly accessible outside of this file, even if this
* header is included in another file.
*
* The property is used to implement private data outside of a class -- readable only by functions declared in the
* header which return the property as a const.
*/
namespace
{
std::vector<nlohmann::json> _cli_configs;
bool _cli_debug_config_load;
}
std::vector<std::string> sb::cli::_reserved = {"--config", "--config-file", "--debug-config-load"};
std::vector<std::string> sb::cli::_config_strings {};
std::vector<fs::path> sb::cli::_config_files {};
const CLI::App& sb::cli::app()
{
return _app;
}
const std::vector<std::string> sb::cli::reserved()
{
return _reserved;
}
void sb::cli::init()
{
/* Always start with no data parsed */
_config_files = {};
_config_strings = {};
_cli_configs = {};
try
{
_add("--config", _config_strings,
"JSON configuration to be loaded into the program. Any existing key values will be overwritten by the "
"values in this string. Multiple strings may be passed and each will be merged into the "
"configuration.");
_add("--config-file", _config_files,
"JSON file of configuration data. Any existing key values will be overwritten by the values in this "
"file. Multiple files may be passed and each will be merged into the configuration.");
_add_flag("--debug-config-load", _cli_debug_config_load,
"If this flag is passed, log statements will be printed to stdout during configuration loading, "
"regardless of the log settings in the configuration files. This can be useful when loading "
"multiple configuration files with conflicting log settings.");
}
catch (const CLI::OptionAlreadyAdded& error)
{
/* If either option is already added, sb::cli::init must have already ran since the reserved names are not
* able to be added from outside the class, so it's OK to ignore this error. */
}
}
void sb::cli::parse(int argc, char** argv)
{
/* Handles Unicode support on Windows */
argv = _app.ensure_utf8(argv);
if (argc > 0)
{
std::vector<std::string> command_line {argv, argv + argc};
parse(command_line);
}
}
void sb::cli::parse(std::vector<std::string>& command_line)
{
if (command_line.size() > 0)
{
try
{
/* CLI::App::parse expects a reversed vector */
std::vector<std::string> reversed {command_line.rbegin(), command_line.rend()};
_app.parse(reversed);
}
catch (const CLI::ParseError& error)
{
_app.exit(error);
throw CLI::ParseError(error);
}
}
}
void sb::cli::parse(std::initializer_list<std::string> _command_line)
{
std::vector<std::string> command_line {_command_line};
parse(command_line);
}
void sb::cli::_add_flag(const std::string& name, bool& storage, const std::string& description)
{
try
{
_app.add_flag(name, storage, description);
}
catch (const CLI::OptionAlreadyAdded& error)
{
std::ostringstream message;
message << "Error while trying to create option " << name << ". This option has already been added.";
throw CLI::OptionAlreadyAdded(message.str());
}
}
const std::vector<std::string>& sb::cli::config_strings()
{
return _config_strings;
}
const std::vector<fs::path>& sb::cli::config_files()
{
return _config_files;
}
void sb::init()
{
init({});
}
void sb::init(int argc, char** argv)
{
if (argc > 0)
{
std::vector<std::string> command_line {argv + 1, argv + argc};
init(command_line);
}
else
{
init();
}
}
void sb::init(std::initializer_list<std::string> _command_line)
{
std::vector<std::string> command_line {_command_line};
init(command_line);
}
void sb::init(std::vector<std::string>& command_line)
{
/* Changing directory is necessary for loading external resources in a macOS app bundle. This may be useful on other
* platforms too, so it may become default on all platforms and/or configurable in the future. */
#if defined(__APPLE__)
fs::current_path(SDL_GetBasePath());
#endif
/* This will make the framework's reserved options available in the CLI */
cli::init();
/* Parse the command line, and throw an exception if the help or version commands were passed. */
cli::parse(command_line);
/* Combine the --config and --config-file options into a single list of JSON objects. The --config flag takes
* priority. */
for (const fs::path& command_line_config_file : cli::config_files())
{
_cli_configs.push_back(json_from_file(command_line_config_file));
sb::Log::log("Parsed " + command_line_config_file.string(), sb::Log::DEBUG);
}
for (const std::string& command_line_config : cli::config_strings())
{
try
{
_cli_configs.push_back(nlohmann::json::parse(command_line_config));
sb::Log::log("Parsed " + command_line_config, sb::Log::DEBUG);
}
catch (const nlohmann::json::parse_error& error)
{
std::ostringstream message;
message << "Error parsing config string, \"" << command_line_config << "\": " << error.what();
/* ID 101 indicates a syntax error. See `json.hpp`. */
throw nlohmann::json::parse_error::create(101, error.byte, message.str(), nullptr);
}
}
#if defined(__EMSCRIPTEN__)
/* Check URL for config overrides in web builds. */
char* config_from_url = (char*) EM_ASM_PTR({
var parsed_url = new URL(window.location.href);
var config = parsed_url.searchParams.get("config");
if (config)
{
return stringToNewUTF8(config);
}
else
{
return stringToNewUTF8("\0");
}
});
if (config_from_url[0] != '\0')
{
sb::Log::Multi() << "Read config string from URL: " << config_from_url << sb::Log::end;
try
{
_cli_configs.push_back(nlohmann::json::parse(config_from_url));
}
catch (const nlohmann::json::parse_error& error)
{
std::ostringstream message;
message << "Error parsing config string, \"" << config_from_url << "\": " << error.what();
/* ID 101 indicates a syntax error. See `json.hpp`. */
throw nlohmann::json::parse_error::create(101, error.byte, message.str(), nullptr);
}
}
free(config_from_url);
#endif
/* If HTTP is enabled, the HTTP library will be initialized. If Steam is enabled, the Steam API will be initialized
* with callbacks checked every 0.5 seconds.
*
* sb::initialize_steam can be set to false to manually force Steam not to load. */
sb::cloud::init(0.5f, initialize_steam);
}
const std::vector<nlohmann::json> sb::cli_configs()
{
return _cli_configs;
}
bool sb::cli_debug_config_load()
{
return _cli_debug_config_load;
}
void sb::quit()
{
sb::cloud::quit();
}