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 | |
12 | namespace cpr { |
13 | |
14 | MultiPerform::MultiPerform() : multicurl_(new CurlMultiHolder()) {} |
15 | |
16 | MultiPerform::~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 | |
30 | void 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 | |
56 | void 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 | |
80 | std::vector<std::pair<std::shared_ptr<Session>, MultiPerform::HttpMethod>>& MultiPerform::GetSessions() { |
81 | return sessions_; |
82 | } |
83 | |
84 | const std::vector<std::pair<std::shared_ptr<Session>, MultiPerform::HttpMethod>>& MultiPerform::GetSessions() const { |
85 | return sessions_; |
86 | } |
87 | |
88 | void 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 | |
109 | std::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: [¤t_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 | |
148 | std::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 | |
157 | std::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 | |
166 | void 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 | |
197 | void 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 | |
209 | void 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 | |
221 | void MultiPerform::SetHttpMethod(HttpMethod method) { |
222 | for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) { |
223 | pair.second = method; |
224 | } |
225 | } |
226 | |
227 | void MultiPerform::PrepareGet() { |
228 | SetHttpMethod(HttpMethod::GET_REQUEST); |
229 | PrepareSessions(); |
230 | } |
231 | |
232 | void MultiPerform::PrepareDelete() { |
233 | SetHttpMethod(HttpMethod::DELETE_REQUEST); |
234 | PrepareSessions(); |
235 | } |
236 | |
237 | void MultiPerform::PreparePut() { |
238 | SetHttpMethod(HttpMethod::PUT_REQUEST); |
239 | PrepareSessions(); |
240 | } |
241 | |
242 | void MultiPerform::PreparePatch() { |
243 | SetHttpMethod(HttpMethod::PATCH_REQUEST); |
244 | PrepareSessions(); |
245 | } |
246 | |
247 | void MultiPerform::PrepareHead() { |
248 | SetHttpMethod(HttpMethod::HEAD_REQUEST); |
249 | PrepareSessions(); |
250 | } |
251 | |
252 | void MultiPerform::PrepareOptions() { |
253 | SetHttpMethod(HttpMethod::OPTIONS_REQUEST); |
254 | PrepareSessions(); |
255 | } |
256 | |
257 | void MultiPerform::PreparePost() { |
258 | SetHttpMethod(HttpMethod::POST_REQUEST); |
259 | PrepareSessions(); |
260 | } |
261 | |
262 | std::vector<Response> MultiPerform::Get() { |
263 | PrepareGet(); |
264 | return MakeRequest(); |
265 | } |
266 | |
267 | std::vector<Response> MultiPerform::Delete() { |
268 | PrepareDelete(); |
269 | return MakeRequest(); |
270 | } |
271 | |
272 | std::vector<Response> MultiPerform::Put() { |
273 | PreparePut(); |
274 | return MakeRequest(); |
275 | } |
276 | |
277 | std::vector<Response> MultiPerform::Head() { |
278 | PrepareHead(); |
279 | return MakeRequest(); |
280 | } |
281 | |
282 | std::vector<Response> MultiPerform::Options() { |
283 | PrepareOptions(); |
284 | return MakeRequest(); |
285 | } |
286 | |
287 | std::vector<Response> MultiPerform::Patch() { |
288 | PreparePatch(); |
289 | return MakeRequest(); |
290 | } |
291 | |
292 | std::vector<Response> MultiPerform::Post() { |
293 | PreparePost(); |
294 | return MakeRequest(); |
295 | } |
296 | |
297 | std::vector<Response> MultiPerform::Perform() { |
298 | PrepareSessions(); |
299 | return MakeRequest(); |
300 | } |
301 | |
302 | std::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 | |
320 | std::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 | |
327 | void MultiPerform::AddInterceptor(const std::shared_ptr<InterceptorMulti>& pinterceptor) { |
328 | interceptors_.push(x: pinterceptor); |
329 | } |
330 | |
331 | } // namespace cpr |