/// <summary> /// Send a CDM request with the retry logic helper function. /// </summary> /// <param name="cdmRequest">The CDM Http request.</param> /// <param name="callback">The callback that gets executed after the request finishes.</param> /// <returns>The <see cref="Task"/>, representing CDM Http response.</returns> private async Task <CdmHttpResponse> SendAsyncHelper(CdmHttpRequest cdmRequest, Callback callback = null, CdmCorpusContext ctx = null) { string fullUrl; if (isApiEndpointSet) { fullUrl = Combine(this.apiEndpoint, cdmRequest.RequestedUrl); } else { fullUrl = cdmRequest.RequestedUrl; } // If the number of retries is 0, we only try once, otherwise we retry the specified number of times. for (int retryNumber = 0; retryNumber <= cdmRequest.NumberOfRetries; retryNumber++) { var requestMessage = new HttpRequestMessage(cdmRequest.Method, fullUrl); foreach (var item in cdmRequest.Headers) { requestMessage.Headers.Add(item.Key, item.Value); } // GET requests might not have any content. if (cdmRequest.Content != null) { requestMessage.Content = new StringContent(cdmRequest.Content, Encoding.UTF8, cdmRequest.ContentType); } CdmHttpResponse cdmHttpResponse = null; var hasFailed = false; try { Task <HttpResponseMessage> request; DateTimeOffset startTime = DateTimeOffset.UtcNow; if (ctx != null) { Logger.Info(nameof(CdmHttpClient), ctx, $"Sending request {cdmRequest.RequestId}, request type: {requestMessage.Method}, request url: {cdmRequest.StripSasSig()}, retry number: {retryNumber}.", nameof(SendAsyncHelper)); } // The check is added to fix a known issue in .net http client when reading HEAD request > 2GB. // .net http client tries to write content even when the request is HEAD request. if (cdmRequest.Method.Equals(HttpMethod.Head)) { request = Task.Run(async() => await this.client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead)); } else { request = Task.Run(async() => await this.client.SendAsync(requestMessage)); } if (!request.Wait((TimeSpan)cdmRequest.Timeout)) { if (ctx != null && cdmRequest.Timeout != null) { Logger.Info(nameof(CdmHttpClient), ctx, $"Request {cdmRequest.RequestId} timeout after {cdmRequest.Timeout?.Seconds} s.", nameof(SendAsyncHelper)); } throw new CdmTimedOutException("Request timeout."); } HttpResponseMessage response = request.Result; if (ctx != null) { DateTimeOffset endTime = DateTimeOffset.UtcNow; Logger.Info(nameof(CdmHttpClient), ctx, $"Response for request {cdmRequest.RequestId} received, elapsed time: {endTime.Subtract(startTime).TotalMilliseconds} ms.", nameof(SendAsyncHelper)); } if (response != null) { cdmHttpResponse = new CdmHttpResponse(response.StatusCode) { Reason = response.ReasonPhrase, Content = response.Content, IsSuccessful = response.IsSuccessStatusCode }; foreach (var item in response.Headers) { cdmHttpResponse.ResponseHeaders.Add(item.Key, string.Join(",", item.Value)); } } } catch (Exception ex) { if (ex is AggregateException aggrEx) { ex = aggrEx.InnerException; } hasFailed = true; // Only throw an exception if another retry is not expected anymore. if (callback == null || retryNumber == cdmRequest.NumberOfRetries) { if (retryNumber != 0) { throw new CdmNumberOfRetriesExceededException(ex.Message); } else { throw ex; } } } // Check whether we have a callback function set and whether this is not our last retry. if (callback != null && retryNumber != cdmRequest.NumberOfRetries) { // Call the callback function with the retry numbers starting from 1. var waitTime = callback(cdmHttpResponse, hasFailed, retryNumber + 1); // Callback returned back that we do not want to retry anymore (probably successful request, client can set up what they want here). if (waitTime == null) { return(cdmHttpResponse); } else { // Sleep time specified by the callback. Thread.Sleep((int)waitTime.Value.TotalMilliseconds); } } else { // CDM Http Response exists, could be successful or bad (e.g. 403/404), it is up to caller to deal with it. if (cdmHttpResponse != null) { return(cdmHttpResponse); } else { if (retryNumber == 0) { return(null); } else { // If response doesn't exist repeatedly, just throw that the number of retries has exceeded (we don't have any other information). throw new CdmNumberOfRetriesExceededException(); } } } } // Should never come here, but just in case throw this exception. throw new CdmNumberOfRetriesExceededException(); }
/// <summary> /// Send a CDM request with the retry logic helper function. /// </summary> /// <param name="cdmRequest">The CDM Http request.</param> /// <param name="callback">The callback that gets executed after the request finishes.</param> /// <returns>The <see cref="Task"/>, representing CDM Http response.</returns> private async Task <CdmHttpResponse> SendAsyncHelper(CdmHttpRequest cdmRequest, Callback callback = null) { string fullUrl; if (isApiEndpointSet) { fullUrl = Combine(this.apiEndpoint, cdmRequest.RequestedUrl); } else { fullUrl = cdmRequest.RequestedUrl; } // If the number of retries is 0, we only try once, otherwise we retry the specified number of times. for (int retryNumber = 0; retryNumber <= cdmRequest.NumberOfRetries; retryNumber++) { var requestMessage = new HttpRequestMessage(cdmRequest.Method, fullUrl); foreach (var item in cdmRequest.Headers) { requestMessage.Headers.Add(item.Key, item.Value); } // GET requests might not have any content. if (cdmRequest.Content != null) { requestMessage.Content = new StringContent(cdmRequest.Content, Encoding.UTF8, cdmRequest.ContentType); } HttpResponseMessage response = null; CdmHttpResponse cdmHttpResponse = null; var cts = new CancellationTokenSource(); cts.CancelAfter((TimeSpan)cdmRequest.Timeout); var hasFailed = false; try { response = await this.client.SendAsync(requestMessage); cts.Token.ThrowIfCancellationRequested(); if (response != null) { cdmHttpResponse = new CdmHttpResponse(response.StatusCode) { Reason = response.ReasonPhrase, Content = response.Content, IsSuccessful = response.IsSuccessStatusCode }; foreach (var item in response.Headers) { cdmHttpResponse.ResponseHeaders.Add(item.Key, string.Join(",", item.Value)); } } } catch (Exception ex) { hasFailed = true; // Only throw an exception if another retry is not expected anymore. if (callback == null || retryNumber == cdmRequest.NumberOfRetries) { if (retryNumber != 0) { throw new CdmNumberOfRetriesExceededException(ex.Message); } else { throw (ex is OperationCanceledException) ? new CdmTimedOutException(ex.Message) : ex; } } } // Check whether we have a callback function set and whether this is not our last retry. if (callback != null && retryNumber != cdmRequest.NumberOfRetries) { // Call the callback function with the retry numbers starting from 1. var waitTime = callback(cdmHttpResponse, hasFailed, retryNumber + 1); // Callback returned back that we do not want to retry anymore (probably successful request, client can set up what they want here). if (waitTime == null) { return(cdmHttpResponse); } else { // Sleep time specified by the callback. System.Threading.Thread.Sleep((int)waitTime.Value.TotalMilliseconds); } } else { // CDM Http Response exists, could be successful or bad (e.g. 403/404), it is up to caller to deal with it. if (cdmHttpResponse != null) { return(cdmHttpResponse); } else { if (retryNumber == 0) { return(null); } else { // If response doesn't exist repeatedly, just throw that the number of retries has exceeded (we don't have any other information). throw new CdmNumberOfRetriesExceededException(); } } } } // Should never come here, but just in case throw this exception. throw new CdmNumberOfRetriesExceededException(); }