1#include "cpr/multiperform.h"
2
3#include "cpr/interceptor.h"
4#include "cpr/multipart.h"
5#include "cpr/response.h"
6#include "cpr/session.h"
7#include <algorithm>
8#include <iostream>
9#include <memory>
10#include <vector>
11
12namespace cpr {
13
14MultiPerform::MultiPerform() : multicurl_(new CurlMultiHolder()) {}
15
16MultiPerform::~MultiPerform() {
17 // Unlock all sessions
18 for (const std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
19 pair.first->isUsedInMultiPerform = false;
20
21 // Remove easy handle from multi handle
22 const CURLMcode error_code = curl_multi_remove_handle(multi_handle: multicurl_->handle, curl_handle: pair.first->curl_->handle);
23 if (error_code) {
24 std::cerr << "curl_multi_remove_handle() failed, code " << static_cast<int>(error_code) << std::endl;
25 return;
26 }
27 }
28}
29
30void MultiPerform::AddSession(std::shared_ptr<Session>& session, HttpMethod method) {
31 // Check if this multiperform is download only
32 if (((method != HttpMethod::DOWNLOAD_REQUEST && is_download_multi_perform) && method != HttpMethod::UNDEFINED) || (method == HttpMethod::DOWNLOAD_REQUEST && !is_download_multi_perform && !sessions_.empty())) {
33 // Currently it is not possible to mix download and non-download methods, as download needs additional parameters
34 throw std::invalid_argument("Failed to add session: Cannot mix download and non-download methods!");
35 }
36
37 // Set download only if neccessary
38 if (method == HttpMethod::DOWNLOAD_REQUEST) {
39 is_download_multi_perform = true;
40 }
41
42 // Add easy handle to multi handle
43 const CURLMcode error_code = curl_multi_add_handle(multi_handle: multicurl_->handle, curl_handle: session->curl_->handle);
44 if (error_code) {
45 std::cerr << "curl_multi_add_handle() failed, code " << static_cast<int>(error_code) << std::endl;
46 return;
47 }
48
49 // Lock session to the multihandle
50 session->isUsedInMultiPerform = true;
51
52 // Add session to sessions_
53 sessions_.emplace_back(args&: session, args&: method);
54}
55
56void MultiPerform::RemoveSession(const std::shared_ptr<Session>& session) {
57 // Remove easy handle from multihandle
58 const CURLMcode error_code = curl_multi_remove_handle(multi_handle: multicurl_->handle, curl_handle: session->curl_->handle);
59 if (error_code) {
60 std::cerr << "curl_multi_remove_handle() failed, code " << static_cast<int>(error_code) << std::endl;
61 return;
62 }
63
64 // Unock session
65 session->isUsedInMultiPerform = false;
66
67 // Remove session from sessions_
68 auto it = std::find_if(first: sessions_.begin(), last: sessions_.end(), pred: [&session](const std::pair<std::shared_ptr<Session>, HttpMethod>& pair) { return session->curl_->handle == pair.first->curl_->handle; });
69 if (it == sessions_.end()) {
70 throw std::invalid_argument("Failed to find session!");
71 }
72 sessions_.erase(position: it);
73
74 // Reset download only if empty
75 if (sessions_.empty()) {
76 is_download_multi_perform = false;
77 }
78}
79
80std::vector<std::pair<std::shared_ptr<Session>, MultiPerform::HttpMethod>>& MultiPerform::GetSessions() {
81 return sessions_;
82}
83
84const std::vector<std::pair<std::shared_ptr<Session>, MultiPerform::HttpMethod>>& MultiPerform::GetSessions() const {
85 return sessions_;
86}
87
88void MultiPerform::DoMultiPerform() {
89 // Do multi perform until every handle has finished
90 int still_running{0};
91 do {
92 CURLMcode error_code = curl_multi_perform(multi_handle: multicurl_->handle, running_handles: &still_running);
93 if (error_code) {
94 std::cerr << "curl_multi_perform() failed, code " << static_cast<int>(error_code) << std::endl;
95 break;
96 }
97
98 if (still_running) {
99 const int timeout_ms{250};
100 error_code = curl_multi_wait(multi_handle: multicurl_->handle, extra_fds: nullptr, extra_nfds: 0, timeout_ms, ret: nullptr);
101 if (error_code) {
102 std::cerr << "curl_multi_wait() failed, code " << static_cast<int>(error_code) << std::endl;
103 break;
104 }
105 }
106 } while (still_running);
107}
108
109std::vector<Response> MultiPerform::ReadMultiInfo(std::function<Response(Session&, CURLcode)>&& complete_function) {
110 // Get infos and create Response objects
111 std::vector<Response> responses;
112 struct CURLMsg* info{nullptr};
113 do {
114 int msgq = 0;
115
116 // Read info from multihandle
117 info = curl_multi_info_read(multi_handle: multicurl_->handle, msgs_in_queue: &msgq);
118
119 if (info) {
120 // Find current session
121 auto it = std::find_if(first: sessions_.begin(), last: sessions_.end(), pred: [&info](const std::pair<std::shared_ptr<Session>, HttpMethod>& pair) { return pair.first->curl_->handle == info->easy_handle; });
122 if (it == sessions_.end()) {
123 std::cerr << "Failed to find current session!" << std::endl;
124 break;
125 }
126 const std::shared_ptr<Session> current_session = (*it).first;
127
128 // Add response object
129 // NOLINTNEXTLINE (cppcoreguidelines-pro-type-union-access)
130 responses.push_back(x: complete_function(*current_session, info->data.result));
131 }
132 } while (info);
133
134 // Sort response objects to match order of added sessions
135 std::vector<Response> sorted_responses;
136 for (const std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
137 Session& current_session = *(pair.first);
138 auto it = std::find_if(first: responses.begin(), last: responses.end(), pred: [&current_session](const Response& response) { return current_session.curl_->handle == response.curl_->handle; });
139 const Response current_response = *it;
140 // Erase response from original vector to increase future search speed
141 responses.erase(position: it);
142 sorted_responses.push_back(x: current_response);
143 }
144
145 return sorted_responses;
146}
147
148std::vector<Response> MultiPerform::MakeRequest() {
149 if (!interceptors_.empty()) {
150 return intercept();
151 }
152
153 DoMultiPerform();
154 return ReadMultiInfo(complete_function: [](Session& session, CURLcode curl_error) -> Response { return session.Complete(curl_error); });
155}
156
157std::vector<Response> MultiPerform::MakeDownloadRequest() {
158 if (!interceptors_.empty()) {
159 return intercept();
160 }
161
162 DoMultiPerform();
163 return ReadMultiInfo(complete_function: [](Session& session, CURLcode curl_error) -> Response { return session.CompleteDownload(curl_error); });
164}
165
166void MultiPerform::PrepareSessions() {
167 for (const std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
168 switch (pair.second) {
169 case HttpMethod::GET_REQUEST:
170 pair.first->PrepareGet();
171 break;
172 case HttpMethod::POST_REQUEST:
173 pair.first->PreparePost();
174 break;
175 case HttpMethod::PUT_REQUEST:
176 pair.first->PreparePut();
177 break;
178 case HttpMethod::DELETE_REQUEST:
179 pair.first->PrepareDelete();
180 break;
181 case HttpMethod::PATCH_REQUEST:
182 pair.first->PreparePatch();
183 break;
184 case HttpMethod::HEAD_REQUEST:
185 pair.first->PrepareHead();
186 break;
187 case HttpMethod::OPTIONS_REQUEST:
188 pair.first->PrepareOptions();
189 break;
190 default:
191 std::cerr << "PrepareSessions failed: Undefined HttpMethod or download without arguments!" << std::endl;
192 return;
193 }
194 }
195}
196
197void MultiPerform::PrepareDownloadSession(size_t sessions_index, const WriteCallback& write) {
198 const std::pair<std::shared_ptr<Session>, HttpMethod>& pair = sessions_[sessions_index];
199 switch (pair.second) {
200 case HttpMethod::DOWNLOAD_REQUEST:
201 pair.first->PrepareDownload(write);
202 break;
203 default:
204 std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << std::endl;
205 return;
206 }
207}
208
209void MultiPerform::PrepareDownloadSession(size_t sessions_index, std::ofstream& file) {
210 const std::pair<std::shared_ptr<Session>, HttpMethod>& pair = sessions_[sessions_index];
211 switch (pair.second) {
212 case HttpMethod::DOWNLOAD_REQUEST:
213 pair.first->PrepareDownload(file);
214 break;
215 default:
216 std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << std::endl;
217 return;
218 }
219}
220
221void MultiPerform::SetHttpMethod(HttpMethod method) {
222 for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) {
223 pair.second = method;
224 }
225}
226
227void MultiPerform::PrepareGet() {
228 SetHttpMethod(HttpMethod::GET_REQUEST);
229 PrepareSessions();
230}
231
232void MultiPerform::PrepareDelete() {
233 SetHttpMethod(HttpMethod::DELETE_REQUEST);
234 PrepareSessions();
235}
236
237void MultiPerform::PreparePut() {
238 SetHttpMethod(HttpMethod::PUT_REQUEST);
239 PrepareSessions();
240}
241
242void MultiPerform::PreparePatch() {
243 SetHttpMethod(HttpMethod::PATCH_REQUEST);
244 PrepareSessions();
245}
246
247void MultiPerform::PrepareHead() {
248 SetHttpMethod(HttpMethod::HEAD_REQUEST);
249 PrepareSessions();
250}
251
252void MultiPerform::PrepareOptions() {
253 SetHttpMethod(HttpMethod::OPTIONS_REQUEST);
254 PrepareSessions();
255}
256
257void MultiPerform::PreparePost() {
258 SetHttpMethod(HttpMethod::POST_REQUEST);
259 PrepareSessions();
260}
261
262std::vector<Response> MultiPerform::Get() {
263 PrepareGet();
264 return MakeRequest();
265}
266
267std::vector<Response> MultiPerform::Delete() {
268 PrepareDelete();
269 return MakeRequest();
270}
271
272std::vector<Response> MultiPerform::Put() {
273 PreparePut();
274 return MakeRequest();
275}
276
277std::vector<Response> MultiPerform::Head() {
278 PrepareHead();
279 return MakeRequest();
280}
281
282std::vector<Response> MultiPerform::Options() {
283 PrepareOptions();
284 return MakeRequest();
285}
286
287std::vector<Response> MultiPerform::Patch() {
288 PreparePatch();
289 return MakeRequest();
290}
291
292std::vector<Response> MultiPerform::Post() {
293 PreparePost();
294 return MakeRequest();
295}
296
297std::vector<Response> MultiPerform::Perform() {
298 PrepareSessions();
299 return MakeRequest();
300}
301
302std::vector<Response> MultiPerform::proceed() {
303 // Check if this multiperform mixes download and non download requests
304 if (!sessions_.empty()) {
305 const bool new_is_download_multi_perform = sessions_.front().second == HttpMethod::DOWNLOAD_REQUEST;
306
307 for (const std::pair<std::shared_ptr<Session>, HttpMethod>& s : sessions_) {
308 const HttpMethod method = s.second;
309 if ((new_is_download_multi_perform && method != HttpMethod::DOWNLOAD_REQUEST) || (!new_is_download_multi_perform && method == HttpMethod::DOWNLOAD_REQUEST)) {
310 throw std::invalid_argument("Failed to proceed with session: Cannot mix download and non-download methods!");
311 }
312 }
313 is_download_multi_perform = new_is_download_multi_perform;
314 }
315
316 PrepareSessions();
317 return MakeRequest();
318}
319
320std::vector<Response> MultiPerform::intercept() {
321 // At least one interceptor exists -> Execute its intercept function
322 const std::shared_ptr<InterceptorMulti> interceptor = interceptors_.front();
323 interceptors_.pop();
324 return interceptor->intercept(multi&: *this);
325}
326
327void MultiPerform::AddInterceptor(const std::shared_ptr<InterceptorMulti>& pinterceptor) {
328 interceptors_.push(x: pinterceptor);
329}
330
331} // namespace cpr