Skip to content

Commit ae9bb56

Browse files
committed
Refactor HandlerOptions with defaults and exception handling
1 parent c1c7e6f commit ae9bb56

25 files changed

+454
-142
lines changed

src/Microsoft.Graph.Core/Exceptions/ErrorConstants.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal static class Codes
2222

2323
internal static string TooManyRetries = "tooManyRetries";
2424

25-
25+
internal static string MaximumValueExceeded = "MaximumValueExceeded";
2626
}
2727

2828
internal static class Messages
@@ -53,6 +53,7 @@ internal static class Messages
5353

5454
internal static string UnexpectedExceptionResponse = "Unexpected exception returned from the service.";
5555

56+
internal static string MaximumValueExceeded = "{0} exceeds the maximum value of {1}.";
5657
}
5758
}
5859
}

src/Microsoft.Graph.Core/Extensions/BaseRequestExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ public static T WithPerRequestAuthProvider<T>(this T baseRequest) where T : IBas
6262
}
6363

6464
/// <summary>
65-
/// Sets a ShouldRetry <see cref="Func{HttpResponseMessage, Boolean}"/> delegate to the default Retry Middleware Handler for this request.
65+
/// Sets a ShouldRetry <see cref="Func{HttpResponseMessage httpResponseMessage, Boolean}"/> delegate to the default Retry Middleware Handler for this request.
6666
/// This only works with the default Retry Middleware Handler.
6767
/// If you use a custom Retry Middleware Handler, you have to handle it's retreival in your implementation.
6868
/// </summary>
6969
/// <typeparam name="T"></typeparam>
7070
/// <param name="baseRequest">The <see cref="BaseRequest"/> for the request.</param>
7171
/// <param name="shouldRetry">A <see cref="Func{HttpResponseMessage, Boolean}"/> for the request.</param>
7272
/// <returns></returns>
73-
public static T WithShouldRetry<T>(this T baseRequest, Func<HttpResponseMessage, bool> shouldRetry) where T : IBaseRequest
73+
public static T WithShouldRetry<T>(this T baseRequest, Func<int, int, HttpResponseMessage, bool> shouldRetry) where T : IBaseRequest
7474
{
7575
string retryOptionKey = typeof(RetryHandlerOption).ToString();
7676
if (baseRequest.MiddlewareOptions.ContainsKey(retryOptionKey))
@@ -121,11 +121,11 @@ public static T WithMaxRedirects<T>(this T baseRequest, int maxRedirects) where
121121
string redirectOptionKey = typeof(RedirectHandlerOption).ToString();
122122
if (baseRequest.MiddlewareOptions.ContainsKey(redirectOptionKey))
123123
{
124-
(baseRequest.MiddlewareOptions[redirectOptionKey] as RedirectHandlerOption).MaxRedirects = maxRedirects;
124+
(baseRequest.MiddlewareOptions[redirectOptionKey] as RedirectHandlerOption).MaxRedirect = maxRedirects;
125125
}
126126
else
127127
{
128-
baseRequest.MiddlewareOptions.Add(redirectOptionKey, new RedirectHandlerOption { MaxRedirects = maxRedirects });
128+
baseRequest.MiddlewareOptions.Add(redirectOptionKey, new RedirectHandlerOption { MaxRedirect = maxRedirects });
129129
}
130130
return baseRequest;
131131
}

src/Microsoft.Graph.Core/Requests/MiddlewareOptions/AuthenticationHandlerOption.cs renamed to src/Microsoft.Graph.Core/Requests/Middleware/Options/AuthenticationHandlerOption.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ namespace Microsoft.Graph
1010
public class AuthenticationHandlerOption : IMiddlewareOption
1111
{
1212
/// <summary>
13-
/// An Authentication Provider
13+
/// An authentication provider
1414
/// </summary>
1515
internal IAuthenticationProvider AuthenticationProvider { get; set; }
1616

1717
/// <summary>
18-
/// An auth provider option property
18+
/// An authentication provider option.
1919
/// </summary>
20-
public IAuthProviderOption AuthProviderOption { get; set; }
20+
public IAuthenticationProviderOption AuthenticationProviderOption { get; set; }
2121
}
2222
}

src/Microsoft.Graph.Core/Requests/MiddlewareOptions/IAuthProviderOption.cs renamed to src/Microsoft.Graph.Core/Requests/Middleware/Options/IAuthProviderOption.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Microsoft.Graph
88
/// An interface used to pass auth provider options in a request.
99
/// Auth providers will be incharge of implementing this interface and providing <see cref="IBaseRequest"/> extensions to set it's values.
1010
/// </summary>
11-
public interface IAuthProviderOption
11+
public interface IAuthenticationProviderOption
1212
{
1313
/// <summary>
1414
/// Microsoft Graph scopes property.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
5+
using System;
6+
using System.Net.Http;
7+
8+
namespace Microsoft.Graph
9+
{
10+
/// <summary>
11+
/// The redirect middleware option class
12+
/// </summary>
13+
public class RedirectHandlerOption : IMiddlewareOption
14+
{
15+
internal const int DEFAULT_MAX_REDIRECT = 5;
16+
internal const int MAX_MAX_REDIRECT = 20;
17+
/// <summary>
18+
/// Constructs a new <see cref="RedirectHandlerOption"/>
19+
/// </summary>
20+
public RedirectHandlerOption()
21+
{
22+
23+
}
24+
25+
26+
private int _maxRedirect = DEFAULT_MAX_REDIRECT;
27+
28+
/// <summary>
29+
/// The maximum number of redirects with a maximum value of 20. This defaults to 5 redirects.
30+
/// </summary>
31+
public int MaxRedirect
32+
{
33+
get { return _maxRedirect; }
34+
set
35+
{
36+
if (value > MAX_MAX_REDIRECT)
37+
{
38+
throw new ServiceException(
39+
new Error
40+
{
41+
Code = ErrorConstants.Codes.MaximumValueExceeded,
42+
Message = string.Format(ErrorConstants.Messages.MaximumValueExceeded, "MaxRedirect", MAX_MAX_REDIRECT)
43+
});
44+
}
45+
_maxRedirect = value;
46+
}
47+
}
48+
49+
/// <summary>
50+
/// A delegate that's called to determine whether a response should be redirected or not. The delegate method should accept <see cref="HttpResponseMessage"/> as it's parameter and return a <see cref="bool"/>. This defaults to true.
51+
/// </summary>
52+
public Func<HttpResponseMessage, bool> ShouldRedirect { get; set; } = (response) => true;
53+
}
54+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
5+
namespace Microsoft.Graph
6+
{
7+
using System;
8+
using System.Net.Http;
9+
10+
/// <summary>
11+
/// The retry middleware option class
12+
/// </summary>
13+
public class RetryHandlerOption : IMiddlewareOption
14+
{
15+
internal const int DEFAULT_DELAY = 3;
16+
internal const int DEFAULT_MAX_RETRY = 3;
17+
internal const int MAX_MAX_RETRY = 10;
18+
internal const int MAX_DELAY = 180;
19+
20+
/// <summary>
21+
/// Constructs a new <see cref="RetryHandlerOption"/>
22+
/// </summary>
23+
public RetryHandlerOption()
24+
{
25+
}
26+
27+
private int _delay = DEFAULT_DELAY;
28+
/// <summary>
29+
/// The waiting time in seconds before retrying a request with a maximum value of 180 seconds. This defaults to 3 seconds.
30+
/// </summary>
31+
public int Delay
32+
{
33+
get { return _delay; }
34+
set
35+
{
36+
if (value > MAX_DELAY)
37+
{
38+
throw new ServiceException(
39+
new Error
40+
{
41+
Code = ErrorConstants.Codes.MaximumValueExceeded,
42+
Message = string.Format(ErrorConstants.Messages.MaximumValueExceeded, "Delay", MAX_DELAY)
43+
});
44+
}
45+
46+
_delay = value;
47+
}
48+
}
49+
50+
private int _maxRetry = DEFAULT_MAX_RETRY;
51+
/// <summary>
52+
/// The maximum number of retries for a request with a maximum value of 10. This defaults to 3.
53+
/// </summary>
54+
public int MaxRetry
55+
{
56+
get
57+
{
58+
return _maxRetry;
59+
}
60+
set
61+
{
62+
if (value > MAX_MAX_RETRY)
63+
{
64+
throw new ServiceException(
65+
new Error
66+
{
67+
Code = ErrorConstants.Codes.MaximumValueExceeded,
68+
Message = string.Format(ErrorConstants.Messages.MaximumValueExceeded, "MaxRetry", MAX_MAX_RETRY)
69+
});
70+
}
71+
_maxRetry = value;
72+
}
73+
}
74+
75+
/// <summary>
76+
/// A delegate that's called to determine whether a request should be retried or not.
77+
/// 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 true
78+
/// </summary>
79+
public Func<int, int, HttpResponseMessage, bool> ShouldRetry { get; set; } = (delay, attempt, response) => true;
80+
}
81+
}

src/Microsoft.Graph.Core/Requests/Handlers/RedirectHandler.cs renamed to src/Microsoft.Graph.Core/Requests/Middleware/RedirectHandler.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
5353
// send request first time to get response
5454
var response = await base.SendAsync(request, cancellationToken);
5555

56-
// check response status code
57-
if (IsRedirect(response.StatusCode))
56+
// check response status code and redirect handler option
57+
if (IsRedirect(response.StatusCode) && RedirectOption.ShouldRedirect(response) && RedirectOption.MaxRedirect > 0)
5858
{
5959
if (response.Headers.Location == null)
6060
{
@@ -68,7 +68,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
6868

6969
var redirectCount = 0;
7070

71-
while (redirectCount < RedirectOption.MaxRedirects)
71+
while (redirectCount < RedirectOption.MaxRedirect)
7272
{
7373
// general clone request with internal CloneAsync (see CloneAsync for details) extension method
7474
var newRequest = await response.RequestMessage.CloneAsync();
@@ -100,6 +100,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
100100
}
101101
redirectCount++;
102102
}
103+
103104
throw new ServiceException(
104105
new Error
105106
{

src/Microsoft.Graph.Core/Requests/Handlers/RetryHandler.cs renamed to src/Microsoft.Graph.Core/Requests/Middleware/RetryHandler.cs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
6060

6161
var response = await base.SendAsync(httpRequest, cancellationToken);
6262

63-
if (RetryOption.ShouldRetry(response) && httpRequest.IsBuffered())
63+
if(IsRetry(response) && httpRequest.IsBuffered() && RetryOption.MaxRetry > 0 && RetryOption.ShouldRetry(RetryOption.Delay, 0, response))
6464
{
6565
response = await SendRetryAsync(response, cancellationToken);
6666
}
@@ -74,13 +74,13 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
7474
/// <param name="response">The <see cref="HttpResponseMessage"/> which is returned and includes the HTTP request needs to be retried.</param>
7575
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the retry.</param>
7676
/// <returns></returns>
77-
public async Task<HttpResponseMessage> SendRetryAsync(HttpResponseMessage response, CancellationToken cancellationToken)
77+
private async Task<HttpResponseMessage> SendRetryAsync(HttpResponseMessage response, CancellationToken cancellationToken)
7878
{
7979
int retryCount = 0;
8080
while (retryCount < RetryOption.MaxRetry)
8181
{
8282
// Call Delay method to get delay time from response's Retry-After header or by exponential backoff
83-
Task delay = Delay(response, retryCount, cancellationToken);
83+
Task delay = Delay(response, retryCount, RetryOption.Delay, cancellationToken);
8484

8585
// general clone request with internal CloneAsync (see CloneAsync for details) extension method
8686
var request = await response.RequestMessage.CloneAsync();
@@ -95,11 +95,10 @@ public async Task<HttpResponseMessage> SendRetryAsync(HttpResponseMessage respon
9595
// Call base.SendAsync to send the request
9696
response = await base.SendAsync(request, cancellationToken);
9797

98-
if (!RetryOption.ShouldRetry(response) || !request.IsBuffered())
98+
if (!IsRetry(response) || !request.IsBuffered() || !RetryOption.ShouldRetry(RetryOption.Delay, retryCount, response))
9999
{
100100
return response;
101101
}
102-
103102
}
104103
throw new ServiceException(
105104
new Error
@@ -129,34 +128,47 @@ private void AddOrUpdateRetryAttempt(HttpRequestMessage request, int retry_count
129128
/// </summary>
130129
/// <param name="response">The <see cref="HttpResponseMessage"/>returned.</param>
131130
/// <param name="retry_count">The retry counts</param>
131+
/// <param name="delay">Delay value in seconds.</param>
132132
/// <param name="cancellationToken">The cancellationToken for the Http request</param>
133133
/// <returns>The <see cref="Task"/> for delay operation.</returns>
134-
public Task Delay(HttpResponseMessage response, int retry_count, CancellationToken cancellationToken)
134+
public Task Delay(HttpResponseMessage response, int retry_count, int delay, CancellationToken cancellationToken)
135135
{
136-
137-
TimeSpan delay = TimeSpan.FromMilliseconds(0);
136+
//TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(0);
138137
HttpHeaders headers = response.Headers;
138+
double delayInSeconds = RetryOption.Delay;
139139
if (headers.TryGetValues(RETRY_AFTER, out IEnumerable<string> values))
140140
{
141141
string retry_after = values.First();
142142
if (Int32.TryParse(retry_after, out int delay_seconds))
143143
{
144-
delay = TimeSpan.FromSeconds(delay_seconds);
144+
delayInSeconds = delay_seconds;
145145
}
146146
}
147147
else
148148
{
149+
m_pow = Math.Pow(2, retry_count);
150+
delayInSeconds = m_pow * RetryOption.Delay;
151+
}
149152

150-
m_pow = Math.Pow(2, retry_count); // m_pow = Pow(2, retry_count)
153+
TimeSpan delayTimeSpan = TimeSpan.FromSeconds(Math.Min(delayInSeconds, RetryHandlerOption.MAX_DELAY));
151154

152-
double delay_time = m_pow * DELAY_MILLISECONDS;
153-
154-
delay = TimeSpan.FromMilliseconds(delay_time);
155-
}
156-
return Task.Delay(delay, cancellationToken);
155+
return Task.Delay(delayTimeSpan, cancellationToken);
157156

158157
}
159158

160-
159+
/// <summary>
160+
/// Check the HTTP response's status to determine whether it should be retried or not.
161+
/// </summary>
162+
/// <param name="response">The <see cref="HttpResponseMessage"/>returned.</param>
163+
/// <returns></returns>
164+
private bool IsRetry(HttpResponseMessage response)
165+
{
166+
if ((response.StatusCode == HttpStatusCode.ServiceUnavailable ||
167+
response.StatusCode == (HttpStatusCode)429))
168+
{
169+
return true;
170+
}
171+
return false;
172+
}
161173
}
162174
}

0 commit comments

Comments
 (0)