libcpr/cpr/multiperform.cpp
2024-04-13 21:01:22 -04:00

331 lines
11 KiB
C++

#include "cpr/multiperform.h"
#include "cpr/interceptor.h"
#include "cpr/multipart.h"
#include "cpr/response.h"
#include "cpr/session.h"
#include <algorithm>
#include <iostream>
#include <memory>
#include <vector>
namespace cpr {
MultiPerform::MultiPerform() : multicurl_(new CurlMultiHolder()) {}
MultiPerform::~MultiPerform() {
// Unlock all sessions
for (const std::pair<std::shared_ptr<Session>, 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<int>(error_code) << std::endl;
return;
}
}
}
void MultiPerform::AddSession(std::shared_ptr<Session>& 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<int>(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>& 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<int>(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<std::shared_ptr<Session>, 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<std::pair<std::shared_ptr<Session>, MultiPerform::HttpMethod>>& MultiPerform::GetSessions() {
return sessions_;
}
const std::vector<std::pair<std::shared_ptr<Session>, 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<int>(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<int>(error_code) << std::endl;
break;
}
}
} while (still_running);
}
std::vector<Response> MultiPerform::ReadMultiInfo(std::function<Response(Session&, CURLcode)>&& complete_function) {
// Get infos and create Response objects
std::vector<Response> 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<std::shared_ptr<Session>, 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<Session> 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<Response> sorted_responses;
for (const std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
Session& current_session = *(pair.first);
auto it = std::find_if(responses.begin(), responses.end(), [&current_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<Response> MultiPerform::MakeRequest() {
if (!interceptors_.empty()) {
return intercept();
}
DoMultiPerform();
return ReadMultiInfo([](Session& session, CURLcode curl_error) -> Response { return session.Complete(curl_error); });
}
std::vector<Response> 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<std::shared_ptr<Session>, 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<std::shared_ptr<Session>, 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<std::shared_ptr<Session>, 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<std::shared_ptr<Session>, 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<Response> MultiPerform::Get() {
PrepareGet();
return MakeRequest();
}
std::vector<Response> MultiPerform::Delete() {
PrepareDelete();
return MakeRequest();
}
std::vector<Response> MultiPerform::Put() {
PreparePut();
return MakeRequest();
}
std::vector<Response> MultiPerform::Head() {
PrepareHead();
return MakeRequest();
}
std::vector<Response> MultiPerform::Options() {
PrepareOptions();
return MakeRequest();
}
std::vector<Response> MultiPerform::Patch() {
PreparePatch();
return MakeRequest();
}
std::vector<Response> MultiPerform::Post() {
PreparePost();
return MakeRequest();
}
std::vector<Response> MultiPerform::Perform() {
PrepareSessions();
return MakeRequest();
}
std::vector<Response> 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<std::shared_ptr<Session>, 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<Response> MultiPerform::intercept() {
// At least one interceptor exists -> Execute its intercept function
const std::shared_ptr<InterceptorMulti> interceptor = interceptors_.front();
interceptors_.pop();
return interceptor->intercept(*this);
}
void MultiPerform::AddInterceptor(const std::shared_ptr<InterceptorMulti>& pinterceptor) {
interceptors_.push(pinterceptor);
}
} // namespace cpr