Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable access token caching #66

Merged
merged 7 commits into from
Apr 11, 2023
Merged

Enable access token caching #66

merged 7 commits into from
Apr 11, 2023

Conversation

NRHelmi
Copy link
Contributor

@NRHelmi NRHelmi commented Apr 4, 2023

Enabling access token caching by:

  • Adding a generic interface so that users can provide their custom access token handlers
  • Providing a default access token handler to cache token on disk

public int ExpiresIn
{
get => _expiresIn;
set => _expiresIn = value > 0 ? value : throw new ArgumentException("ExpiresIn should be greater than 0 ");
}

public bool IsExpired => (DateTime.Now - DateTimeOffset.FromUnixTimeSeconds(CreatedOn)).TotalSeconds >= _expiresIn - 5; // Anticipate access token expiration by 5 seconds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get the expiration timestamp exposed next to this method? I'd like to control the renewal not based on this flag, but on the actual expiration time

// Check DefaultAccessToken for an example
public interface IAccessTokenHandler
{
Rest Rest { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Rest client should not be part of the interface, nor the Logger. The interface is to simply provide token retrieval. Baking it into the interface conflates 1) The fact of even using the Rest client to do the implementation and 2) That the implementation logs at a ll, much less using the MS Logging extension.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense thanks

{
_context = context;
_logger = logger ?? new LoggerFactory().CreateLogger("RAI-SDK");
_rest = new Rest(context, _logger);
_rest = new Rest(context, _logger, accessTokenHandler);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please implement the default access token handler creation here in the client, instead of in the REST layer, to keep the REST layer as thin as possible (think, as close to Curl as possible)

{
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1);

public Rest Rest { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove these public REST/Logger properties after fixing the interface as described in the interface

{
try
{
var data = File.ReadAllText(CacheName());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this class should fallback to in-memory caching if it cannot find the cache folder

Copy link
Contributor Author

@NRHelmi NRHelmi Apr 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's right, this behavior is already handled in the caller method GetAccessTokenAsync() in the Rest class but it should be part of the handler implementation, moving it here

dict.Add(creds.ClientId, token);
}

File.WriteAllText(CacheName(), JsonConvert.SerializeObject(dict));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar comment, should fallback to memory cache is no file location

{
_context = context;
HttpClient = new HttpClient();
_logger = logger ?? new LoggerFactory().CreateLogger("RAI-SDK");

// Init AccessTokenHandler
AccessTokenHandler = accessTokenHandler ?? new DefaultAccessTokenHandler();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move default behavior into client

}

public HttpClient HttpClient { get; set; }

public IAccessTokenHandler AccessTokenHandler { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the purpose is to make the Client class able to set the access token handler on the Rest class. Will change this field to make it internal
Otherwise we will need to use the Rest constructor the inject the AccessTokenHandler something like

 public Client(Context context, ILogger logger = null, IAccessTokenHandler accessTokenHandler = null)
        {
            _context = context;
            _logger = logger ?? new LoggerFactory().CreateLogger("RAI-SDK");
            _rest = new Rest(context, _logger, accessTokenHandler ?? new DefaultAccessTokenHandler(new Rest(context, null, null));
        }

vs (current implementation)

        public Client(Context context, ILogger logger = null, IAccessTokenHandler accessTokenHandler = null)
        {
            _context = context;
            _logger = logger ?? new LoggerFactory().CreateLogger("RAI-SDK");
            _rest = new Rest(context, _logger);
            _rest.AccessTokenHandler = accessTokenHandler ?? new DefaultAccessTokenHandler(_rest);
        }

I lean toward using the second option

{ "grant_type", "client_credentials" }
};
var resp = await RequestHelperAsync("POST", creds.ClientCredentialsUrl, data);
if (!(resp is string stringResponse))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what scenario does this happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not the one who implemented the body of this method RequestAccessTokenAsync, just making it public so that the IAccessTokenHandler implementations can use it to fetch new tokens

_createdOn = DateTime.Now;
_expiresIn = expiresIn;
Scope = scope;
CreatedOn = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the token creation time, this is the objects creation time (which could be created during deserialization from a cache for example). Take the creation time as an argument instead.

@torkins
Copy link
Collaborator

torkins commented Apr 4, 2023

Needs test for default and non-default behavior

@NRHelmi NRHelmi marked this pull request as ready for review April 4, 2023 23:03
@@ -41,21 +41,16 @@ public class Rest

private readonly ILogger _logger;

public Rest(Context context, ILogger logger, IAccessTokenHandler accessTokenHandler)
public Rest(Context context, ILogger logger)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we relax the ctor API here to default to null so the arg is not required at all for logger, now that clients are much more likely to use the direct Rest ctor, it'd be nice to not force ugly calls of Rest(ctx, null)

}

public HttpClient HttpClient { get; set; }

public IAccessTokenHandler AccessTokenHandler { get; set; }
internal IAccessTokenHandler AccessTokenHandler { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does the assembly need access to this prop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the field internal so that the client class can set and manipulate the access token handler in the rest class

@@ -32,34 +31,40 @@ namespace RelationalAI
public class DefaultAccessTokenHandler : IAccessTokenHandler
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs handling for when filename not found -> INFO log msg, fallback to memory-based cache local ot the tokenhandler

@NRHelmi NRHelmi requested a review from torkins April 6, 2023 11:24
CHANGELOG.md Outdated
@@ -1,5 +1,10 @@
# Changelog

# main
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you change to 0.9.13-alpha


namespace RelationalAI.Test
{
[Collection("RelationalAI.Test")]
public class DefaultAccessTokenHandlerTests : UnitTest
{
private string TestCachePath()
{
var envHome = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "HOMEPATH" : "HOME";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use the C# corelib env var for this for better portability: https://learn.microsoft.com/en-us/dotnet/api/system.environment.specialfolder?view=net-7.0

(System.Environment.UserProfile)

@@ -59,12 +62,12 @@ public async Task<AccessToken> GetAccessTokenAsync(ClientCredentials creds)
return creds.AccessToken;
}

private string CachePath()
private string DefaultCachePath()
{
var envHome = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "HOMEPATH" : "HOME";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as in comment in test please use corelib portable environment enum

Copy link
Collaborator

@torkins torkins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make the changes I mentioned

  1. changelog adding version,
  2. use corelib env enums everywhere

@NRHelmi NRHelmi merged commit 79dad30 into main Apr 11, 2023
@NRHelmi NRHelmi deleted the hnr-rai-11583-caching branch April 11, 2023 23:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants