Written in front

The previous two articles understand the internal implementation of HttpClientFactory through the source code. When we use it in the project, we will always deal with the following issues:

  • HttpClient timeout processing and retry mechanism
  • Implementation of HttpClient fuse mode
  • HttpClient logging and tracking chain

Next, we will explain the above problems from the perspective of use.

Detailed introduction

The following code refers to
MSDN

, because the GitHub interface shown in the code can be tuned, and I will write an interface to test it.

HttpClient timeout processing and retry mechanism

Before that, we need to look at the library Polly, a .NET-based resilience and transient error handling library that allows developers to perform retry, circuit breakers in a smooth and thread-safe manner. , Timeout, Bulkhead Isolation and Fallback.

The following code describes how to use the timeout mechanism in .NET Core 3.0.

   1:   Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10))

So how to register it to the corresponding HttpClient instance, there are many ways:

  • Register with AddPolicyHandler
   1:   services.AddHttpClient( "github" , c =>
   2:               {
   3:                   c.BaseAddress = new Uri( "https://api.github.com/" );
   4:   
   5:                   c.DefaultRequestHeaders.Add( "Accept" , "application/vnd.github.v3+json" );
   6:                   c.DefaultRequestHeaders.Add( "User-Agent" , "HttpClientFactory-Sample" );
   7:               }).AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)));
  • Declare the Policy registration object and add the timeout policy object
   1:   var registry = services.AddPolicyRegistry();
   2:   var timeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));
   3:   registry.Add( "regular" , timeout);

Call method

   1:   services.AddHttpClient( "github" , c =>
   2:               {
   3:                   c.BaseAddress = new Uri( "https://api.github.com/" );
   4:   
   5:                   c.DefaultRequestHeaders.Add( "Accept" , "application/vnd.github.v3+json" ); // GitHub API versioning
   6:                   c.DefaultRequestHeaders.Add( "User-Agent" , "HttpClientFactory-Sample" ); // GitHub requires a user-agent
   7:               }).AddPolicyHandlerFromRegistry( "regular" )

Polly’s retry is also very simple

   1:   var policyRegistry = services.AddPolicyRegistry();
   2:   
   3:   policyRegistry.Add("MyHttpRetry", HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(

3

, retryAttempt => TimeSpan.FromSeconds(

Math.Pow(2, retryAttempt)

)));

The retry setting here is that after the first call fails, there are three more chances to continue the retry, and the interval between each request is an exponential delay.

In addition to the Polly implementation, the retry function can also use DelegatingHandler, which inherits from HttpMessageHandler and is used to “handle requests and respond to responses”. Essentially, it is an ordered combination of a set of HttpMessageHandlers, which can be regarded as a “two-way pipeline”. .

Here we mainly show how to use DelegatingHandler. In actual use, it is still recommended to use Polly to try again.

   1:   private  class RetryHandler : DelegatingHandler
   2:   {
   3:       public  int RetryCount { get; set; } = 5;
   4:   
   5:       protected  override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   6:       {
   7:           for (var i = 0; i < RetryCount; i++)
   8:           {
   9:               try
  10:               {
  11:                   return await base .SendAsync(request, cancellationToken);
  12:               }
  13:               catch (HttpRequestException) when (i == RetryCount - 1)
  14:               {
  15:                   throw ;
  16:               }
  17:               catch (HttpRequestException)
  18:               {
  19:                   // Try again after fifty milliseconds
  20:                   await Task.Delay(TimeSpan.FromMilliseconds(50));
  21:               }
  22:           }
  23:       }
  24:   }

The registration method is as follows:

   1:   services.AddHttpClient( "github" , c =>
   2:   {
   3:       c.BaseAddress = new Uri( "https://api.github.com/" );
   4:   
   5:       c.DefaultRequestHeaders.Add( "Accept" , "application/vnd.github.v3+json" ); // GitHub API versioning
   6:       c.DefaultRequestHeaders.Add( "User-Agent" , "HttpClientFactory-Sample" ); // GitHub requires a user-agent
   7:   })
   8:   .AddHttpMessageHandler(() => new RetryHandler());

Implementation of HttpClient fuse mode

If you understand the use of the Polly library, the implementation of the fuse mode will be very simple.

   1:   var policyRegistry = services.AddPolicyRegistry();
   2:   
   3:   policyRegistry.Add("MyCircuitBreaker", HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 10 , durationOfBreak: TimeSpan.FromSeconds( 30 )));

The fuse setting rule here is to pause for 30 seconds after 10 consecutive requests fail. This place can be written to an extension method to register with the IServiceCollection.

HttpClient logging and tracking chain

Logging this and tracking chain, we generally use request.Header to achieve, and in the micro-service, pay close attention to the relevant caller’s information and its acquisition, the general approach is to increase the request Id to determine the request and its related Log information.

The idea is to add a DelegatingHandler instance to record the relevant logs and request links.

   1:       public  class TraceEntryHandler : DelegatingHandler
   2:       {
   3:           private TraceEntry TraceEntry { get; set; }
   4:   
   5:           public TraceEntryHandler(TraceEntry traceEntry)
   6:           {
   7:               this .TraceEntry = traceEntry;
   8:           }
   9:   
  10:          protected  override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  11:           {
  12:               request.Headers.TryAddWithoutValidation( "X-TRACE-CID" , this .TraceEntry.ClientId);
  13:               request.Headers.TryAddWithoutValidation( "X-TRACE-RID" , this .TraceEntry.RequestId);
  14:               request.Headers.TryAddWithoutValidation( "X-TRACE-SID" , this .TraceEntry.SessionId);
  15:               if ( this .TraceEntry.SourceIP.IsNullOrEmpty())
  16:               {
  17:                   request.Headers.TryAddWithoutValidation( "X-TRACE-IP" , this .TraceEntry.SourceIP);
  18:               }
  19:   
  20:               if ( this .TraceEntry.SourceUserAgent.IsNullOrEmpty())
  21:               {
  22:                   request.Headers.TryAddWithoutValidation( "X-TRACE-UA" , this .TraceEntry.SourceUserAgent);
  23:               }
  twenty four:   
  25:               if ( this .TraceEntry.UserId.IsNullOrEmpty())
  26:               {
  27:                   request.Headers.TryAddWithoutValidation( "X-TRACE-UID" , this .TraceEntry.UserId);
  28:               }
  29:   
  30:               return  base .SendAsync(request, cancellationToken);
  31:           }
  32:       }

When I was looking for relevant information, I found a foreigner using the CorrelationId component. As an implementation, I decided to show it for you to choose:

   1:   public  class CorrelationIdDelegatingHandler : DelegatingHandler
   2:   {
   3:       private  readonly ICorrelationContextAccessor correlationContextAccessor;
   4:       private  readonly IOptions<CorrelationIdOptions> options;
   5:   
   6:       public CorrelationIdDelegatingHandler(
   7:           ICorrelationContextAccessor correlationContextAccessor,
   8:           IOptions<CorrelationIdOptions> options)
   9:       {
  10:           this .correlationContextAccessor = correlationContextAccessor;
  11:           this .options = options;
  12:       }
  13:   
  14:       protected  override Task<HttpResponseMessage> SendAsync(
  15:           HttpRequestMessage request,
  16:           CancellationToken cancellationToken)
  17:       {
  18:           if (!request.Headers.Contains( this .options.Value.Header))
  19:           {
  20:               request.Headers.Add( this .options.Value.Header, correlationContextAccessor.CorrelationContext.CorrelationId);
  21:           }
  twenty two:   
  23:           // Else the header has already been added due to a retry.
  twenty four:   
  25:           return  base .SendAsync(request, cancellationToken);
  26:       }
  27:   }

Reference link:


Https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.0


Https://rehansaeed.com/optimally-configuring-asp-net-core-httpclientfactory/

Orignal link:https://www.cnblogs.com/edison0621/p/11298882.html