示例#1
0
        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);
            }
        }
示例#2
0
        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;
            }));
        }
示例#4
0
        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);
                }
            }
        }
示例#7
0
        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);
            }
        }
示例#8
0
        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);
        }
示例#9
0
        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}");
        }
示例#13
0
        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);
                }
        }
示例#14
0
        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" });
        }
示例#18
0
        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;
            }));
        }
示例#21
0
        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);
        }