我正在使用Service Bus主题触发器Azure函数,在向外部API发出HTTP请求时,我需要处理401(未经授权)错误,例如刷新承载令牌和重试失败的请求.

我正在考虑两种方法,我不确定哪一种更合适. 对于哪种方法是最好的,我将不胜感激.

这是我的重试策略类,包含针对401错误的未经授权的重试策略.

public class PollyRetryPolicy : IPollyRetryPolicy
{
    private readonly ILogger<PollyRetryPolicy> _logger;
    private readonly IOptions<RetryPolicyConfigOptions> _options;
   

    public PollyRetryPolicy(ILogger<PollyRetryPolicy> logger,
        IOptions<RetryPolicyConfigOptions> options)
    {
        _logger = logger;
        _options = options;
        _jitterer = new Random();
    }

    public IAsyncPolicy GetUnauthorizedRetryPolicy()
    {
        var policy = Policy
            .Handle<HttpRequestException>(ex => ex.StatusCode == HttpStatusCode.Unauthorized)
            .WaitAndRetryAsync(
                _options.Value.MaxRetryAttempts,
                retryAttempt => TimeSpan.Zero); // Set the delay to zero milliseconds for immediate retry
        return policy;
    }
}

这是我的API客户端,我试图在其中调用第一个承载令牌端点,并使用IMory yCache存储承载令牌,然后使用该承载令牌进行实际调用.

public class NotificationApiClient : INotificationApiClient
{
    
    private readonly ILogger<NotificationApiClient> _logger;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IMemoryCache _memoryCache;
    private readonly NotificationApiConfigOption _notificationApiConfigOption;

    
    public NotificationApiClient(ILogger<NotificationApiClient> logger,
        IHttpClientFactory httpClientFactory,
        IMemoryCache memoryCache,
        IOptions<NotificationApiConfigOption> notificationApiConfigOption)
    {
        _logger = logger;
        _httpClientFactory = httpClientFactory;
        _memoryCache = memoryCache;
        _notificationApiConfigOption = notificationApiConfigOption.Value;
    }

    
    public async Task<bool> CheckDeliveryAccessAsync(int code)
    {
        try
        {
            
            var httpClient = _httpClientFactory.CreateClient();
            httpClient.BaseAddress = new Uri(_notificationApiConfigOption.BaseUri);
            
            // Get the bearer token
            string bearerToken = await GetNotificationAccessTokenAsync();
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
            var apiResponse = await httpClient.GetAsync($"{_notificationApiConfigOption.CheckDeliveryAccessApiUri}?code={code}");
            if (apiResponse.StatusCode == HttpStatusCode.OK)
            {
                string resultContent = await apiResponse.Content.ReadAsStringAsync();
                var result = resultContent.AsPoco<CheckInvoiceDeliveryAccessResponse>();
                return result.Data.IsActive;
            }
            else
            {
                return false;
            }
        }
        catch (Exception ex)
        {
            
            throw;
        }
    }

   
    public async Task<string> GetNotificationAccessTokenAsync()
    {
        try
        {
            
            if (_memoryCache.TryGetValue("BearerToken", out string cachedBearerToken))
            {
                return cachedBearerToken;
            }
            var httpClient = _httpClientFactory.CreateClient();
            httpClient.BaseAddress = new Uri(_notificationApiConfigOption.BaseUri);
            
            var formData = new Dictionary<string, string>
            {
                { "clientId", _notificationApiConfigOption.ClientId },
                { "clientSecret", _notificationApiConfigOption.ClientSecret }
            };
            
            var content = new FormUrlEncodedContent(formData);
            var apiResponse = await httpClient.PostAsync($"/{_notificationApiConfigOption.AccessTokenUri}", content);
            
            if (apiResponse.StatusCode == HttpStatusCode.OK)
            {
                string resultContent = await apiResponse.Content.ReadAsStringAsync();
                
                var result = resultContent.AsPoco<NotificationAccessTokenResponse>();
                
                _memoryCache.Set("BearerToken", result.AccessToken, new MemoryCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(result.ExpiresIn)
                });
                
                return result.AccessToken;
            }
            else
            {
               
                return string.Empty;
            }
        }
        catch (Exception ex)
        {
            
            throw;
        }
    }
}

推荐答案

正如@codebrane在 comments 部分中指出的那样,您还可以在新令牌到期之前主动检索它.

主动型和被动型方法各有优缺点.如果您的服务是面向客户的,我建议您使用主动方法.使用此方法时,不应因服务令牌过期而重试用户请求.

如果服务之间的通信不是连续的,而是按需进行的,则react 式方法可能会很有用.并且重试施加的延迟是可以接受的.

在这里,我详细介绍了三种不同的方法来实现它:Refresh Token using Polly with Named Client


关于这篇文章,有一点值得注意:

var policy = Policy
    .Handle<HttpRequestException>(ex => ex.StatusCode == HttpStatusCode.Unauthorized)
    .WaitAndRetryAsync(
        _options.Value.MaxRetryAttempts,
        retryAttempt => TimeSpan.Zero); // Set the delay to zero milliseconds for immediate retry

如果您不想在两次重试之间等待,则可以使用RetryAsync

var policy = Policy
    .Handle<HttpRequestException>(ex => ex.StatusCode == HttpStatusCode.Unauthorized)
    .RetryAsync(_options.Value.MaxRetryAttempts);

UPDATE #1

我的应用程序不是面向客户的,它是一种后台工作,根据事件触发通知.那么,在这种情况下,我应该使用主动式还是应该使用被动式?

一如既往,这要视情况而定.:)让我试着帮你做一个权衡分析.

被动办法

优点

  • 它是按需触发的.如果没有流量,则不执行令牌刷新
  • 如果您的系统具有react 性,则更容易对此方法进行推理

缺点

  • 如果令牌检索/刷新通常比原始请求花费更长的时间,则增加的延迟(由于令牌服务调用)变得明显
  • 如果令牌在突发期间过期,则许多请求将暂停(排队),直到新令牌可用

积极主动的方法

优点

  • 从传入请求处理的Angular 来看,您的系统的行为就像您拥有一个长期存在的令牌
  • 根据令牌服务实现,它可能允许一次检索多个令牌(每个下游服务一个令牌

缺点

  • 如果令牌服务是速率受限的/强制实施配额,则此方法比被动方法更有可能超出限制
  • 您可能还需要使用重试来修饰令牌服务调用,以克服暂时性故障

附注:在这两种情况下,您都必须确保没有并发的检索调用.换句话说,您只能通过单个线程刷新令牌.采用积极主动的方法更容易保证这一点.

Csharp相关问答推荐

VS Code - C# - dotnet run找不到文件,但我可以打开并编辑它们吗?

在实际上是List T的 IESEARCH上多次调用First()是否不好?

EF Core:看不到任何查询日志(log)?

在C#中使用in修饰符

使用命令初始化可绑定属性

try 在Blazor项目中生成html

C#XmlSerializer-输出控制新行的多个XML片段

如何管理Azure认证客户端响应和证书 fingerprint

是否由DI容器自动处理由ActivatorUilties.CreateInstance()创建的服务?

HelperText属性不支持复杂内容(混合C#和标记)

在使用StringBuilder时,如何根据 colored颜色 设置为richTextBox中的特定行着色?

链接到字典字符串.拆分为(.Key,.Value)

发布.NET 8 Blazor WebAssembly独立应用程序以进行静态站点部署

从GRPC连接创建ZipArchive

Xamarin.Forms-如何创建可 Select 的显示alert 或弹出窗口?

如何正确地在VB中初始化类?

项目参考和方法签名问题

客户端/服务器RPC如何处理全局变量?

.NET8支持Vector512,但为什么向量不能达到512位?

分别切换用于读取和写入的EF核心日志(log)