/// <summary> /// Update host's state /// </summary> /// <param name="tryableHost">A statefull host</param> /// <param name="response">Algolia's API response</param> /// <returns></returns> public RetryOutcomeType Decide(StatefulHost tryableHost, AlgoliaHttpResponse response) { lock (_lock) { if (!response.IsTimedOut && IsSuccess(response)) { tryableHost.Up = true; tryableHost.LastUse = DateTime.UtcNow; return(RetryOutcomeType.Success); } else if (!response.IsTimedOut && IsRetryable(response)) { tryableHost.Up = false; tryableHost.LastUse = DateTime.UtcNow; return(RetryOutcomeType.Retry); } else if (response.IsTimedOut) { tryableHost.Up = true; tryableHost.LastUse = DateTime.UtcNow; tryableHost.RetryCount++; return(RetryOutcomeType.Retry); } return(RetryOutcomeType.Failure); } }
/// <summary> /// Tells if the response is retryable or not /// </summary> /// <param name="response">Aloglia's API response</param> /// <returns></returns> private bool IsRetryable(AlgoliaHttpResponse response) { var isRetryableHttpCode = (int)Math.Floor((decimal)response.HttpStatusCode / 100) != 2 && (int)Math.Floor((decimal)response.HttpStatusCode / 100) != 4; return(isRetryableHttpCode || response.IsNetworkError); }
/// <summary> /// Call api with retry strategy /// </summary> /// <typeparam name="TResult">Return type</typeparam> /// <typeparam name="TData">Data type</typeparam> /// <param name="method">The HttpMethod <see cref="HttpMethod"/></param> /// <param name="uri">The endpoint URI</param> /// <param name="callType">The method Algolia's call type <see cref="CallType"/> </param> /// <param name="data">Your data</param> /// <param name="requestOptions">Add extra http header or query parameters to Algolia</param> /// <param name="ct">Optional cancellation token</param> public async Task <TResult> ExecuteRequestAsync <TResult, TData>(HttpMethod method, string uri, CallType callType, TData data = default, RequestOptions requestOptions = null, CancellationToken ct = default) where TResult : class where TData : class { if (string.IsNullOrWhiteSpace(uri)) { throw new ArgumentNullException(nameof(uri)); } if (method == null) { throw new ArgumentNullException(nameof(method)); } var request = new Request { Method = method, Headers = GenerateHeaders(requestOptions?.Headers), Compression = _algoliaConfig.Compression }; foreach (var host in _retryStrategy.GetTryableHost(callType)) { request.Body = CreateRequestContent(data, request.CanCompress); request.Uri = BuildUri(host.Url, uri, requestOptions?.QueryParameters); int requestTimeout = (requestOptions?.Timeout ?? GetTimeOut(callType)) * (host.RetryCount + 1); AlgoliaHttpResponse response = await _httpClient .SendRequestAsync(request, requestTimeout, ct) .ConfigureAwait(false); errorMessage = response.Error; switch (_retryStrategy.Decide(host, response)) { case RetryOutcomeType.Success: return(_serializer.Deserialize <TResult>(response.Body)); case RetryOutcomeType.Retry: continue; case RetryOutcomeType.Failure: throw new AlgoliaApiException(response.Error, response.HttpStatusCode); } } throw new AlgoliaUnreachableHostException("RetryStrategy failed to connect to Algolia. Reason: " + errorMessage); }
/// <summary> /// Tells if the response is a success or not /// </summary> /// <param name="response">Aloglia's API response</param> /// <returns></returns> private bool IsSuccess(AlgoliaHttpResponse response) { return((int)Math.Floor((decimal)response.HttpStatusCode / 100) == 2); }