From ae0aeff7ac5e3bda80547babdba9a26533d428d9 Mon Sep 17 00:00:00 2001 From: Aotrix <9aotrix@protonmail.com> Date: Sat, 31 Jan 2026 00:37:35 +0100 Subject: added parallelization for cards retrieval --- battleNetApi/include/hearthstoneApi.hpp | 1 + battleNetApi/src/hearthstoneApi.cpp | 61 +++++++++++++++++++++++++++++++++ battleNetApi/tests/tests.cpp | 43 +++++++++++++++++++++-- 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/battleNetApi/include/hearthstoneApi.hpp b/battleNetApi/include/hearthstoneApi.hpp index 72f5141..56ac53c 100644 --- a/battleNetApi/include/hearthstoneApi.hpp +++ b/battleNetApi/include/hearthstoneApi.hpp @@ -10,6 +10,7 @@ class HearthstoneApi { HearthstoneApi(const Token &iToken); HearthstoneApi(Token &&iToken); json makeApiRequest(const std::string &iUrl) const; + std::vector makeApiRequest(const std::vector &iUrls) const; private: std::string authorizationHeader(); diff --git a/battleNetApi/src/hearthstoneApi.cpp b/battleNetApi/src/hearthstoneApi.cpp index 945425f..1d2d870 100644 --- a/battleNetApi/src/hearthstoneApi.cpp +++ b/battleNetApi/src/hearthstoneApi.cpp @@ -1,6 +1,9 @@ #include "hearthstoneApi.hpp" #include +#include +#include #include +#include HearthstoneApi::HearthstoneApi(Token &&iToken) : mToken(iToken), mAuthorizationHeader(authorizationHeader()) {} HearthstoneApi::HearthstoneApi(const Token &iToken) : mToken(iToken), mAuthorizationHeader(authorizationHeader()) {} @@ -36,3 +39,61 @@ json HearthstoneApi::makeApiRequest(const std::string &iUrl) const { json aOutputJson = json::parse(aRequestOutput); return aOutputJson; } + +std::vector HearthstoneApi::makeApiRequest(const std::vector &iUrls) const { + std::vector aResponses(iUrls.size()); + CURLM *curlm = curl_multi_init(); + if (curlm) { + for (std::vector::size_type i{0}; i < aResponses.size(); ++i) { + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, iUrls[i].c_str()); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlCallbackStringOutput); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &(aResponses[i])); + struct curl_slist *chunk = nullptr; + chunk = curl_slist_append(chunk, mAuthorizationHeader.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + curl_multi_add_handle(curlm, curl); + } + const int kMaxWaitMs{5000}; + int aStillRunning{0}; + curl_multi_perform(curlm, &aStillRunning); + do { + int numfds = 0; + int res = curl_multi_wait(curlm, NULL, 0, kMaxWaitMs, &numfds); + if (res != CURLM_OK) { + std::cerr << std::format("error: curl_multi_wait() returned {}\n", res); + } + curl_multi_perform(curlm, &aStillRunning); + } while (aStillRunning); + + // clean-up + CURLMsg *aMessage{nullptr}; + CURL *aHandle{nullptr}; + int aMessagesLeft{0}; + CURLcode aReturnCode; + while ((aMessage = curl_multi_info_read(curlm, &aMessagesLeft))) { + if (aMessage->msg == CURLMSG_DONE) { + aHandle = aMessage->easy_handle; + aReturnCode = aMessage->data.result; + if (aReturnCode != CURLE_OK) { + fprintf(stderr, "CURL error code: %d\n", aMessage->data.result); + continue; + } + curl_multi_remove_handle(curlm, aHandle); + curl_easy_cleanup(aHandle); + aHandle = nullptr; + } else { + std::cerr << std::format("error: after curl_multi_info_read(), CURLMsg={}\n", + static_cast(aMessage->msg)); + } + } + curl_multi_cleanup(curlm); + } + std::vector aJsonOutputs; + for (std::string aResponse : aResponses) { + aJsonOutputs.push_back(json::parse(aResponse)); + } + return aJsonOutputs; +} diff --git a/battleNetApi/tests/tests.cpp b/battleNetApi/tests/tests.cpp index 062608e..39951bf 100644 --- a/battleNetApi/tests/tests.cpp +++ b/battleNetApi/tests/tests.cpp @@ -1,5 +1,12 @@ #include "apiUrl.hpp" #include "hearthstoneApi.hpp" +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -46,7 +53,7 @@ TEST_CASE("Retrieve cards", "[retrieveCardsOnce]") { REQUIRE(kApiResponse["cards"].size() == 16); } -TEST_CASE("Retrieve cards", "[retrieveCardsFourTimes]") { +TEST_CASE("Retrieve cards 10 times without parallelization", "[retrieveCardsNoParallel]") { const std::string kCredentialsFile{"tests/resources/credentials.json"}; Token aToken(kCredentialsFile); HearthstoneApi aHearthstoneApi(aToken); @@ -60,9 +67,10 @@ TEST_CASE("Retrieve cards", "[retrieveCardsFourTimes]") { json kApiResponse = aHearthstoneApi.makeApiRequest(aApiUrl()); // Retrieve number of pages + const int kNumberOfRetrievals{10}; const int kNumberOfPages{kApiResponse["pageCount"]}; - REQUIRE(kNumberOfPages >= 4); - for (int i{1}; i < 4; ++i) { + REQUIRE(kNumberOfPages >= kNumberOfRetrievals); + for (int i{1}; i < kNumberOfRetrievals; ++i) { REQUIRE(kApiResponse["cards"].size() == 16); aApiUrl.setPage(std::to_string(i + 1)); kApiResponse = aHearthstoneApi.makeApiRequest(aApiUrl()); @@ -70,3 +78,32 @@ TEST_CASE("Retrieve cards", "[retrieveCardsFourTimes]") { REQUIRE(kApiResponse["cards"].size() >= 1); REQUIRE(kApiResponse["cards"].size() <= 16); } + +TEST_CASE("Retrieve cards 30 times with parallelization", "[retrieveCardsParallel]") { + const std::string kCredentialsFile{"tests/resources/credentials.json"}; + Token aToken(kCredentialsFile); + HearthstoneApi aHearthstoneApi(aToken); + const std::string kContentType{"cards"}; + const std::string kRegion{"eu"}; + ApiUrl aApiUrl(kContentType, kRegion); + aApiUrl.setLocale("fr_FR"); + aApiUrl.setGameMode("constructed"); + aApiUrl.setPageSize("16"); + aApiUrl.setPage("1"); + std::vector aRequests; + json kFirstPageJson = aHearthstoneApi.makeApiRequest(aApiUrl()); + + int kNumberOfPages{kFirstPageJson["pageCount"]}; + const int kNumberOfRetrievals{30}; + REQUIRE(kNumberOfPages >= kNumberOfRetrievals); + for (int i{1}; i < kNumberOfRetrievals; ++i) { + aApiUrl.setPage(std::to_string(i + 1)); + aRequests.push_back(aApiUrl()); + } + std::vector kApiResponses = aHearthstoneApi.makeApiRequest(aRequests); + kApiResponses.emplace(kApiResponses.begin(), kFirstPageJson); + REQUIRE(kApiResponses.size() == kNumberOfRetrievals); + for (const json &kApiResponse : kApiResponses) { + REQUIRE(kApiResponse["cards"].size() == 16); + } +} -- cgit v1.2.3