Last active
November 25, 2024 18:32
-
-
Save mt89vein/fe67f6c811f20d4e150d0f77926ada93 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// <summary> | |
| /// Example of typed http client. | |
| /// </summary> | |
| public class MyHttpClient | |
| { | |
| /// <summary> | |
| /// Request policy name. | |
| /// </summary> | |
| public const string GoogleRequestPolicy = nameof(MyHttpClient) + ":" + nameof(MakeGoogleRequestAsync); | |
| /// <summary> | |
| /// Http client factory. | |
| /// </summary> | |
| private readonly IHttpClientFactory _httpClientFactory; | |
| /// <summary> | |
| /// Creates new instance of <see cref="MyHttpClient"/>. | |
| /// </summary> | |
| /// <param name="httpClientFactory">HTTP client factory.</param> | |
| public MyHttpClient(IHttpClientFactory httpClientFactory) | |
| { | |
| _httpClientFactory = httpClientFactory; | |
| } | |
| /// <summary> | |
| /// Executes request to google. | |
| /// </summary> | |
| /// <returns>Some response.</returns> | |
| public async Task<string> MakeGoogleRequestAsync() | |
| { | |
| using var ctx = OutgoingHttpRequestContext.Create(); | |
| ctx.WithResiliencePipeline(GoogleRequestPolicy); | |
| var httpClient = _httpClientFactory.CreateClient(nameof(MyHttpClient)); | |
| using var response = await httpClient.GetAsync(new Uri("https://google.com")); | |
| response.EnsureSuccessStatusCode(); | |
| return await response.Content.ReadAsStringAsync(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// <summary> | |
| /// Outgoing http request context. | |
| /// Use it to configure request. | |
| /// </summary> | |
| public sealed record OutgoingHttpRequestContext : IDisposable | |
| { | |
| /// <summary> | |
| /// Parent context. | |
| /// </summary> | |
| private readonly OutgoingHttpRequestContext? _parentRequestContext; | |
| /// <summary> | |
| /// Current outgoing http request context. | |
| /// </summary> | |
| private static readonly AsyncLocal<OutgoingHttpRequestContext?> _currentRequestContext = new(); | |
| /// <summary> | |
| /// Returns current outgoing http request context. | |
| /// </summary> | |
| public static OutgoingHttpRequestContext? Current => _currentRequestContext.Value; | |
| /// <summary> | |
| /// Any other additional props attached to outgoing request. | |
| /// </summary> | |
| public IDictionary<string, object?> Properties { get; } | |
| /// <summary> | |
| /// Creates <see cref="OutgoingHttpRequestContext"/>. | |
| /// </summary> | |
| /// <param name="properties">Any other additional props attached to outgoing request.</param> | |
| /// <param name="parent">Parent context.</param> | |
| private OutgoingHttpRequestContext(IDictionary<string, object?>? properties, OutgoingHttpRequestContext? parent) | |
| { | |
| Properties = properties ?? new Dictionary<string, object?>(); | |
| _parentRequestContext = parent; | |
| } | |
| /// <summary> | |
| /// Creates <see cref="OutgoingHttpRequestContext"/>. | |
| /// </summary> | |
| public static OutgoingHttpRequestContext Create(IDictionary<string, object?>? properties = null) | |
| { | |
| return _currentRequestContext.Value = new OutgoingHttpRequestContext(properties, _currentRequestContext.Value); | |
| } | |
| /// <summary> | |
| /// Adds property to context. | |
| /// </summary> | |
| public OutgoingHttpRequestContext WithProperty(string key, object? value) | |
| { | |
| Properties.TryAdd(key, value); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. | |
| /// </summary> | |
| public void Dispose() | |
| { | |
| _currentRequestContext.Value = _parentRequestContext; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// <summary> | |
| /// Extensions for <see cref="OutgoingHttpRequestContext"/>. | |
| /// </summary> | |
| public static class OutgoingHttpRequestContextExtensions | |
| { | |
| /// <summary> | |
| /// The key where stored resilience pipeline name. | |
| /// </summary> | |
| private const string ResiliencePipelineKey = "ResiliencePipeline"; | |
| /// <summary> | |
| /// Sets resilience pipeline for request. | |
| /// </summary> | |
| /// <param name="ctx">Outgoing http request context.</param> | |
| /// <param name="pipelineName">The name of resilience pipeline.</param> | |
| /// <returns>Outgoing http request context.</returns> | |
| public static OutgoingHttpRequestContext WithResiliencePipeline( | |
| this OutgoingHttpRequestContext ctx, | |
| string pipelineName | |
| ) | |
| { | |
| return ctx.WithProperty(ResiliencePipelineKey, pipelineName); | |
| } | |
| /// <summary> | |
| /// Returns resilience pipeline for request. | |
| /// </summary> | |
| /// <param name="ctx">Outgoing http request context.</param> | |
| /// <param name="pipelineName">The name of resilience pipeline.</param> | |
| /// <returns>True, if resilience pipeline configured.</returns> | |
| public static bool TryGetResiliencePipeline( | |
| this OutgoingHttpRequestContext ctx, | |
| [NotNullWhen(returnValue: true)] out string? pipelineName | |
| ) | |
| { | |
| if (ctx.Properties.TryGetValue(ResiliencePipelineKey, out var value) && value is string s) | |
| { | |
| pipelineName = s; | |
| return true; | |
| } | |
| pipelineName = null; | |
| return false; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// <summary> | |
| /// Executes resilience pipeline by name. | |
| /// </summary> | |
| public sealed class PipelineExecuteDelegatingHandler : DelegatingHandler | |
| { | |
| /// <summary> | |
| /// Resilience pipelines provider. | |
| /// </summary> | |
| private readonly ResiliencePipelineProvider<string> _provider; | |
| /// <summary> | |
| /// Creates new instance of <see cref="PipelineExecuteDelegatingHandler"/>. | |
| /// </summary> | |
| /// <param name="provider">Resilience pipelines provider.</param> | |
| public PipelineExecuteDelegatingHandler(ResiliencePipelineProvider<string> provider) | |
| { | |
| _provider = provider; | |
| } | |
| /// <summary> | |
| /// Executes resillience pipeline by name. | |
| /// </summary> | |
| /// <param name="request">Outgoing HTTP request.</param> | |
| /// <param name="cancellationToken"></param> | |
| /// <returns></returns> | |
| protected override Task<HttpResponseMessage> SendAsync( | |
| HttpRequestMessage request, | |
| CancellationToken cancellationToken | |
| ) | |
| { | |
| if (OutgoingHttpRequestContext.Current is null || | |
| !OutgoingHttpRequestContext.Current.TryGetResiliencePipeline(out var pipelineName)) | |
| { | |
| return base.SendAsync(request, cancellationToken); | |
| } | |
| var pipeline = _provider.GetPipeline(pipelineName); | |
| return pipeline | |
| .ExecuteAsync((r, ct) => new ValueTask<HttpResponseMessage>(base.SendAsync(r, ct)), request, | |
| cancellationToken) | |
| .AsTask(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Microsoft.AspNetCore.Mvc; | |
| using Polly; | |
| using Polly.CircuitBreaker; | |
| using Polly.Retry; | |
| using Sstv.HttpClient; | |
| using Sstv.HttpClient.Example; | |
| using System.Threading.RateLimiting; | |
| var builder = WebApplication.CreateBuilder(args); | |
| builder.Services.AddSingleton<MyHttpClient>(); | |
| var clientBuilder = builder.Services.AddHttpClient(nameof(MyHttpClient)); | |
| builder.Services.AddSingleton<PipelineExecuteDelegatingHandler>(); | |
| clientBuilder.AddHttpMessageHandler<PipelineExecuteDelegatingHandler>(); | |
| builder.Services.AddResiliencePipeline(MyHttpClient.GoogleRequestPolicy, b => | |
| { | |
| var overallTimeout = TimeSpan.FromSeconds(20); | |
| var attemptTimeout = TimeSpan.FromSeconds(5); | |
| var attemptsCount = 2; | |
| var concurrencyLimiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions { PermitLimit = 1 }); | |
| b.AddRateLimiter(concurrencyLimiter) | |
| .AddTimeout(overallTimeout) | |
| .AddRetry( | |
| new RetryStrategyOptions | |
| { | |
| Name = "MyResiliencePipeline.Retry", | |
| Delay = TimeSpan.FromMilliseconds(100), | |
| MaxRetryAttempts = attemptsCount, | |
| BackoffType = DelayBackoffType.Exponential, | |
| UseJitter = true, | |
| MaxDelay = TimeSpan.FromMilliseconds(300), | |
| }) | |
| .AddCircuitBreaker( | |
| new CircuitBreakerStrategyOptions | |
| { | |
| Name = "MyResiliencePipeline.CircuitBreaker", | |
| BreakDuration = TimeSpan.FromSeconds(5), | |
| FailureRatio = 0.5, | |
| MinimumThroughput = 10, | |
| SamplingDuration = overallTimeout, | |
| }) | |
| .AddTimeout(attemptTimeout); | |
| }); | |
| var app = builder.Build(); | |
| app.MapGet("/", async ([FromServices] MyHttpClient httpClient) => Results.Ok(await httpClient.MakeGoogleRequestAsync())); | |
| app.Run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment