我正在使用async / await使用他们的API每秒向服务发出多个请求。我遇到的问题是当我需要刷新令牌时(它每小时都会消失)。令牌过期后,我从服务中收到401未经授权的错误。那是我刷新令牌,然后再次重试失败的请求。令牌刷新得很好,但我发现即使刷新令牌后,仍然会发送许多后续请求和旧令牌。以下是此功能中使用的方法。想知道是否有任何突出的因素是这种意外行为的罪魁祸首。
public void Process(id)
{
var tasks = items.Select(async item =>
{
var response = await SendRequestAsync(() => CreateRequest(item.Url));
//do something with response
await Process(item.subId); //recursive call to process sub items.
}).ToList();
if (tasks.Any())
await Task.WhenAll(tasks);
}
public HttpRequestMessage CreateRequest(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Authorization", "Bearer " + AppSettings.AccessToken);
return request;
}
public async Task<HttpResponseMessage> SendRequestAsync(Func<HttpRequestMessage> funcReq)
{
var response = await ExecuteRequestAsync(funcReq());
while (response.StatusCode == HttpStatusCode.Unauthorized)
{
await RefreshTokenAsync();
return await ExecuteRequestAsync(funcReq()); //assuming func ensures that CreateRequest is called each time, so I'll always have a new request with the updated token.
}
return response;
}
private async Task<HttpResponseMessage> ExecuteRequestAsync(HttpRequestMessage request)
{
var client = new HttpClient();
var response = await client.SendAsync(request);
return response;
}
public async Task RefreshTokenAsync()
{
await semaphoreSlim.WaitAsync();
try
{
if ((DateTime.Now - refreshTime).TotalMinutes < 60) //tokens last for an hour, so after the refresh is made by the first request that failed, subsequent requests should have the latest token.
return;
Token newToken = GetNewToken();
AppSettings.AccessToken = newToken.AccessToken //AppSettings is a singleton wrapper class for app.cofig app settings
refreshTime = DateTime.Now
}
finally
{
semaphoreSlim.Release();
}
}
答案 0 :(得分:1)
这不是答案。只是我没有在哪里发布其中一条评论中讨论的代码。
Prabhu,我认为这样的事情应该有效,以便在获得401之前更新令牌。这只有在您可以对令牌过期的频率做出假设时才有效。
public HttpRequestMessage CreateRequest(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Authorization", "Bearer " + GetUpToDateAccessToken());
return request;
}
private Token GetUpToDateAccessToken()
{
_readWriteLockSlim.EnterReadLock();
try
{
return _latestToken;
}
finally
{
_readWriteLockSlim.ExitReadLock();
}
}
每隔60分钟就可以使用计时器更新令牌。使用读写锁完成同步。这将是Timer的tick处理程序(您可以使用System.Timers.Timer
)。
private void UpdateToken()
{
_readWriteLockSlim.EnterWriteLock();
try
{
if ((DateTime.Now - refreshTime).TotalMinutes >= 60)
{
Token newToken = GetNewToken();
_latestToken = newToken.AccessToken;
refreshTime = DateTime.Now;
}
}
finally
{
_readWriteLockSlim.ExitWriteLock();
}
}
正如您所提到的,如果60分钟的到期时间无法保证,那么这将无法按预期工作。也许您可以每5分钟左右重新生成一次令牌,以确保您不会使用无效令牌发出请求。
最后,要处理401s因为它们仍然可以发生,你可以将SendRequestAsync
中的while循环修改为:
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
UpdateToken();
return await ExecuteRequestAsync(funcReq());
}
答案 1 :(得分:1)
我将建议以下工作流程(添加到Marcel N目前建议的)(伪代码):
// Manage the expiration token yourself in the Application or Db
var token = GetTokenFromDbOrApplicationWithExpirationDateTime()
// Expiration on your application can be a little bit less than real, so instead of 60 can be 50 minutes.
if (token.isExpired)
token = RequestNewToken()
Db.SaveChanges(token);
}
CallMethod1Async(token)
CallMethod2Async(token)
CallMethod3Async(token)
您还可能想要检查CallMethodAsync
是否返回带有无效令牌的响应,如Marcel N所提供的那样。