http request middleware & Polly handlers & DI in .NetCore
作者:互联网
Make HTTP requests using IHttpClientFactory in ASP.NET Core | Microsoft Docs
HttpClient
has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory
:
- Simplifies defining the handlers to apply for each named client.
- Supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Each of these handlers is able to perform work before and after the outgoing request. This pattern:
- Is similar to the inbound middleware pipeline in ASP.NET Core.
- Provides a mechanism to manage cross-cutting concerns around HTTP requests, such as:
- caching
- error handling
- serialization
- logging
To create a delegating handler:
- Derive from DelegatingHandler.
- Override SendAsync. Execute code before passing the request to the next handler in the pipeline:
public class ValidateHeaderHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (!request.Headers.Contains("X-API-KEY")) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent( "The API key header X-API-KEY is required.") }; } return await base.SendAsync(request, cancellationToken); } }
The preceding code checks if the X-API-KEY
header is in the request. If X-API-KEY
is missing, BadRequest is returned.
More than one handler can be added to the configuration for an HttpClient
with Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
builder.Services.AddTransient<ValidateHeaderHandler>(); builder.Services.AddHttpClient("HttpMessageHandler") .AddHttpMessageHandler<ValidateHeaderHandler>();
In the preceding code, the ValidateHeaderHandler
is registered with DI. Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.
Multiple handlers can be registered in the order that they should execute. Each handler wraps the next handler until the final HttpClientHandler
executes the request:
builder.Services.AddTransient<SampleHandler1>(); builder.Services.AddTransient<SampleHandler2>(); builder.Services.AddHttpClient("MultipleHttpMessageHandlers") .AddHttpMessageHandler<SampleHandler1>() .AddHttpMessageHandler<SampleHandler2>();
In the preceding code, SampleHandler1
runs first, before SampleHandler2
.
Use DI in outgoing request middleware
When IHttpClientFactory
creates a new delegating handler, it uses DI to fulfill the handler's constructor parameters. IHttpClientFactory
creates a separate DI scope for each handler, which can lead to surprising behavior when a handler consumes a scoped service.
For example, consider the following interface and its implementation, which represents a task as an operation with an identifier, OperationId
:
public interface IOperationScoped { string OperationId { get; } } public class OperationScoped : IOperationScoped { public string OperationId { get; } = Guid.NewGuid().ToString()[^4..]; }
As its name suggests, IOperationScoped
is registered with DI using a scoped lifetime:
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
The following delegating handler consumes and uses IOperationScoped
to set the X-OPERATION-ID
header for the outgoing request:
In the HttpRequestsSample
download, navigate to /Operation
and refresh the page. The request scope value changes for each request, but the handler scope value only changes every 5 seconds.
Handlers can depend upon services of any scope. Services that handlers depend upon are disposed when the handler is disposed.
Use one of the following approaches to share per-request state with message handlers:
- Pass data into the handler using HttpRequestMessage.Options.
- Use IHttpContextAccessor to access the current request.
- Create a custom AsyncLocal<T> storage object to pass the data.
Use Polly-based handlers
IHttpClientFactory
integrates with the third-party library Polly. Polly is a comprehensive resilience and transient fault-handling library for .NET. It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.
Extension methods are provided to enable the use of Polly policies with configured HttpClient
instances. The Polly extensions support adding Polly-based handlers to clients. Polly requires the Microsoft.Extensions.Http.Polly NuGet package.
Handle transient faults
Faults typically occur when external HTTP calls are transient. AddTransientHttpErrorPolicy allows a policy to be defined to handle transient errors. Policies configured with AddTransientHttpErrorPolicy
handle the following responses:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
provides access to a PolicyBuilder
object configured to handle errors representing a possible transient fault:
builder.Services.AddHttpClient("PollyWaitAndRetry") .AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.WaitAndRetryAsync( 3, retryNumber => TimeSpan.FromMilliseconds(600)));
In the preceding code, a WaitAndRetryAsync
policy is defined. Failed requests are retried up to three times with a delay of 600 ms between attempts.
Dynamically select policies
Extension methods are provided to add Polly-based handlers, for example, AddPolicyHandler. The following AddPolicyHandler
overload inspects the request to decide which policy to apply:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>( TimeSpan.FromSeconds(10)); var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>( TimeSpan.FromSeconds(30)); builder.Services.AddHttpClient("PollyDynamic") .AddPolicyHandler(httpRequestMessage => httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. For any other HTTP method, a 30-second timeout is used.
Add multiple Polly handlers
It's common to nest Polly policies:
builder.Services.AddHttpClient("PollyMultiple") .AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.RetryAsync(3)) .AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
In the preceding example:
- Two handlers are added.
- The first handler uses AddTransientHttpErrorPolicy to add a retry policy. Failed requests are retried up to three times.
- The second
AddTransientHttpErrorPolicy
call adds a circuit breaker policy. Further external requests are blocked for 30 seconds if 5 failed attempts occur sequentially. Circuit breaker policies are stateful. All calls through this client share the same circuit state.
Add policies from the Polly registry
An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry
. For example:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>( TimeSpan.FromSeconds(10)); var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>( TimeSpan.FromSeconds(30)); var policyRegistry = builder.Services.AddPolicyRegistry(); policyRegistry.Add("Regular", timeoutPolicy); policyRegistry.Add("Long", longTimeoutPolicy); builder.Services.AddHttpClient("PollyRegistryRegular") .AddPolicyHandlerFromRegistry("Regular"); builder.Services.AddHttpClient("PollyRegistryLong") .AddPolicyHandlerFromRegistry("Long");
In the preceding code:
- Two policies,
Regular
andLong
, are added to the Polly registry. - AddPolicyHandlerFromRegistry configures individual named clients to use these policies from the Polly registry.
For more information on IHttpClientFactory
and Polly integrations, see the Polly wiki.
标签:Polly,http,handlers,builder,request,handler,Services 来源: https://www.cnblogs.com/panpanwelcome/p/16467316.html