如何对 HttpClient 请求进行排队,直到 Polly 重试策略成功?

问题描述 投票:0回答:1

如果 http 请求返回 401 错误,我将使用 polly 刷新授权令牌。我的完整解决方案基于:使用 Polly 和指定客户端刷新令牌,但相关部分是:

重试政策:

static IAsyncPolicy<HttpResponseMessage> GetAuthRefreshPolicy(IServiceProvider serviceProvider)
{
        return Policy<HttpResponseMessage>
            .HandleResult(response => response.StatusCode == HttpStatusCode.Unauthorized)
            .RetryAsync(async (handler, retry) => await serviceProvider.GetRequiredService<IAuthorisationService>().RefreshTokenAsync());
}

授权服务:

public class AuthorisationService : IAuthorisationService
{
    public String? AuthToken { get; private set; }

    private readonly IAuthenticationClient _AuthenticationClient;
    private readonly AuthOptions _AuthOptions;

    public AuthorisationService(IAuthenticationClient authenticationClient, IOptions<AuthOptions> options)
    {
        _AuthenticationClient = authenticationClient;
        _AuthOptions = options.Value;
    }

    public async Task RefreshTokenAsync()
    {
        this.AuthToken = await _AuthenticationClient.LoginAsync(_AuthOptions.User, _AuthOptions.Password);
    }
}

这在大多数情况下都可以正常工作。我遇到的问题是,如果身份验证令牌当前无效并且同时发出多个请求,因为它们是异步执行的,它们都将使用无效令牌调用,失败并触发 polly 重试策略,从而导致多个不必要的请求令牌刷新请求。

有没有办法设置 polly,以便如果请求因 401 失败,所有后续请求都将等待,直到令牌成功刷新?或者对重试尝试进行排队并在已刷新的情况下跳过请求新令牌?

我尝试过研究诸如舱壁断路器之类的 Polly 功能,但无法确定这些功能是否适合此用例,或者是否有更简单的方法来锁定令牌刷新。

c# asp.net-core refresh-token polly retry-logic
1个回答
0
投票

当我写下答案时,我认为很明显代码示例还没有生产就绪

但是让我们关注您的具体问题。为了避免多个同时并发的令牌刷新调用,您必须保护

TokenService
RefreshToken
方法。这可以通过许多不同的方式来完成。让我向您展示如何使用
SemaphoreSlim
:

做到这一点
public class TokenService : ITokenService
{
    private readonly SemaphoreSlim semaphore = new(1);
    private DateTime lastRefreshed = DateTime.UtcNow;
    public Token GetToken()
        => new() { Scheme = "Bearer", AccessToken = lastRefreshed.ToString("HH:mm:ss")}; 

    public async Task RefreshToken()
    {
        await semaphore.WaitAsync();

        try
        {
            //Use any arbitrary logic to detect simultaneous calls  
            if(lastRefreshed - DateTime.UtcNow < TimeSpan.FromSeconds(3)) 
            {
                Console.WriteLine("No refreshment happened");
                return;
            }
            
            Console.WriteLine("Refreshment happened");
            lastRefreshed = DateTime.UtcNow;
        }
        finally
        {
            semaphore.Release();
        }
    }
}

让我们测试一下代码

var service = new TokenService();
for (int i = 0; i < 10; i++)
{
    Task.Run(async() =>
    {
        await Task.Delay(Random.Shared.Next() % 1000);
        await service.RefreshToken();
    });
}

输出将是:

Refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
© www.soinside.com 2019 - 2024. All rights reserved.