#include "cpr/multiperform.h" #include "cpr/interceptor.h" #include "cpr/multipart.h" #include "cpr/response.h" #include "cpr/session.h" #include #include #include #include namespace cpr { MultiPerform::MultiPerform() : multicurl_(new CurlMultiHolder()) {} MultiPerform::~MultiPerform() { // Unlock all sessions for (const std::pair, HttpMethod>& pair : sessions_) { pair.first->isUsedInMultiPerform = false; // Remove easy handle from multi handle const CURLMcode error_code = curl_multi_remove_handle(multicurl_->handle, pair.first->curl_->handle); if (error_code) { std::cerr << "curl_multi_remove_handle() failed, code " << static_cast(error_code) << std::endl; return; } } } void MultiPerform::AddSession(std::shared_ptr& session, HttpMethod method) { // Check if this multiperform is download only if (((method != HttpMethod::DOWNLOAD_REQUEST && is_download_multi_perform) && method != HttpMethod::UNDEFINED) || (method == HttpMethod::DOWNLOAD_REQUEST && !is_download_multi_perform && !sessions_.empty())) { // Currently it is not possible to mix download and non-download methods, as download needs additional parameters throw std::invalid_argument("Failed to add session: Cannot mix download and non-download methods!"); } // Set download only if neccessary if (method == HttpMethod::DOWNLOAD_REQUEST) { is_download_multi_perform = true; } // Add easy handle to multi handle const CURLMcode error_code = curl_multi_add_handle(multicurl_->handle, session->curl_->handle); if (error_code) { std::cerr << "curl_multi_add_handle() failed, code " << static_cast(error_code) << std::endl; return; } // Lock session to the multihandle session->isUsedInMultiPerform = true; // Add session to sessions_ sessions_.emplace_back(session, method); } void MultiPerform::RemoveSession(const std::shared_ptr& session) { // Remove easy handle from multihandle const CURLMcode error_code = curl_multi_remove_handle(multicurl_->handle, session->curl_->handle); if (error_code) { std::cerr << "curl_multi_remove_handle() failed, code " << static_cast(error_code) << std::endl; return; } // Unock session session->isUsedInMultiPerform = false; // Remove session from sessions_ auto it = std::find_if(sessions_.begin(), sessions_.end(), [&session](const std::pair, HttpMethod>& pair) { return session->curl_->handle == pair.first->curl_->handle; }); if (it == sessions_.end()) { throw std::invalid_argument("Failed to find session!"); } sessions_.erase(it); // Reset download only if empty if (sessions_.empty()) { is_download_multi_perform = false; } } std::vector, MultiPerform::HttpMethod>>& MultiPerform::GetSessions() { return sessions_; } const std::vector, MultiPerform::HttpMethod>>& MultiPerform::GetSessions() const { return sessions_; } void MultiPerform::DoMultiPerform() { // Do multi perform until every handle has finished int still_running{0}; do { CURLMcode error_code = curl_multi_perform(multicurl_->handle, &still_running); if (error_code) { std::cerr << "curl_multi_perform() failed, code " << static_cast(error_code) << std::endl; break; } if (still_running) { const int timeout_ms{250}; error_code = curl_multi_poll(multicurl_->handle, nullptr, 0, timeout_ms, nullptr); if (error_code) { std::cerr << "curl_multi_poll() failed, code " << static_cast(error_code) << std::endl; break; } } } while (still_running); } std::vector MultiPerform::ReadMultiInfo(std::function&& complete_function) { // Get infos and create Response objects std::vector responses; struct CURLMsg* info{nullptr}; do { int msgq = 0; // Read info from multihandle info = curl_multi_info_read(multicurl_->handle, &msgq); if (info) { // Find current session auto it = std::find_if(sessions_.begin(), sessions_.end(), [&info](const std::pair, HttpMethod>& pair) { return pair.first->curl_->handle == info->easy_handle; }); if (it == sessions_.end()) { std::cerr << "Failed to find current session!" << std::endl; break; } const std::shared_ptr current_session = (*it).first; // Add response object // NOLINTNEXTLINE (cppcoreguidelines-pro-type-union-access) responses.push_back(complete_function(*current_session, info->data.result)); } } while (info); // Sort response objects to match order of added sessions std::vector sorted_responses; for (const std::pair, HttpMethod>& pair : sessions_) { Session& current_session = *(pair.first); auto it = std::find_if(responses.begin(), responses.end(), [¤t_session](const Response& response) { return current_session.curl_->handle == response.curl_->handle; }); const Response current_response = *it; // Erase response from original vector to increase future search speed responses.erase(it); sorted_responses.push_back(current_response); } return sorted_responses; } std::vector MultiPerform::MakeRequest() { if (!interceptors_.empty()) { return intercept(); } DoMultiPerform(); return ReadMultiInfo([](Session& session, CURLcode curl_error) -> Response { return session.Complete(curl_error); }); } std::vector MultiPerform::MakeDownloadRequest() { if (!interceptors_.empty()) { return intercept(); } DoMultiPerform(); return ReadMultiInfo([](Session& session, CURLcode curl_error) -> Response { return session.CompleteDownload(curl_error); }); } void MultiPerform::PrepareSessions() { for (const std::pair, HttpMethod>& pair : sessions_) { switch (pair.second) { case HttpMethod::GET_REQUEST: pair.first->PrepareGet(); break; case HttpMethod::POST_REQUEST: pair.first->PreparePost(); break; case HttpMethod::PUT_REQUEST: pair.first->PreparePut(); break; case HttpMethod::DELETE_REQUEST: pair.first->PrepareDelete(); break; case HttpMethod::PATCH_REQUEST: pair.first->PreparePatch(); break; case HttpMethod::HEAD_REQUEST: pair.first->PrepareHead(); break; case HttpMethod::OPTIONS_REQUEST: pair.first->PrepareOptions(); break; default: std::cerr << "PrepareSessions failed: Undefined HttpMethod or download without arguments!" << std::endl; return; } } } void MultiPerform::PrepareDownloadSession(size_t sessions_index, const WriteCallback& write) { const std::pair, HttpMethod>& pair = sessions_[sessions_index]; switch (pair.second) { case HttpMethod::DOWNLOAD_REQUEST: pair.first->PrepareDownload(write); break; default: std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << std::endl; return; } } void MultiPerform::PrepareDownloadSession(size_t sessions_index, std::ofstream& file) { const std::pair, HttpMethod>& pair = sessions_[sessions_index]; switch (pair.second) { case HttpMethod::DOWNLOAD_REQUEST: pair.first->PrepareDownload(file); break; default: std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << std::endl; return; } } void MultiPerform::SetHttpMethod(HttpMethod method) { for (std::pair, HttpMethod>& pair : sessions_) { pair.second = method; } } void MultiPerform::PrepareGet() { SetHttpMethod(HttpMethod::GET_REQUEST); PrepareSessions(); } void MultiPerform::PrepareDelete() { SetHttpMethod(HttpMethod::DELETE_REQUEST); PrepareSessions(); } void MultiPerform::PreparePut() { SetHttpMethod(HttpMethod::PUT_REQUEST); PrepareSessions(); } void MultiPerform::PreparePatch() { SetHttpMethod(HttpMethod::PATCH_REQUEST); PrepareSessions(); } void MultiPerform::PrepareHead() { SetHttpMethod(HttpMethod::HEAD_REQUEST); PrepareSessions(); } void MultiPerform::PrepareOptions() { SetHttpMethod(HttpMethod::OPTIONS_REQUEST); PrepareSessions(); } void MultiPerform::PreparePost() { SetHttpMethod(HttpMethod::POST_REQUEST); PrepareSessions(); } std::vector MultiPerform::Get() { PrepareGet(); return MakeRequest(); } std::vector MultiPerform::Delete() { PrepareDelete(); return MakeRequest(); } std::vector MultiPerform::Put() { PreparePut(); return MakeRequest(); } std::vector MultiPerform::Head() { PrepareHead(); return MakeRequest(); } std::vector MultiPerform::Options() { PrepareOptions(); return MakeRequest(); } std::vector MultiPerform::Patch() { PreparePatch(); return MakeRequest(); } std::vector MultiPerform::Post() { PreparePost(); return MakeRequest(); } std::vector MultiPerform::Perform() { PrepareSessions(); return MakeRequest(); } std::vector MultiPerform::proceed() { // Check if this multiperform mixes download and non download requests if (!sessions_.empty()) { const bool new_is_download_multi_perform = sessions_.front().second == HttpMethod::DOWNLOAD_REQUEST; for (const std::pair, HttpMethod>& s : sessions_) { const HttpMethod method = s.second; if ((new_is_download_multi_perform && method != HttpMethod::DOWNLOAD_REQUEST) || (!new_is_download_multi_perform && method == HttpMethod::DOWNLOAD_REQUEST)) { throw std::invalid_argument("Failed to proceed with session: Cannot mix download and non-download methods!"); } } is_download_multi_perform = new_is_download_multi_perform; } PrepareSessions(); return MakeRequest(); } std::vector MultiPerform::intercept() { // At least one interceptor exists -> Execute its intercept function const std::shared_ptr interceptor = interceptors_.front(); interceptors_.pop(); return interceptor->intercept(*this); } void MultiPerform::AddInterceptor(const std::shared_ptr& pinterceptor) { interceptors_.push(pinterceptor); } } // namespace cpr