| File: PolicyHttpMessageHandler.cs | Web Access |
| Project: src\aspnetcore\src\HttpClientFactory\Polly\src\Microsoft.Extensions.Http.Polly.csproj (Microsoft.Extensions.Http.Polly) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. 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"/>. /// For authoritative information on Polly, see the Polly project and its documentation (<see href="https://github.com/app-vnext/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 that converts a /// non-generic <c>IAsyncPolicy policy</c> to <see cref="IAsyncPolicy{HttpResponseMessage}"/>: /// <code lang="csharp"> /// policy.AsAsyncPolicy<HttpResponseMessage>() /// </code> /// </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"/> attaches a context to the <see cref="HttpRequestMessage"/> prior /// to executing a <see cref="Policy"/>, if one does not already exist. The <see cref="Context"/> is provided /// to the policy for use inside the <see cref="Policy"/> and in other message handlers. /// </para> /// </remarks> public class PolicyHttpMessageHandler : DelegatingHandler { private const string PriorResponseKey = "PolicyHttpMessageHandler.PriorResponse"; 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).ConfigureAwait(false); } 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 async 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)); } if (request.Properties.TryGetValue(PriorResponseKey, out var priorResult) && priorResult is IDisposable disposable) { // This is a retry, dispose the prior response to free up the connection. request.Properties.Remove(PriorResponseKey); disposable.Dispose(); } var result = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); request.Properties[PriorResponseKey] = result; return result; } 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; } }