multiperform.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #include "cpr/multiperform.h"
  2. #include <algorithm>
  3. #include <iostream>
  4. namespace cpr {
  5. MultiPerform::MultiPerform() : multicurl_(new CurlMultiHolder()) {}
  6. MultiPerform::~MultiPerform() {
  7. // Unock all sessions
  8. for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
  9. pair.first->isUsedInMultiPerform = false;
  10. }
  11. }
  12. void MultiPerform::AddSession(std::shared_ptr<Session>& session, HttpMethod method) {
  13. // Check if this multiperform is download only
  14. if (((method != HttpMethod::DOWNLOAD_REQUEST && is_download_multi_perform) && method != HttpMethod::UNDEFINED) || (method == HttpMethod::DOWNLOAD_REQUEST && !is_download_multi_perform && !sessions_.empty())) {
  15. // Currently it is not possible to mix download and non-download methods, as download needs additional parameters
  16. throw std::invalid_argument("Failed to add session: Cannot mix download and non-download methods!");
  17. }
  18. // Set download only if neccessary
  19. if (method == HttpMethod::DOWNLOAD_REQUEST) {
  20. is_download_multi_perform = true;
  21. }
  22. // Add easy handle to multi handle
  23. CURLMcode error_code = curl_multi_add_handle(multicurl_->handle, session->curl_->handle);
  24. if (error_code) {
  25. std::cerr << "curl_multi_add_handle() failed, code " << static_cast<int>(error_code) << std::endl;
  26. return;
  27. }
  28. // Lock session to the multihandle
  29. session->isUsedInMultiPerform = true;
  30. // Add session to sessions_
  31. sessions_.emplace_back(session, method);
  32. }
  33. void MultiPerform::RemoveSession(const std::shared_ptr<Session>& session) {
  34. // Remove easy handle from multihandle
  35. CURLMcode error_code = curl_multi_remove_handle(multicurl_->handle, session->curl_->handle);
  36. if (error_code) {
  37. std::cerr << "curl_multi_remove_handle() failed, code " << static_cast<int>(error_code) << std::endl;
  38. return;
  39. }
  40. // Unock session
  41. session->isUsedInMultiPerform = false;
  42. // Remove session from sessions_
  43. 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; });
  44. if (it == sessions_.end()) {
  45. throw std::invalid_argument("Failed to find session!");
  46. }
  47. sessions_.erase(it);
  48. // Reset download only if empty
  49. if (sessions_.empty()) {
  50. is_download_multi_perform = false;
  51. }
  52. }
  53. void MultiPerform::DoMultiPerform() {
  54. // Do multi perform until every handle has finished
  55. int still_running{0};
  56. do {
  57. CURLMcode error_code = curl_multi_perform(multicurl_->handle, &still_running);
  58. if (error_code) {
  59. std::cerr << "curl_multi_perform() failed, code " << static_cast<int>(error_code) << std::endl;
  60. break;
  61. }
  62. if (still_running) {
  63. const int timeout_ms{250};
  64. error_code = curl_multi_poll(multicurl_->handle, nullptr, 0, timeout_ms, nullptr);
  65. if (error_code) {
  66. std::cerr << "curl_multi_poll() failed, code " << static_cast<int>(error_code) << std::endl;
  67. break;
  68. }
  69. }
  70. } while (still_running);
  71. }
  72. std::vector<Response> MultiPerform::ReadMultiInfo(std::function<Response(Session&, CURLcode)>&& complete_function) {
  73. // Get infos and create Response objects
  74. std::vector<Response> responses;
  75. struct CURLMsg* info{nullptr};
  76. do {
  77. int msgq = 0;
  78. // Read info from multihandle
  79. info = curl_multi_info_read(multicurl_->handle, &msgq);
  80. if (info) {
  81. // Find current session
  82. 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; });
  83. if (it == sessions_.end()) {
  84. std::cerr << "Failed to find current session!" << std::endl;
  85. break;
  86. }
  87. std::shared_ptr<Session> current_session = (*it).first;
  88. // Add response object
  89. // NOLINTNEXTLINE (cppcoreguidelines-pro-type-union-access)
  90. responses.push_back(complete_function(*current_session, info->data.result));
  91. }
  92. } while (info);
  93. // Sort response objects to match order of added sessions
  94. std::vector<Response> sorted_responses;
  95. for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
  96. Session& current_session = *(pair.first);
  97. auto it = std::find_if(responses.begin(), responses.end(), [&current_session](const Response& response) { return current_session.curl_->handle == response.curl_->handle; });
  98. Response current_response = *it;
  99. // Erase response from original vector to increase future search speed
  100. responses.erase(it);
  101. sorted_responses.push_back(current_response);
  102. }
  103. return sorted_responses;
  104. }
  105. std::vector<Response> MultiPerform::MakeRequest() {
  106. DoMultiPerform();
  107. return ReadMultiInfo([](Session& session, CURLcode curl_error) -> Response { return session.Complete(curl_error); });
  108. }
  109. std::vector<Response> MultiPerform::MakeDownloadRequest() {
  110. DoMultiPerform();
  111. return ReadMultiInfo([](Session& session, CURLcode curl_error) -> Response { return session.CompleteDownload(curl_error); });
  112. }
  113. void MultiPerform::PrepareSessions() {
  114. for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
  115. switch (pair.second) {
  116. case HttpMethod::GET_REQUEST:
  117. pair.first->PrepareGet();
  118. break;
  119. case HttpMethod::POST_REQUEST:
  120. pair.first->PreparePost();
  121. break;
  122. case HttpMethod::PUT_REQUEST:
  123. pair.first->PreparePut();
  124. break;
  125. case HttpMethod::DELETE_REQUEST:
  126. pair.first->PrepareDelete();
  127. break;
  128. case HttpMethod::PATCH_REQUEST:
  129. pair.first->PreparePatch();
  130. break;
  131. case HttpMethod::HEAD_REQUEST:
  132. pair.first->PrepareHead();
  133. break;
  134. case HttpMethod::OPTIONS_REQUEST:
  135. pair.first->PrepareOptions();
  136. break;
  137. default:
  138. std::cerr << "PrepareSessions failed: Undefined HttpMethod or download without arguments!" << std::endl;
  139. return;
  140. }
  141. }
  142. }
  143. void MultiPerform::PrepareDownloadSession(size_t sessions_index, const WriteCallback& write) {
  144. std::pair<std::shared_ptr<Session>, HttpMethod>& pair = sessions_[sessions_index];
  145. switch (pair.second) {
  146. case HttpMethod::DOWNLOAD_REQUEST:
  147. pair.first->PrepareDownload(write);
  148. break;
  149. default:
  150. std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << std::endl;
  151. return;
  152. }
  153. }
  154. void MultiPerform::PrepareDownloadSession(size_t sessions_index, std::ofstream& file) {
  155. std::pair<std::shared_ptr<Session>, HttpMethod>& pair = sessions_[sessions_index];
  156. switch (pair.second) {
  157. case HttpMethod::DOWNLOAD_REQUEST:
  158. pair.first->PrepareDownload(file);
  159. break;
  160. default:
  161. std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << std::endl;
  162. return;
  163. }
  164. }
  165. void MultiPerform::SetHttpMethod(HttpMethod method) {
  166. for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
  167. pair.second = method;
  168. }
  169. }
  170. void MultiPerform::PrepareGet() {
  171. SetHttpMethod(HttpMethod::GET_REQUEST);
  172. PrepareSessions();
  173. }
  174. void MultiPerform::PrepareDelete() {
  175. SetHttpMethod(HttpMethod::DELETE_REQUEST);
  176. PrepareSessions();
  177. }
  178. void MultiPerform::PreparePut() {
  179. SetHttpMethod(HttpMethod::PUT_REQUEST);
  180. PrepareSessions();
  181. }
  182. void MultiPerform::PreparePatch() {
  183. SetHttpMethod(HttpMethod::PATCH_REQUEST);
  184. PrepareSessions();
  185. }
  186. void MultiPerform::PrepareHead() {
  187. SetHttpMethod(HttpMethod::HEAD_REQUEST);
  188. PrepareSessions();
  189. }
  190. void MultiPerform::PrepareOptions() {
  191. SetHttpMethod(HttpMethod::OPTIONS_REQUEST);
  192. PrepareSessions();
  193. }
  194. void MultiPerform::PreparePost() {
  195. SetHttpMethod(HttpMethod::POST_REQUEST);
  196. PrepareSessions();
  197. }
  198. std::vector<Response> MultiPerform::Get() {
  199. PrepareGet();
  200. return MakeRequest();
  201. }
  202. std::vector<Response> MultiPerform::Delete() {
  203. PrepareDelete();
  204. return MakeRequest();
  205. }
  206. std::vector<Response> MultiPerform::Put() {
  207. PreparePut();
  208. return MakeRequest();
  209. }
  210. std::vector<Response> MultiPerform::Head() {
  211. PrepareHead();
  212. return MakeRequest();
  213. }
  214. std::vector<Response> MultiPerform::Options() {
  215. PrepareOptions();
  216. return MakeRequest();
  217. }
  218. std::vector<Response> MultiPerform::Patch() {
  219. PreparePatch();
  220. return MakeRequest();
  221. }
  222. std::vector<Response> MultiPerform::Post() {
  223. PreparePost();
  224. return MakeRequest();
  225. }
  226. std::vector<Response> MultiPerform::Perform() {
  227. PrepareSessions();
  228. return MakeRequest();
  229. }
  230. } // namespace cpr