Skip to content

Commit 66b8ad4

Browse files
Add sync OlpClient::CallApi and cover it with same suite as
async CallApi: - add testable polymorphic CallApi wrapper - add parametric tests over polymorph CallApi Relates-To: 1168 Signed-off-by: Diachenko Mykahilo <[email protected]>
1 parent 24fb3e2 commit 66b8ad4

File tree

3 files changed

+478
-342
lines changed

3 files changed

+478
-342
lines changed

olp-cpp-sdk-core/include/olp/core/client/OlpClient.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ class CORE_API OlpClient {
9292
const std::string& content_type,
9393
const NetworkAsyncCallback& callback) const;
9494

95+
HttpResponse CallApi(std::string path, std::string method,
96+
std::multimap<std::string, std::string> query_params,
97+
std::multimap<std::string, std::string> header_params,
98+
std::multimap<std::string, std::string> form_params,
99+
std::shared_ptr<std::vector<unsigned char>> post_body,
100+
std::string content_type,
101+
CancellationContext context) const;
102+
95103
private:
96104
std::shared_ptr<http::NetworkRequest> CreateRequest(
97105
const std::string& path, const std::string& method,

olp-cpp-sdk-core/src/client/OlpClient.cpp

Lines changed: 167 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,29 @@
2525
#include <sstream>
2626
#include <thread>
2727

28+
#include "olp/core/client/Condition.h"
29+
#include "olp/core/client/ErrorCode.h"
2830
#include "olp/core/http/NetworkConstants.h"
31+
#include "olp/core/logging/Log.h"
2932
#include "olp/core/utils/Url.h"
3033

3134
namespace olp {
3235
namespace client {
36+
constexpr auto kLogTag = "OlpClient";
37+
38+
static const auto kCancelledErrorResponse =
39+
http::NetworkResponse()
40+
.WithStatus(static_cast<int>(http::ErrorCode::CANCELLED_ERROR))
41+
.WithError("Operation Cancelled.");
42+
43+
static const auto kTimeoutErrorResponse =
44+
http::NetworkResponse()
45+
.WithStatus(static_cast<int>(http::ErrorCode::TIMEOUT_ERROR))
46+
.WithError("Network request timed out.");
47+
48+
HttpResponse ToHttpResponse(const http::NetworkResponse& response) {
49+
return {response.GetStatus(), response.GetError()};
50+
}
3351

3452
namespace {
3553
bool CaseInsensitiveCompare(const std::string& str1, const std::string& str2) {
@@ -125,6 +143,61 @@ http::NetworkRequest::HttpVerb GetHttpVerb(const std::string& verb) {
125143
return http_verb;
126144
}
127145

146+
HttpResponse SendRequest(const http::NetworkRequest& request,
147+
const olp::client::OlpClientSettings& settings,
148+
const olp::client::RetrySettings& retry_settings,
149+
client::CancellationContext context) {
150+
http::NetworkResponse network_response = kCancelledErrorResponse;
151+
auto interest_flag = std::make_shared<std::atomic_bool>(true);
152+
Condition condition{};
153+
auto response_body = std::make_shared<std::stringstream>();
154+
http::SendOutcome outcome{http::ErrorCode::CANCELLED_ERROR};
155+
156+
context.ExecuteOrCancelled(
157+
[&]() {
158+
outcome = settings.network_request_handler->Send(
159+
request, response_body,
160+
[&, interest_flag](http::NetworkResponse response) {
161+
if (interest_flag->exchange(false)) {
162+
network_response = std::move(response);
163+
condition.Notify();
164+
}
165+
});
166+
167+
return CancellationToken([&, interest_flag]() {
168+
if (interest_flag->exchange(false)) {
169+
settings.network_request_handler->Cancel(outcome.GetRequestId());
170+
network_response = kCancelledErrorResponse;
171+
network_response.WithRequestId(outcome.GetRequestId());
172+
condition.Notify();
173+
}
174+
});
175+
},
176+
[&condition]() { condition.Notify(); });
177+
178+
if (!outcome.IsSuccessful()) {
179+
return {static_cast<int>(outcome.GetErrorCode()),
180+
ErrorCodeToString(outcome.GetErrorCode())};
181+
}
182+
183+
if (!condition.Wait(std::chrono::seconds(retry_settings.timeout))) {
184+
OLP_SDK_LOG_INFO_F(kLogTag, "Timeout");
185+
context.CancelOperation();
186+
return ToHttpResponse(kTimeoutErrorResponse);
187+
}
188+
189+
// If request was cancelled before execution, we reach here without
190+
// calling callback or cancellation token, meaning interest_flag is still
191+
// true.
192+
interest_flag->store(false);
193+
if (context.IsCancelled()) {
194+
return ToHttpResponse(kCancelledErrorResponse);
195+
}
196+
197+
return {network_response.GetStatus(), std::move(*response_body)};
198+
}
199+
200+
bool StatusSuccess(int status) { return status >= 0 && status < 400; }
128201
} // anonymous namespace
129202

130203
OlpClient::OlpClient() {}
@@ -155,7 +228,8 @@ std::shared_ptr<http::NetworkRequest> OlpClient::CreateRequest(
155228
http::NetworkRequest::HttpVerb http_verb = GetHttpVerb(method);
156229
network_request->WithVerb(http_verb);
157230

158-
if (settings_.authentication_settings) {
231+
if (settings_.authentication_settings &&
232+
settings_.authentication_settings.get().provider) {
159233
std::string bearer = http::kBearer + std::string(" ") +
160234
settings_.authentication_settings.get().provider();
161235
network_request->WithHeader(http::kAuthorizationHeader, bearer);
@@ -198,7 +272,7 @@ NetworkAsyncCallback GetRetryCallback(
198272
const std::weak_ptr<CancellationContext>& weak_cancel_context) {
199273
++current_try;
200274
return [=](HttpResponse response) {
201-
if (current_try >= settings.max_attempts ||
275+
if (current_try > settings.max_attempts ||
202276
!settings.retry_condition(response)) {
203277
callback(std::move(response));
204278
} else {
@@ -224,8 +298,11 @@ NetworkAsyncCallback GetRetryCallback(
224298
"Operation Cancelled."));
225299
});
226300
} else {
227-
callback(HttpResponse(static_cast<int>(http::ErrorCode::UNKNOWN_ERROR),
228-
"Unknown error."));
301+
// Last (and only) strong reference lives in cancellation
302+
// token, which is reset on CancelOperation.
303+
callback(
304+
HttpResponse(static_cast<int>(http::ErrorCode::CANCELLED_ERROR),
305+
"Operation Cancelled."));
229306
}
230307
}
231308
};
@@ -286,5 +363,91 @@ CancellationToken OlpClient::CallApi(
286363
[cancel_context]() { cancel_context->CancelOperation(); });
287364
}
288365

366+
HttpResponse OlpClient::CallApi(
367+
std::string path, std::string method,
368+
std::multimap<std::string, std::string> query_params,
369+
std::multimap<std::string, std::string> header_params,
370+
std::multimap<std::string, std::string> forms_params,
371+
std::shared_ptr<std::vector<unsigned char>> post_body,
372+
std::string content_type, CancellationContext context) const {
373+
http::NetworkRequest network_request(
374+
olp::utils::Url::Construct(base_url_, path, query_params));
375+
376+
network_request.WithVerb(GetHttpVerb(method));
377+
378+
if (settings_.authentication_settings &&
379+
settings_.authentication_settings.get().provider) {
380+
std::string bearer = http::kBearer + std::string(" ") +
381+
settings_.authentication_settings.get().provider();
382+
network_request.WithHeader(http::kAuthorizationHeader, bearer);
383+
}
384+
385+
for (const auto& header : default_headers_) {
386+
network_request.WithHeader(header.first, header.second);
387+
}
388+
389+
std::string custom_user_agent;
390+
for (const auto& header : header_params) {
391+
// Merge all User-Agent headers into one header.
392+
// This is required for (at least) iOS network implementation,
393+
// which uses headers dictionary without any duplicates.
394+
// User agents entries are usually separated by a whitespace, e.g.
395+
// Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Firefox/47.0
396+
if (CaseInsensitiveCompare(header.first, http::kUserAgentHeader)) {
397+
custom_user_agent += header.second + std::string(" ");
398+
} else {
399+
network_request.WithHeader(header.first, header.second);
400+
}
401+
}
402+
403+
custom_user_agent += http::kOlpSdkUserAgent;
404+
network_request.WithHeader(http::kUserAgentHeader, custom_user_agent);
405+
406+
if (!content_type.empty()) {
407+
network_request.WithHeader(http::kContentTypeHeader, content_type);
408+
}
409+
410+
network_request.WithBody(post_body);
411+
412+
const auto& retry_settings = settings_.retry_settings;
413+
auto backdown_period = retry_settings.initial_backdown_period;
414+
415+
olp::http::NetworkSettings network_settings;
416+
417+
network_settings.WithTransferTimeout(retry_settings.timeout)
418+
.WithConnectionTimeout(retry_settings.timeout);
419+
420+
network_settings.WithProxySettings(
421+
settings_.proxy_settings.value_or(olp::http::NetworkProxySettings()));
422+
423+
network_request.WithSettings(std::move(network_settings));
424+
425+
auto response =
426+
SendRequest(network_request, settings_, retry_settings, context);
427+
428+
for (int i = 0; i < retry_settings.max_attempts && !context.IsCancelled();
429+
i++) {
430+
if (StatusSuccess(response.status)) {
431+
return response;
432+
}
433+
434+
if (!retry_settings.retry_condition(response)) {
435+
return response;
436+
}
437+
438+
// do the periodical sleep and check for cancellation status in between.
439+
auto duration_to_sleep = backdown_period;
440+
while (duration_to_sleep > 0 && !context.IsCancelled()) {
441+
auto sleep_ms = std::min(1000, duration_to_sleep);
442+
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
443+
duration_to_sleep -= sleep_ms;
444+
}
445+
446+
backdown_period = retry_settings.backdown_policy(backdown_period);
447+
response = SendRequest(network_request, settings_, retry_settings, context);
448+
}
449+
450+
return response;
451+
}
289452
} // namespace client
290453
} // namespace olp

0 commit comments

Comments
 (0)