public async Task HttpRetryHandler_404VerifySingleHit() { // Arrange var hits = 0; Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { hits++; return(new HttpResponseMessage(HttpStatusCode.OK)); }; var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = MaxTries, RequestTimeout = Timeout.InfiniteTimeSpan, RetryDelay = TimeSpan.Zero }; var log = new TestLogger(); // Act using (var response = await retryHandler.SendAsync(request, log, CancellationToken.None)) { // Assert Assert.Equal(1, hits); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } }
public async Task HttpRetryHandler_ThrowsTimeoutExceptionForTimeout() { // Arrange Func <HttpRequestMessage, CancellationToken, Task <HttpResponseMessage> > handler = async(requestMessage, token) => { await Task.Delay(LargeTimeout); return(new HttpResponseMessage(HttpStatusCode.OK)); }; var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = 1, RequestTimeout = TimeSpan.Zero, RetryDelay = TimeSpan.Zero }; // Act Func <Task> actionAsync = () => retryHandler.SendAsync( request, new TestLogger(), CancellationToken.None); // Assert await Assert.ThrowsAsync <TimeoutException>(actionAsync); }
private static async Task <T> ThrowsException <T>(ITestServer server) where T : Exception { return(await server.ExecuteAsync(async address => { // Arrange var retryHandler = new HttpRetryHandler(); var countingHandler = new CountingHandler { InnerHandler = new HttpClientHandler() }; var httpClient = new HttpClient(countingHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, address)) { MaxTries = 2, RetryDelay = TimeSpan.Zero }; // Act Func <Task> actionAsync = () => retryHandler.SendAsync( request, new TestLogger(), CancellationToken.None); // Act & Assert var exception = await Assert.ThrowsAsync <T>(actionAsync); Assert.Equal(2, countingHandler.Hits); return exception; })); }
public async Task HttpRetryHandler_DifferentRequestInstanceEachTime() { // Arrange var requests = new HashSet <HttpRequestMessage>(); Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { requests.Add(requestMessage); return(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); }; var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = MaxTries, RequestTimeout = Timeout.InfiniteTimeSpan, RetryDelay = TimeSpan.Zero }; var log = new TestLogger(); // Act using (await retryHandler.SendAsync(request, log, CancellationToken.None)) { } // Assert Assert.Equal(MaxTries, requests.Count); }
public async Task HttpRetryHandler_EnhancedRetryAllowsSettingMoreRetries() { // Arrange var tries = 0; var sent503 = false; Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { tries++; // Return 503 for the first 2 tries if (tries > 10) { return(new HttpResponseMessage(HttpStatusCode.OK)); } else { sent503 = true; return(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); } }; TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( new Dictionary <string, string>() { [EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = bool.TrueString, [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = "11", [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = "3" }); EnhancedHttpRetryHelper helper = new EnhancedHttpRetryHelper(testEnvironmentVariableReader); Assert.Equal(helper.IsEnabled, true); // Enhanced retry mode causes a random 0-199 ms jitter so we can't time it in this test // but we can make sure the setting got through Assert.Equal(helper.DelayInMilliseconds, 3); Assert.Equal(helper.RetryCount, 11); var retryHandler = new HttpRetryHandler(testEnvironmentVariableReader); var testHandler = new HttpRetryTestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = helper.RetryCount, RequestTimeout = Timeout.InfiniteTimeSpan, // HttpRetryHandler will override with values from NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS // so set this to a value that will cause test timeout if the correct value is not honored. RetryDelay = TimeSpan.FromMilliseconds(int.MaxValue) // = about 24 days }; var log = new TestLogger(); // Act using (var response = await retryHandler.SendAsync(request, log, CancellationToken.None)) { // Assert Assert.True(sent503); Assert.Equal(11, tries); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } }
public async Task HttpRetryHandler_AddHeaders() { // Arrange var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(); using (var httpClient = new HttpClient(testHandler)) { var request = new HttpRetryHandlerRequest( httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)); var id = Guid.NewGuid().ToString(); request.AddHeaders.Add(new KeyValuePair <string, IEnumerable <string> >(ProtocolConstants.SessionId, new[] { id })); var log = new TestLogger(); // Act using (var actualResponse = await retryHandler.SendAsync(request, log, CancellationToken.None)) { // Assert testHandler.LastRequest.Headers.GetValues(ProtocolConstants.SessionId) .FirstOrDefault() .Should() .Be(id); } } }
public async Task HttpSource_AllowsCustomResponseTimeout() { // Arrange using (var td = TestDirectory.Create()) { var tc = new TestContext(td); HttpRetryHandlerRequest actualRequest = null; tc.SetResponseFactory(request => { actualRequest = request; var response = new HttpResponseMessage(HttpStatusCode.OK); return(Task.FromResult(response)); }); var timeout = TimeSpan.FromMinutes(5); // Act await tc.HttpSource.ProcessResponseAsync( new HttpSourceRequest(() => new HttpRequestMessage(HttpMethod.Get, tc.Url)) { RequestTimeout = timeout, }, response => { return(Task.FromResult(0)); }, tc.Logger, CancellationToken.None); // Assert Assert.NotNull(actualRequest); Assert.Equal(timeout, actualRequest.RequestTimeout); } }
public async Task HttpRetryHandler_MultipleTriesNoSuccess() { // Arrange var hits = 0; Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { hits++; return(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); }; var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = MaxTries, RequestTimeout = Timeout.InfiniteTimeSpan, RetryDelay = TimeSpan.Zero }; var log = new TestLogger(); // Act using (await retryHandler.SendAsync(request, log, CancellationToken.None)) { } // Assert Assert.Equal(MaxTries, hits); }
public async Task HttpSource_HasDefaultResponseTimeout() { // Arrange using (var td = TestFileSystemUtility.CreateRandomTestFolder()) { var tc = new TestContext(td); HttpRetryHandlerRequest actualRequest = null; tc.SetResponseFactory(request => { actualRequest = request; var response = new HttpResponseMessage(HttpStatusCode.OK); return(Task.FromResult(response)); }); // Act await tc.HttpSource.ProcessResponseAsync( new HttpSourceRequest(() => new HttpRequestMessage(HttpMethod.Get, tc.Url)), response => { return(Task.FromResult(0)); }, tc.Logger, CancellationToken.None); // Assert Assert.NotNull(actualRequest); Assert.Equal(TimeSpan.FromSeconds(100), actualRequest.RequestTimeout); } }
public async Task HttpRetryHandler_MultipleTriesUntilSuccess() { // Arrange TimeSpan retryDelay = TimeSpan.Zero; TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( new Dictionary <string, string>() { [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() }); var tries = 0; var sent503 = false; Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { tries++; // Return 503 for the first 2 tries if (tries > 2) { return(new HttpResponseMessage(HttpStatusCode.OK)); } else { sent503 = true; return(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); } }; var retryHandler = new HttpRetryHandler(testEnvironmentVariableReader); var testHandler = new HttpRetryTestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = MaxTries, RequestTimeout = Timeout.InfiniteTimeSpan, RetryDelay = retryDelay }; var log = new TestLogger(); // Act using (var response = await retryHandler.SendAsync(request, log, CancellationToken.None)) { // Assert Assert.True(sent503); Assert.Equal(3, tries); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } }
public async Task HttpRetryHandler_AppliesTimeoutToRequestsIndividually() { // Arrange // 20 requests that take 250ms each for a total of 5 seconds (plus noise). var requestDuration = TimeSpan.FromMilliseconds(250); var maxTries = 20; var retryDelay = TimeSpan.Zero; TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( new Dictionary <string, string>() { [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = maxTries.ToString(), [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() }); // Make the request timeout longer than each request duration but less than the total // duration of all attempts. var requestTimeout = TimeSpan.FromMilliseconds(4000); var hits = 0; Func <HttpRequestMessage, CancellationToken, Task <HttpResponseMessage> > handler = async(requestMessage, token) => { hits++; await Task.Delay(requestDuration); return(new HttpResponseMessage(HttpStatusCode.InternalServerError)); }; var retryHandler = new HttpRetryHandler(testEnvironmentVariableReader); var testHandler = new HttpRetryTestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = maxTries, RequestTimeout = requestTimeout, RetryDelay = retryDelay }; var log = new TestLogger(); // Act using (await retryHandler.SendAsync(request, log, CancellationToken.None)) { } // Assert Assert.Equal(maxTries, hits); }
public async Task HttpRetryHandler_MultipleTriesTimed() { // Arrange TimeSpan retryDelay = SmallTimeout; TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( new Dictionary <string, string>() { [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() }); Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { return(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); }; var minTime = GetRetryMinTime(MaxTries, SmallTimeout); var retryHandler = new HttpRetryHandler(testEnvironmentVariableReader); var testHandler = new HttpRetryTestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = MaxTries, RequestTimeout = Timeout.InfiniteTimeSpan, RetryDelay = retryDelay }; var log = new TestLogger(); // Act var timer = new Stopwatch(); timer.Start(); using (await retryHandler.SendAsync(request, log, CancellationToken.None)) { } timer.Stop(); // Assert Assert.True( timer.Elapsed >= minTime, $"Expected this to take at least: {minTime} But it finished in: {timer.Elapsed}"); }
public async Task HttpRetryHandler_TimesOutDownload() { // Arrange var hits = 0; var memoryStream = new MemoryStream(Encoding.ASCII.GetBytes("foobar")); var expectedMilliseconds = 50; Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { hits++; return(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(new SlowStream(memoryStream) { DelayPerByte = TimeSpan.FromSeconds(1) }) }); }; var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { DownloadTimeout = TimeSpan.FromMilliseconds(expectedMilliseconds) }; var destinationStream = new MemoryStream(); var log = new TestLogger(); // Act using (var response = await retryHandler.SendAsync(request, log, CancellationToken.None)) using (var stream = await response.Content.ReadAsStreamAsync()) { var actual = await Assert.ThrowsAsync <IOException>(() => stream.CopyToAsync(destinationStream)); // Assert Assert.Equal(1, hits); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.IsType <TimeoutException>(actual.InnerException); Assert.EndsWith( $"timed out because no data was received for {expectedMilliseconds}ms.", actual.Message); } }
public async Task HttpRetryHandler_MultipleTriesUntilSuccess() { // Arrange var tries = 0; var sent503 = false; Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { tries++; // Return 503 for the first 2 tries if (tries > 2) { return(new HttpResponseMessage(HttpStatusCode.OK)); } else { sent503 = true; return(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); } }; var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = MaxTries, RequestTimeout = Timeout.InfiniteTimeSpan, RetryDelay = TimeSpan.Zero }; var log = new TestLogger(); // Act using (var response = await retryHandler.SendAsync(request, log, CancellationToken.None)) { // Assert Assert.True(sent503); Assert.Equal(3, tries); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } }
public async Task HttpRetryHandler_CancelsRequestAfterTimeout() { // Arrange TimeSpan retryDelay = TimeSpan.Zero; TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( new Dictionary <string, string>() { [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() }); var requestToken = CancellationToken.None; Func <HttpRequestMessage, CancellationToken, Task <HttpResponseMessage> > handler = async(requestMessage, token) => { requestToken = token; await Task.Delay(LargeTimeout, token); return(new HttpResponseMessage(HttpStatusCode.OK)); }; var retryHandler = new HttpRetryHandler(testEnvironmentVariableReader); var testHandler = new HttpRetryTestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = 1, RequestTimeout = SmallTimeout, RetryDelay = retryDelay }; // Act Func <Task> actionAsync = () => retryHandler.SendAsync( request, new TestLogger(), CancellationToken.None); // Assert await Assert.ThrowsAsync <TimeoutException>(actionAsync); Assert.True(requestToken.CanBeCanceled); Assert.True(requestToken.IsCancellationRequested); }
public async Task HttpRetryHandler_ReturnsContentHeaders() { // Arrange var retryHandler = new HttpRetryHandler(); using (var httpClient = new HttpClient()) { var request = new HttpRetryHandlerRequest( httpClient, () => new HttpRequestMessage(HttpMethod.Get, "https://api.nuget.org/v3/index.json")); var log = new TestLogger(); // Act using (var actualResponse = await retryHandler.SendAsync(request, log, CancellationToken.None)) { // Assert Assert.NotEmpty(actualResponse.Content.Headers); } } }
public async Task HttpRetryHandler_ReturnsContentHeaders() { // Arrange Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { var stringContent = new StringContent("Plain text document.", Encoding.UTF8, "text/plain"); stringContent.Headers.TryAddWithoutValidation("X-Content-ID", "49f47c14-c21f-4c1d-9e13-4f5fcf5f8013"); var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = stringContent, }; response.Headers.TryAddWithoutValidation("X-Message-Header", "This isn't on the content."); return(response); }; var retryHandler = new HttpRetryHandler(); var testHandler = new HttpRetryTestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest( httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)); var log = new TestLogger(); // Act var actualResponse = await retryHandler.SendAsync(request, log, CancellationToken.None); // Assert var actualHeaders = actualResponse .Content .Headers .OrderBy(x => x.Key) .ToDictionary(x => x.Key, x => x.Value); Assert.Equal( new List <string> { "Content-Type", "X-Content-ID" }, actualHeaders.Keys.OrderBy(x => x).ToList()); Assert.Equal(actualHeaders["Content-Type"], new[] { "text/plain; charset=utf-8" }); Assert.Equal(actualHeaders["X-Content-ID"], new[] { "49f47c14-c21f-4c1d-9e13-4f5fcf5f8013" }); }
public async Task HttpRetryHandler_AppliesTimeoutToRequestsIndividually() { // Arrange // 20 requests that take 50ms each for a total of 1 second (plus noise). var requestDuration = TimeSpan.FromMilliseconds(50); var maxTries = 20; // Make the request timeout longer than each request duration but less than the total // duration of all attempts. var requestTimeout = TimeSpan.FromMilliseconds(500); int hits = 0; Func <HttpRequestMessage, CancellationToken, Task <HttpResponseMessage> > handler = async(requestMessage, token) => { hits++; await Task.Delay(requestDuration); return(new HttpResponseMessage(HttpStatusCode.InternalServerError)); }; var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = maxTries, RequestTimeout = requestTimeout, RetryDelay = TimeSpan.Zero }; var log = new TestLogger(); // Act using (await retryHandler.SendAsync(request, log, CancellationToken.None)) { } // Assert Assert.Equal(maxTries, hits); }
public async Task HttpRetryHandler_MultipleTriesNoSuccess() { // Arrange TimeSpan retryDelay = TimeSpan.Zero; TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( new Dictionary <string, string>() { [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() }); var hits = 0; Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { hits++; return(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); }; var retryHandler = new HttpRetryHandler(testEnvironmentVariableReader); var testHandler = new HttpRetryTestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = MaxTries, RequestTimeout = Timeout.InfiniteTimeSpan, RetryDelay = retryDelay }; var log = new TestLogger(); // Act using (await retryHandler.SendAsync(request, log, CancellationToken.None)) { } // Assert Assert.Equal(MaxTries, hits); }
private static async Task <T> ThrowsException <T>(ITestServer server) where T : Exception { return(await server.ExecuteAsync(async address => { int maxTries = 2; TimeSpan retryDelay = TimeSpan.Zero; TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( new Dictionary <string, string>() { [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = maxTries.ToString(), [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() }); // Arrange var retryHandler = new HttpRetryHandler(testEnvironmentVariableReader); var countingHandler = new CountingHandler { InnerHandler = new HttpClientHandler() }; var httpClient = new HttpClient(countingHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, address)) { MaxTries = maxTries, RetryDelay = retryDelay }; // Act Func <Task> actionAsync = () => retryHandler.SendAsync( request, new TestLogger(), CancellationToken.None); // Act & Assert var exception = await Assert.ThrowsAsync <T>(actionAsync); Assert.Equal(2, countingHandler.Hits); return exception; })); }
public async Task HttpRetryHandler_MultipleTriesTimed() { // Arrange Func <HttpRequestMessage, HttpResponseMessage> handler = requestMessage => { return(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); }; var minTime = GetRetryMinTime(MaxTries, SmallTimeout); var retryHandler = new HttpRetryHandler(); var testHandler = new TestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { MaxTries = MaxTries, RequestTimeout = Timeout.InfiniteTimeSpan, RetryDelay = SmallTimeout }; var log = new TestLogger(); // Act var timer = new Stopwatch(); timer.Start(); using (await retryHandler.SendAsync(request, log, CancellationToken.None)) { } timer.Stop(); // Assert Assert.True( timer.Elapsed >= minTime, $"Expected this to take at least: {minTime} But it finished in: {timer.Elapsed}"); }
/// <summary> /// Make an HTTP request while retrying after failed attempts or timeouts. /// </summary> /// <remarks> /// This method accepts a factory to create instances of the <see cref="HttpRequestMessage"/> because /// requests cannot always be used. For example, suppose the request is a POST and contains content /// of a stream that can only be consumed once. /// </remarks> public async Task <HttpResponseMessage> SendAsync( HttpRetryHandlerRequest request, ILogger log, CancellationToken cancellationToken) { var tries = 0; HttpResponseMessage response = null; var success = false; while (tries < request.MaxTries && !success) { if (tries > 0) { await Task.Delay(request.RetryDelay, cancellationToken); } tries++; success = true; using (var requestMessage = request.RequestFactory()) { var stopwatch = Stopwatch.StartNew(); var requestUri = requestMessage.RequestUri.ToString(); try { // The only time that we will be disposing this existing response is if we have // successfully fetched an HTTP response but the response has an status code indicating // failure (i.e. HTTP status code >= 500). // // If we don't even get an HTTP response message because an exception is thrown, then there // is no response instance to dispose. Additionally, we cannot use a finally here because // the caller needs the response instance returned in a non-disposed state. // // Also, remember that if an HTTP server continuously returns a failure status code (like // 500 Internal Server Error), we will retry some number of times but eventually return the // response as-is, expecting the caller to check the status code as well. This results in the // success variable being set to false but the response being returned to the caller without // disposing it. response?.Dispose(); log.LogInformation(" " + string.Format( CultureInfo.InvariantCulture, "{0} {1}", requestMessage.Method, requestUri)); // Issue the request. var timeoutMessage = string.Format( CultureInfo.CurrentCulture, "The HTTP request to '{0} {1}' has timed out after {2}ms.", requestMessage.Method, requestUri, (int)request.RequestTimeout.TotalMilliseconds); response = await TimeoutUtility.StartWithTimeout( timeoutToken => request.HttpClient.SendAsync(requestMessage, request.CompletionOption, timeoutToken), request.RequestTimeout, timeoutMessage, cancellationToken); // Wrap the response stream so that the download can timeout. if (response.Content != null) { var networkStream = await response.Content.ReadAsStreamAsync(); var contentLength = response.Content.Headers.ContentLength; // Only bother to report if size is big enough (1MB) if (contentLength != null && contentLength.Value >= 1024 * 1024) { networkStream = new NugetDownloadProgressStream(networkStream, contentLength.Value, downloadProgress); } var newContent = new DownloadTimeoutStreamContent( requestUri, networkStream, request.DownloadTimeout); // Copy over the content headers since we are replacing the HttpContent instance associated // with the response message. foreach (var header in response.Content.Headers) { newContent.Headers.TryAddWithoutValidation(header.Key, header.Value); } response.Content = newContent; } log.LogInformation(" " + string.Format( CultureInfo.InvariantCulture, "{0} {1} {2}ms", response.StatusCode, requestUri, stopwatch.ElapsedMilliseconds)); if ((int)response.StatusCode >= 500) { success = false; } } catch (OperationCanceledException) { response?.Dispose(); throw; } catch (Exception e) { success = false; response?.Dispose(); if (tries >= request.MaxTries) { // Note: we override nuget default behavior by ignoring http errors (NoContent is ignored in V2FeedParser) //throw; return(new HttpResponseMessage(HttpStatusCode.NoContent)); } log.LogInformation(string.Format( CultureInfo.CurrentCulture, "An error was encountered when fetching '{0} {1}'. The request will now be retried.", requestMessage.Method, requestUri, requestMessage) + Environment.NewLine + ExceptionUtilities.DisplayMessage(e)); } } } return(response); }