Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/http/httpClient/Middleware/Options/RetryHandlerOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// ------------------------------------------------------------------------------

using System;
using System.Net;
using System.Net.Http;
using Microsoft.Kiota.Abstractions;

Expand Down Expand Up @@ -66,8 +67,16 @@ public int MaxRetry

/// <summary>
/// A delegate that's called to determine whether a request should be retried or not.
/// The delegate method should accept a delay time in seconds of, number of retry attempts and <see cref="HttpResponseMessage"/> as it's parameters and return a <see cref="bool"/>. This defaults to false
/// The delegate method should accept a delay time in seconds of, number of retry attempts and <see cref="HttpResponseMessage"/> as its parameters and return a <see cref="bool"/>.
/// This defaults to a function that returns true for 503, 504, and 429 status codes and false otherwise.
/// </summary>
public Func<int, int, HttpResponseMessage, bool> ShouldRetry { get; set; } = (_, _, _) => false;
public Func<int, int, HttpResponseMessage, bool> ShouldRetry { get; set; } = (_, _, response) => response.StatusCode switch
{
// By default, retry on 503, 504, and 429 status codes
HttpStatusCode.ServiceUnavailable => true,
HttpStatusCode.GatewayTimeout => true,
(HttpStatusCode)429 => true,
_ => false
};
}
}
20 changes: 2 additions & 18 deletions src/http/httpClient/Middleware/RetryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);

// Check whether retries are permitted and that the MaxRetry value is a non - negative, non - zero value
if(request.IsBuffered() && retryOption.MaxRetry > 0 && (ShouldRetry(response.StatusCode) || retryOption.ShouldRetry(retryOption.Delay, 0, response)))
if(request.IsBuffered() && retryOption.MaxRetry > 0 && retryOption.ShouldRetry(retryOption.Delay, 0, response))
{
response = await SendRetryAsync(response, retryOption, cancellationToken, activitySource).ConfigureAwait(false);
}
Expand Down Expand Up @@ -143,7 +143,7 @@ private async Task<HttpResponseMessage> SendRetryAsync(HttpResponseMessage respo
// Call base.SendAsync to send the request
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);

if(!(request.IsBuffered() && (ShouldRetry(response.StatusCode) || retryOption.ShouldRetry(retryOption.Delay, retryCount, response))))
if(!(request.IsBuffered() && retryOption.ShouldRetry(retryOption.Delay, retryCount, response)))
{
return response;
}
Expand Down Expand Up @@ -217,22 +217,6 @@ private static double CalculateExponentialDelay(int retryCount, int delay)
return Math.Pow(2, retryCount) * delay;
}

/// <summary>
/// Check the HTTP status to determine whether it should be retried or not.
/// </summary>
/// <param name="statusCode">The <see cref="HttpStatusCode"/>returned.</param>
/// <returns></returns>
private static bool ShouldRetry(HttpStatusCode statusCode)
{
return statusCode switch
{
HttpStatusCode.ServiceUnavailable => true,
HttpStatusCode.GatewayTimeout => true,
(HttpStatusCode)429 => true,
_ => false
};
}

private static async Task<Exception> GetInnerExceptionAsync(HttpResponseMessage response)
{
string? errorMessage = null;
Expand Down
29 changes: 29 additions & 0 deletions tests/http/httpClient/Middleware/RetryHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,35 @@ public async Task ShouldRetryBasedOnCustomShouldRetryDelegate(int expectedMaxRet

// Assert
mockHttpMessageHandler.Protected().Verify<Task<HttpResponseMessage>>("SendAsync", Times.Exactly(1 + expectedMaxRetry), ItExpr.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>());
}

[Theory]
[InlineData(HttpStatusCode.GatewayTimeout)] // 504
[InlineData(HttpStatusCode.ServiceUnavailable)] // 503
[InlineData((HttpStatusCode)429)] // 429
public async Task ShouldNotRetryWithCustomShouldRetryDelegate(HttpStatusCode statusCode)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://example.org/foo")
{
Content = new StringContent("Test Content")
};
httpRequestMessage.Content.Headers.ContentLength = -1;

var retryResponse = new HttpResponseMessage(statusCode);
var response2 = new HttpResponseMessage(HttpStatusCode.OK);
this._testHttpMessageHandler.SetHttpResponse(retryResponse, response2);
_retryHandler.RetryOption.ShouldRetry = (_, _, _) => false;
// Act
var response = await _invoker.SendAsync(httpRequestMessage, new CancellationToken());
// Assert
Assert.NotEqual(response, response2);
Assert.Same(response, retryResponse);
Assert.Same(response.RequestMessage, httpRequestMessage);
Assert.NotNull(response.RequestMessage);
Assert.NotNull(response.RequestMessage.Content);
Assert.NotNull(response.RequestMessage.Content.Headers.ContentLength);
Assert.Equal(response.RequestMessage.Content.Headers.ContentLength, -1);
}

private async Task DelayTestWithMessage(HttpResponseMessage response, int count, string message, int delay = RetryHandlerOption.MaxDelay)
Expand Down
Loading