-
Notifications
You must be signed in to change notification settings - Fork 4.5k
/
RetryDelegatingHandler.cs
150 lines (134 loc) · 6.36 KB
/
RetryDelegatingHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
namespace Microsoft.Rest
{
using Microsoft.Rest.ClientRuntime.Properties;
using Microsoft.Rest.TransientFaultHandling;
using System;
using System.Globalization;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Http retry handler.
/// </summary>
public class RetryDelegatingHandler : DelegatingHandler
{
private const int DefaultNumberOfAttempts = 3;
private readonly TimeSpan DefaultBackoffDelta = new TimeSpan(0, 0, 10);
private readonly TimeSpan DefaultMaxBackoff = new TimeSpan(0, 0, 10);
private readonly TimeSpan DefaultMinBackoff = new TimeSpan(0, 0, 1);
/// <summary>
/// Initializes a new instance of the <see cref="RetryDelegatingHandler"/> class.
/// Sets default retry policy base on Exponential Backoff.
/// </summary>
public RetryDelegatingHandler() : base()
{
Init();
}
/// <summary>
/// Initializes a new instance of the <see cref="RetryDelegatingHandler"/> class. Sets
/// the default retry policy base on Exponential Backoff.
/// </summary>
/// <param name="innerHandler">Inner http handler.</param>
public RetryDelegatingHandler(DelegatingHandler innerHandler)
: this((HttpMessageHandler)innerHandler)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RetryDelegatingHandler"/> class. Sets
/// the default retry policy base on Exponential Backoff.
/// </summary>
/// <param name="innerHandler">Inner http handler.</param>
public RetryDelegatingHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
Init();
}
/// <summary>
/// Initializes a new instance of the <see cref="RetryDelegatingHandler"/> class.
/// </summary>
/// <param name="retryPolicy">Retry policy to use.</param>
/// <param name="innerHandler">Inner http handler.</param>
public RetryDelegatingHandler(RetryPolicy retryPolicy, HttpMessageHandler innerHandler)
: this(innerHandler)
{
RetryPolicy = retryPolicy ?? throw new ArgumentNullException("retryPolicy");
}
private void Init()
{
var retryStrategy = new ExponentialBackoffRetryStrategy(
DefaultNumberOfAttempts,
DefaultMinBackoff,
DefaultMaxBackoff,
DefaultBackoffDelta);
RetryPolicy = new RetryPolicy<HttpStatusCodeErrorDetectionStrategy>(retryStrategy);
}
/// <summary>
/// Gets or sets retry policy.
/// </summary>
public RetryPolicy RetryPolicy { get; set; }
/// <summary>
/// Get delegate count associated with the event
/// </summary>
public int EventCallbackCount => this.RetryPolicy.EventCallbackCount;
/// <summary>
/// Sends an HTTP request to the inner handler to send to the server as an asynchronous
/// operation. Retries request if needed based on Retry Policy.
/// </summary>
/// <param name="request">The HTTP request message to send to the server.</param>
/// <param name="cancellationToken">A cancellation token to cancel operation.</param>
/// <returns>Returns System.Threading.Tasks.Task<TResult>. The
/// task object representing the asynchronous operation.</returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage responseMessage = null;
HttpResponseMessage lastErrorResponseMessage = null;
try
{
await RetryPolicy.ExecuteAsync(async () =>
{
responseMessage = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (!responseMessage.IsSuccessStatusCode)
{
try
{
// Save off the response message and read back its content so it does not go away as this
// response will be used if retries continue to fail.
// NOTE: If the content is not read and this message is returned later, an IO Exception will end up
// happening indicating that the stream has been aborted.
if (responseMessage.Content != null)
{
await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
}
var oldResponse = lastErrorResponseMessage;
lastErrorResponseMessage = responseMessage;
oldResponse?.Dispose();
}
catch
{
// We can end up getting errors reading the content of the message if the connection was closed.
// These errors will be ignored and the previous last error response will continue to be used.
if (lastErrorResponseMessage != null)
{
responseMessage = lastErrorResponseMessage;
}
}
throw new HttpRequestWithStatusException(string.Format(
CultureInfo.InvariantCulture,
Resources.ResponseStatusCodeError,
(int) responseMessage.StatusCode,
responseMessage.StatusCode)) {StatusCode = responseMessage.StatusCode};
}
return responseMessage;
}, cancellationToken).ConfigureAwait(false);
return responseMessage;
}
catch (Exception) when (responseMessage != null || lastErrorResponseMessage != null)
{
return responseMessage ?? lastErrorResponseMessage;
}
}
}
}