How do I update ALL requests when the request property changes when using Async / Await?

I am using async / await to make multiple requests per second for a service using their API. The problem I am running into is when I need to update the token (it breaks every time). After the token has expired, I get a 401 unauthorized error from the service. This is when I refresh the token and retry the failed request again. The token refreshes perfectly, but I found that even after refreshing the token, many subsequent requests are still sent with the old token. Following are the methods that are used in this function. I wonder if there is something outstanding as the culprit for this unintended behavior.

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();
    }
}

      

+3


source to share


2 answers


This is not an answer. I just don't have a place to post the code discussed in one of the comments.

Prabhu, I think something like this should work to refresh the token before getting 401. This only works if you can make an assumption about how often the tokens are running out.

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();
    }  
}

      

Token refresh can be performed by a timer every 60 minutes. Synchronization is performed with a read / write lock. This will be the tick timer handler (you can use 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();
  }
}

      

As you mentioned, if the 60 minute expiration date is not guaranteed, this will not work as expected. Perhaps you can re-generate the token every 5 minutes or so to make sure you are not making requests with invalid tokens.

Finally, to handle 401s as they can still occur, you can change the while loop in SendRequestAsync

to:

if (response.StatusCode == HttpStatusCode.Unauthorized)
{
   UpdateToken();
   return await ExecuteRequestAsync(funcReq());
}

      

+1


source


I suggest the following workflow (added to the one currently suggested by Marcel N) (pseudocode):

// 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)

      



you can also check if the CallMethodAsync

response returns with an invalid token as Marcel N. provided.

+1


source







All Articles