This repository has been archived by the owner on Nov 17, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 72
/
PolicyHttpMessageHandler.cs
179 lines (164 loc) · 8.36 KB
/
PolicyHttpMessageHandler.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Polly;
namespace Microsoft.Extensions.Http
{
/// <summary>
/// A <see cref="DelegatingHandler"/> implementation that executes request processing surrounded by a <see cref="Policy"/>.
/// </summary>
/// <remarks>
/// <para>
/// This message handler implementation supports the use of policies provided by the Polly library for
/// transient-fault-handling and resiliency.
/// </para>
/// <para>
/// The documentation provided here is focused guidance for using Polly together with the <see cref="IHttpClientFactory"/>.
/// See the Polly project and its documentation (https://github.com/app-vnext/Polly) for authoritative information on Polly.
/// </para>
/// <para>
/// The extension methods on <see cref="PollyHttpClientBuilderExtensions"/> are designed as a convenient and correct
/// way to create a <see cref="PolicyHttpMessageHandler"/>.
/// </para>
/// <para>
/// The <see cref="PollyHttpClientBuilderExtensions.AddPolicyHandler(IHttpClientBuilder, IAsyncPolicy{HttpResponseMessage})"/>
/// method supports the creation of a <see cref="PolicyHttpMessageHandler"/> for any kind of policy. This includes
/// non-reactive policies, such as Timeout or Cache, which don't require the underlying request to fail first.
/// </para>
/// <para>
/// <see cref="PolicyHttpMessageHandler"/> and the <see cref="PollyHttpClientBuilderExtensions"/> convenience methods
/// only accept the generic <see cref="IAsyncPolicy{HttpResponseMessage}"/>. Generic policy instances can be created
/// by using the generic methods on <see cref="Policy"/> such as <see cref="Policy.TimeoutAsync{TResult}(int)"/>.
/// </para>
/// <para>
/// To adapt an existing non-generic <see cref="IAsyncPolicy"/>, use code like the following:
/// <example>
/// Converting a non-generic <code>IAsyncPolicy policy</code> to <see cref="IAsyncPolicy{HttpResponseMessage}"/>.
/// <code>
/// policy.AsAsyncPolicy<HttpResponseMessage>()
/// </code>
/// </example>
/// </para>
/// <para>
/// The <see cref="PollyHttpClientBuilderExtensions.AddTransientHttpErrorPolicy(IHttpClientBuilder, Func{PolicyBuilder{HttpResponseMessage}, IAsyncPolicy{HttpResponseMessage}})"/>
/// method is an opinionated convenience method that supports the application of a policy for requests that fail due
/// to a connection failure or server error (5XX HTTP status code). This kind of method supports only reactive policies
/// such as Retry, Circuit-Breaker or Fallback. This method is only provided for convenience; we recommend creating
/// your own policies as needed if this does not meet your requirements.
/// </para>
/// <para>
/// Take care when using policies such as Retry or Timeout together as HttpClient provides its own timeout via
/// <see cref="HttpClient.Timeout"/>. When combining Retry and Timeout, <see cref="HttpClient.Timeout"/> will act as a
/// timeout across all tries; a Polly Timeout policy can be configured after a Retry policy in the configuration sequence,
/// to provide a timeout-per-try.
/// </para>
/// <para>
/// All policies provided by Polly are designed to be efficient when used in a long-lived way. Certain policies such as the
/// Bulkhead and Circuit-Breaker maintain state and should be scoped across calls you wish to share the Bulkhead or Circuit-Breaker state.
/// Take care to ensure the correct lifetimes when using policies and message handlers together in custom scenarios. The extension
/// methods provided by <see cref="PollyHttpClientBuilderExtensions"/> are designed to assign a long lifetime to policies
/// and ensure that they can be used when the handler rotation feature is active.
/// </para>
/// <para>
/// The <see cref="PolicyHttpMessageHandler"/> will attach a context to the <see cref="HttpRequestMessage"/> prior
/// to executing a <see cref="Policy"/>, if one does not already exist. The <see cref="Context"/> will be provided
/// to the policy for use inside the <see cref="Policy"/> and in other message handlers.
/// </para>
/// </remarks>
public class PolicyHttpMessageHandler : DelegatingHandler
{
private readonly IAsyncPolicy<HttpResponseMessage> _policy;
private readonly Func<HttpRequestMessage, IAsyncPolicy<HttpResponseMessage>> _policySelector;
/// <summary>
/// Creates a new <see cref="PolicyHttpMessageHandler"/>.
/// </summary>
/// <param name="policy">The policy.</param>
public PolicyHttpMessageHandler(IAsyncPolicy<HttpResponseMessage> policy)
{
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
_policy = policy;
}
/// <summary>
/// Creates a new <see cref="PolicyHttpMessageHandler"/>.
/// </summary>
/// <param name="policySelector">A function which can select the desired policy for a given <see cref="HttpRequestMessage"/>.</param>
public PolicyHttpMessageHandler(Func<HttpRequestMessage, IAsyncPolicy<HttpResponseMessage>> policySelector)
{
if (policySelector == null)
{
throw new ArgumentNullException(nameof(policySelector));
}
_policySelector = policySelector;
}
/// <inheritdoc />
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
// Guarantee the existence of a context for every policy execution, but only create a new one if needed. This
// allows later handlers to flow state if desired.
var cleanUpContext = false;
var context = request.GetPolicyExecutionContext();
if (context == null)
{
context = new Context();
request.SetPolicyExecutionContext(context);
cleanUpContext = true;
}
HttpResponseMessage response;
try
{
var policy = _policy ?? SelectPolicy(request);
response = await policy.ExecuteAsync((c, ct) => SendCoreAsync(request, c, ct), context, cancellationToken);
}
finally
{
if (cleanUpContext)
{
request.SetPolicyExecutionContext(null);
}
}
return response;
}
/// <summary>
/// Called inside the execution of the <see cref="Policy"/> to perform request processing.
/// </summary>
/// <param name="request">The <see cref="HttpRequestMessage"/>.</param>
/// <param name="context">The <see cref="Context"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>Returns a <see cref="Task{HttpResponseMessage}"/> that will yield a response when completed.</returns>
protected virtual Task<HttpResponseMessage> SendCoreAsync(HttpRequestMessage request, Context context, CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return base.SendAsync(request, cancellationToken);
}
private IAsyncPolicy<HttpResponseMessage> SelectPolicy(HttpRequestMessage request)
{
var policy = _policySelector(request);
if (policy == null)
{
var message = Resources.FormatPolicyHttpMessageHandler_PolicySelector_ReturnedNull(
"policySelector",
"Policy.NoOpAsync<HttpResponseMessage>()");
throw new InvalidOperationException(message);
}
return policy;
}
}
}