11/*
2- * Copyright (C) 2019-2021 HERE Europe B.V.
2+ * Copyright (C) 2019-2020 HERE Europe B.V.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
@@ -49,7 +49,6 @@ struct RequestSettings {
4949 std::chrono::milliseconds accumulated_wait_time{0 };
5050 std::chrono::milliseconds current_backdown_period{0 };
5151 const std::chrono::milliseconds max_wait_time{0 };
52- olp::client::NetworkStatistics accumulated_statistics;
5352};
5453} // namespace
5554
@@ -91,6 +90,10 @@ HttpResponse ToHttpResponse(const http::SendOutcome& outcome) {
9190 http::ErrorCodeToString (outcome.GetErrorCode ())};
9291}
9392
93+ bool StatusSuccess (int status) {
94+ return status >= 0 && status < http::HttpStatusCode::BAD_REQUEST;
95+ }
96+
9497bool CaseInsensitiveCompare (const std::string& str1, const std::string& str2) {
9598 return (str1.size () == str2.size ()) &&
9699 std::equal (str1.begin (), str1.end (), str2.begin (),
@@ -134,13 +137,15 @@ void ExecuteSingleRequest(const std::shared_ptr<http::Network>& network,
134137 request, response_body,
135138 [=](const http::NetworkResponse& response) {
136139 auto status = response.GetStatus ();
137-
138- HttpResponse http_response{status, std::move (*response_body),
139- std::move (*headers)};
140- http_response.SetNetworkStatistics (
141- {response.GetBytesUploaded (), response.GetBytesDownloaded ()});
142-
143- callback (response.GetRequestId (), std::move (http_response));
140+ if (!StatusSuccess (status)) {
141+ response_body->str (
142+ response.GetError ().empty ()
143+ ? " Error occurred, please check HTTP status code"
144+ : response.GetError ());
145+ }
146+
147+ callback (response.GetRequestId (),
148+ {status, std::move (*response_body), std::move (*headers)});
144149 },
145150 [=](std::string key, std::string value) {
146151 headers->emplace_back (std::move (key), std::move (value));
@@ -153,7 +158,6 @@ void ExecuteSingleRequest(const std::shared_ptr<http::Network>& network,
153158 }
154159
155160 id = send_outcome.GetRequestId ();
156-
157161 return CancellationToken ([=] { network->Cancel (id); });
158162 };
159163
@@ -177,7 +181,6 @@ NetworkCallbackType GetRetryCallback(
177181 const NetworkRequestPtr& request) {
178182 return [=](const http::RequestId request_id, HttpResponse response) mutable {
179183 ++settings->current_try ;
180- settings->accumulated_statistics += response.GetNetworkStatistics ();
181184
182185 if (CheckRetryCondition (*settings, retry_settings, response)) {
183186 // Response is either successull or retries count/time expired
@@ -187,11 +190,9 @@ NetworkCallbackType GetRetryCallback(
187190 " Wrong response received, pending_request=%" PRIu64
188191 " , request_id=%" PRIu64,
189192 pending_request->GetRequestId (), request_id);
193+ return ;
190194 }
191195
192- // Set accumulated statistics for all retries
193- response.SetNetworkStatistics (settings->accumulated_statistics );
194-
195196 if (merge) {
196197 const auto & url = request->GetUrl ();
197198 pending_requests->OnRequestCompleted (request_id, url,
@@ -250,9 +251,79 @@ http::NetworkRequest::HttpVerb GetHttpVerb(const std::string& verb) {
250251 return http::NetworkRequest::HttpVerb::GET;
251252}
252253
254+ HttpResponse SendRequest (const http::NetworkRequest& request,
255+ const olp::client::OlpClientSettings& settings,
256+ const olp::client::RetrySettings& retry_settings,
257+ client::CancellationContext context) {
258+ struct ResponseData {
259+ Condition condition;
260+ http::NetworkResponse response{kCancelledErrorResponse };
261+ http::Headers headers;
262+ };
263+
264+ auto response_data = std::make_shared<ResponseData>();
265+ auto response_body = std::make_shared<std::stringstream>();
266+ http::SendOutcome outcome{http::ErrorCode::CANCELLED_ERROR};
267+ const auto timeout = std::chrono::seconds (retry_settings.timeout );
268+
269+ context.ExecuteOrCancelled (
270+ [&]() {
271+ outcome = settings.network_request_handler ->Send (
272+ request, response_body,
273+ [response_data](http::NetworkResponse response) {
274+ response_data->response = std::move (response);
275+ response_data->condition .Notify ();
276+ },
277+ [response_data](std::string key, std::string value) {
278+ response_data->headers .emplace_back (std::move (key),
279+ std::move (value));
280+ });
281+
282+ if (!outcome.IsSuccessful ()) {
283+ OLP_SDK_LOG_WARNING_F (kLogTag ,
284+ " SendRequest: sending request failed, url=%s" ,
285+ request.GetUrl ().c_str ());
286+ return CancellationToken ();
287+ }
288+
289+ auto request_handler = settings.network_request_handler ;
290+ auto request_id = outcome.GetRequestId ();
291+
292+ return CancellationToken ([=]() {
293+ request_handler->Cancel (request_id);
294+ response_data->condition .Notify ();
295+ });
296+ },
297+ [&]() { response_data->condition .Notify (); });
298+
299+ if (!outcome.IsSuccessful ()) {
300+ return ToHttpResponse (outcome);
301+ }
302+
303+ if (!response_data->condition .Wait (timeout)) {
304+ OLP_SDK_LOG_WARNING_F (kLogTag , " Request %" PRIu64 " timed out!" ,
305+ outcome.GetRequestId ());
306+ context.CancelOperation ();
307+ return ToHttpResponse (kTimeoutErrorResponse );
308+ }
309+
310+ if (context.IsCancelled ()) {
311+ return ToHttpResponse (kCancelledErrorResponse );
312+ }
313+
314+ HttpResponse response{response_data->response .GetStatus (),
315+ std::move (*response_body),
316+ std::move (response_data->headers )};
317+
318+ response.SetNetworkStatistics (GetStatistics (response_data->response ));
319+
320+ return response;
321+ }
322+
253323bool IsPending (const PendingUrlRequestPtr& request) {
254324 // A request is pending when it already triggered a Network call
255- return request && request->IsPending ();
325+ return request &&
326+ request->GetRequestId () != PendingUrlRequest::kInvalidRequestId ;
256327}
257328
258329} // namespace
@@ -466,49 +537,103 @@ HttpResponse OlpClient::OlpClientImpl::CallApi(
466537 std::string path, std::string method,
467538 OlpClient::ParametersType query_params,
468539 OlpClient::ParametersType header_params,
469- OlpClient::ParametersType forms_params,
540+ OlpClient::ParametersType /* forms_params*/ ,
470541 OlpClient::RequestBodyType post_body, std::string content_type,
471542 CancellationContext context) const {
472543 if (!settings_.network_request_handler ) {
473544 return HttpResponse (static_cast <int >(olp::http::ErrorCode::OFFLINE_ERROR),
474545 " Network request handler is empty." );
475546 }
476547
477- struct ResponseData {
478- Condition condition;
479- HttpResponse response = ToHttpResponse(kCancelledErrorResponse );
480- };
548+ const auto & retry_settings = settings_.retry_settings ;
549+ auto network_settings =
550+ http::NetworkSettings ()
551+ .WithTransferTimeout (retry_settings.timeout )
552+ .WithConnectionTimeout (retry_settings.timeout )
553+ .WithProxySettings (
554+ settings_.proxy_settings .value_or (http::NetworkProxySettings ()));
481555
482- auto response_data = std::make_shared<ResponseData>();
556+ auto network_request = http::NetworkRequest (
557+ utils::Url::Construct (GetBaseUrl (), path, query_params));
483558
484- auto callback = [=](HttpResponse http_response) {
485- response_data->response = std::move (http_response);
486- response_data->condition .Notify ();
487- };
559+ network_request.WithVerb (GetHttpVerb (method))
560+ .WithBody (std::move (post_body))
561+ .WithSettings (std::move (network_settings));
488562
489- context.ExecuteOrCancelled (
490- [&]() -> client::CancellationToken {
491- auto token = CallApi (path, method, query_params, header_params,
492- forms_params, post_body, content_type, callback);
493- return CancellationToken ([=] {
494- token.Cancel ();
495- response_data->condition .Notify ();
496- });
497- },
498- [=]() { response_data->condition .Notify (); });
563+ for (const auto & header : default_headers_) {
564+ network_request.WithHeader (header.first , header.second );
565+ }
499566
500- // We have to wait settings_.retry_settings.timeout for the initial request.
501- // And double this time for retries.
502- const auto timeout =
503- std::chrono::seconds (settings_.retry_settings .timeout *
504- (settings_.retry_settings .max_attempts ? 2 : 1 ));
505- if (!response_data->condition .Wait (timeout)) {
506- context.CancelOperation ();
507- return ToHttpResponse (kTimeoutErrorResponse );
567+ std::string custom_user_agent;
568+ for (const auto & header : header_params) {
569+ // Merge all User-Agent headers into one header.
570+ // This is required for (at least) iOS network implementation,
571+ // which uses headers dictionary without any duplicates.
572+ // User agents entries are usually separated by a whitespace, e.g.
573+ // Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Firefox/47.0
574+ if (CaseInsensitiveCompare (header.first , http::kUserAgentHeader )) {
575+ custom_user_agent += header.second + std::string (" " );
576+ } else {
577+ network_request.WithHeader (header.first , header.second );
578+ }
508579 }
509580
510- return response_data->response ;
511- } // namespace client
581+ custom_user_agent += http::kOlpSdkUserAgent ;
582+ network_request.WithHeader (http::kUserAgentHeader , custom_user_agent);
583+
584+ if (!content_type.empty ()) {
585+ network_request.WithHeader (http::kContentTypeHeader , content_type);
586+ }
587+
588+ auto backdown_period =
589+ std::chrono::milliseconds (retry_settings.initial_backdown_period );
590+
591+ AddBearer (query_params.empty (), network_request);
592+
593+ auto response =
594+ SendRequest (network_request, settings_, retry_settings, context);
595+
596+ NetworkStatistics accumulated_statistics = response.GetNetworkStatistics ();
597+
598+ // Make sure that we don't wait longer than `timeout` in retry settings
599+ auto accumulated_wait_time = backdown_period;
600+ const auto max_wait_time =
601+ std::chrono::duration_cast<std::chrono::milliseconds>(
602+ std::chrono::seconds (retry_settings.timeout ));
603+ for (int i = 1 ; i <= retry_settings.max_attempts && !context.IsCancelled () &&
604+ accumulated_wait_time < max_wait_time;
605+ i++) {
606+ if (StatusSuccess (response.status )) {
607+ return response;
608+ }
609+
610+ if (!retry_settings.retry_condition (response)) {
611+ return response;
612+ }
613+
614+ // do the periodical sleep and check for cancellation status in between.
615+ auto duration_to_sleep =
616+ std::min (backdown_period, max_wait_time - accumulated_wait_time);
617+ accumulated_wait_time += duration_to_sleep;
618+
619+ while (duration_to_sleep.count () > 0 && !context.IsCancelled ()) {
620+ const auto sleep_ms =
621+ std::min (std::chrono::milliseconds (1000 ), duration_to_sleep);
622+ std::this_thread::sleep_for (sleep_ms);
623+ duration_to_sleep -= sleep_ms;
624+ }
625+
626+ backdown_period = CalculateNextWaitTime (retry_settings, i);
627+ response = SendRequest (network_request, settings_, retry_settings, context);
628+
629+ // In case we retry, accumulate the stats
630+ accumulated_statistics += response.GetNetworkStatistics ();
631+ }
632+
633+ response.SetNetworkStatistics (accumulated_statistics);
634+
635+ return response;
636+ }
512637
513638OlpClient::OlpClient () : impl_(std::make_shared<OlpClientImpl>()){};
514639OlpClient::OlpClient (const OlpClientSettings& settings, std::string base_url)
0 commit comments