// CurlModule implementation: HTTP GET and POST using libcurl #include "CurlModule.hpp" #include "Modules/ModuleManager.hpp" #include "Symbols/Value.hpp" #include #include #include #include #include // Callback for libcurl to write received data into a std::string static size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) { auto* buffer = static_cast(userdata); buffer->append(static_cast(ptr), size * nmemb); return size * nmemb; } // Register module functions void Modules::CurlModule::registerModule() { auto& mgr = Modules::ModuleManager::instance(); // Register HTTP GET: curlGet(url) mgr.registerFunction("curlGet", [this](const std::vector& args) -> Symbols::Value { return this->curlGet(args); }); // Register HTTP POST: curlPost(url, data) mgr.registerFunction("curlPost", [this](const std::vector& args) -> Symbols::Value { return this->curlPost(args); }); } Symbols::Value Modules::CurlModule::curlPost(const std::vector& args) { // curlPost: url, data [, options] if (args.size() < 2 || args.size() > 3) { throw std::runtime_error("curlPost: expects url, data, and optional options object"); } std::string url = Symbols::Value::to_string(args[0]); std::string data = Symbols::Value::to_string(args[1]); struct curl_slist *headers = nullptr; long timeoutSec = 0; bool follow = false; bool haveContentType = false; if (args.size() == 3) { using namespace Symbols; if (args[2].getType() != Variables::Type::OBJECT) { throw std::runtime_error("curlPost: options must be object"); } const auto & obj = std::get(args[2].get()); for (const auto & kv : obj) { const std::string & key = kv.first; const Value & v = kv.second; if (key == "timeout") { using namespace Variables; switch (v.getType()) { case Type::INTEGER: timeoutSec = v.get(); break; case Type::DOUBLE: timeoutSec = static_cast(v.get()); break; case Type::FLOAT: timeoutSec = static_cast(v.get()); break; default: throw std::runtime_error("curlPost: timeout must be number"); } } else if (key == "follow_redirects") { if (v.getType() != Variables::Type::BOOLEAN) { throw std::runtime_error("curlPost: follow_redirects must be boolean"); } follow = v.get(); } else if (key == "headers") { if (v.getType() != Variables::Type::OBJECT) { throw std::runtime_error("curlPost: headers must be object"); } const auto & hobj = std::get(v.get()); for (const auto & hk : hobj) { if (hk.second.getType() != Variables::Type::STRING) { throw std::runtime_error("curlPost: header values must be string"); } std::string hdr = hk.first + ": " + hk.second.get(); std::string lower = hk.first; std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); if (lower == "content-type") { haveContentType = true; } headers = curl_slist_append(headers, hdr.c_str()); } } else { throw std::runtime_error("curlPost: unknown option '" + key + "'"); } } } CURL * curl = curl_easy_init(); if (!curl) { if (headers) curl_slist_free_all(headers); throw std::runtime_error("curl: failed to initialize"); } std::string response; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); if (timeoutSec > 0) { curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeoutSec); } if (follow) { curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); } if (!haveContentType) { headers = curl_slist_append(headers, "Content-Type: application/json"); } if (headers) { curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); CURLcode res = curl_easy_perform(curl); if (headers) curl_slist_free_all(headers); if (res != CURLE_OK) { std::string error = curl_easy_strerror(res); curl_easy_cleanup(curl); throw std::runtime_error("curl: request failed: " + error); } curl_easy_cleanup(curl); return Symbols::Value(response); } Symbols::Value Modules::CurlModule::curlGet(const std::vector& args) { // curlGet: url [, options] if (args.size() < 1 || args.size() > 2) { throw std::runtime_error("curlGet: expects url and optional options object"); } std::string url = Symbols::Value::to_string(args[0]); // parse options struct curl_slist *headers = nullptr; long timeoutSec = 0; bool follow = false; if (args.size() == 2) { using namespace Symbols; if (args[1].getType() != Variables::Type::OBJECT) { throw std::runtime_error("curlGet: options must be object"); } const auto & obj = std::get(args[1].get()); for (const auto & kv : obj) { const std::string & key = kv.first; const Value & v = kv.second; if (key == "timeout") { using namespace Variables; switch (v.getType()) { case Type::INTEGER: timeoutSec = v.get(); break; case Type::DOUBLE: timeoutSec = static_cast(v.get()); break; case Type::FLOAT: timeoutSec = static_cast(v.get()); break; default: throw std::runtime_error("curlGet: timeout must be number"); } } else if (key == "follow_redirects") { if (v.getType() != Symbols::Variables::Type::BOOLEAN) { throw std::runtime_error("curlGet: follow_redirects must be boolean"); } follow = v.get(); } else if (key == "headers") { if (v.getType() != Symbols::Variables::Type::OBJECT) { throw std::runtime_error("curlGet: headers must be object"); } const auto & hobj = std::get(v.get()); for (const auto & hk : hobj) { if (hk.second.getType() != Symbols::Variables::Type::STRING) { throw std::runtime_error("curlGet: header values must be string"); } std::string line = hk.first + ": " + hk.second.get(); headers = curl_slist_append(headers, line.c_str()); } } else { throw std::runtime_error("curlGet: unknown option '" + key + "'"); } } } // initialize handle CURL * curl = curl_easy_init(); if (!curl) { if (headers) curl_slist_free_all(headers); throw std::runtime_error("curl: failed to initialize"); } std::string response; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); if (timeoutSec > 0) { curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeoutSec); } if (follow) { curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); } if (headers) { curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); CURLcode res = curl_easy_perform(curl); if (headers) { curl_slist_free_all(headers); } if (res != CURLE_OK) { std::string error = curl_easy_strerror(res); curl_easy_cleanup(curl); throw std::runtime_error("curl: request failed: " + error); } curl_easy_cleanup(curl); return Symbols::Value(response); }