mirror of
https://github.com/TwiN/gatus.git
synced 2024-12-12 17:50:55 +01:00
141 lines
6.2 KiB
Go
141 lines
6.2 KiB
Go
package g8
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/TwiN/gocache/v2"
|
|
)
|
|
|
|
var (
|
|
// ErrNoExpiration is the error returned by ClientProvider.StartCacheJanitor if there was an attempt to start the
|
|
// janitor despite no expiration being configured.
|
|
// To clarify, this is because the cache janitor is only useful when an expiration is set.
|
|
ErrNoExpiration = errors.New("no point starting the janitor if the TTL is set to not expire")
|
|
|
|
// ErrCacheNotInitialized is the error returned by ClientProvider.StartCacheJanitor if there was an attempt to start
|
|
// the janitor despite the cache not having been initialized using ClientProvider.WithCache
|
|
ErrCacheNotInitialized = errors.New("cannot start janitor because cache is not configured")
|
|
)
|
|
|
|
// ClientProvider has the task of retrieving a Client from an external source (e.g. a database) when provided with a
|
|
// token. It should be used when you have a lot of tokens, and it wouldn't make sense to register all of them using
|
|
// AuthorizationService's WithToken, WithTokens, WithClient or WithClients.
|
|
//
|
|
// Note that the provider is used as a fallback source. As such, if a token is explicitly registered using one of the 4
|
|
// aforementioned functions, the client provider will not be used by the AuthorizationService when a request is made
|
|
// with said token. It will, however, be called upon if a token that is not explicitly registered in
|
|
// AuthorizationService is sent alongside a request going through the Gate.
|
|
//
|
|
// clientProvider := g8.NewClientProvider(func(token string) *g8.Client {
|
|
// // We'll assume that the following function calls your database and returns a struct "User" that
|
|
// // has the user's token as well as the permissions granted to said user
|
|
// user := database.GetUserByToken(token)
|
|
// if user != nil {
|
|
// return g8.NewClient(user.Token).WithPermissions(user.Permissions)
|
|
// }
|
|
// return nil
|
|
// })
|
|
// gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClientProvider(clientProvider))
|
|
//
|
|
type ClientProvider struct {
|
|
getClientByTokenFunc func(token string) *Client
|
|
|
|
cache *gocache.Cache
|
|
ttl time.Duration
|
|
}
|
|
|
|
// NewClientProvider creates a ClientProvider
|
|
// The parameter that must be passed is a function that the provider will use to retrieve a client by a given token
|
|
//
|
|
// Example:
|
|
// clientProvider := g8.NewClientProvider(func(token string) *g8.Client {
|
|
// // We'll assume that the following function calls your database and returns a struct "User" that
|
|
// // has the user's token as well as the permissions granted to said user
|
|
// user := database.GetUserByToken(token)
|
|
// if user == nil {
|
|
// return nil
|
|
// }
|
|
// return g8.NewClient(user.Token).WithPermissions(user.Permissions)
|
|
// })
|
|
// gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClientProvider(clientProvider))
|
|
//
|
|
func NewClientProvider(getClientByTokenFunc func(token string) *Client) *ClientProvider {
|
|
return &ClientProvider{
|
|
getClientByTokenFunc: getClientByTokenFunc,
|
|
}
|
|
}
|
|
|
|
// WithCache adds cache options to the ClientProvider.
|
|
//
|
|
// ttl is the time until the cache entry will expire. A TTL of gocache.NoExpiration (-1) means no expiration
|
|
// maxSize is the maximum amount of entries that can be in the cache at any given time.
|
|
// If a value of gocache.NoMaxSize (0) or less is provided for maxSize, there will be no maximum size.
|
|
//
|
|
// Example:
|
|
// clientProvider := g8.NewClientProvider(func(token string) *g8.Client {
|
|
// // We'll assume that the following function calls your database and returns a struct "User" that
|
|
// // has the user's token as well as the permissions granted to said user
|
|
// user := database.GetUserByToken(token)
|
|
// if user != nil {
|
|
// return g8.NewClient(user.Token).WithPermissions(user.Permissions)
|
|
// }
|
|
// return nil
|
|
// })
|
|
// gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClientProvider(clientProvider.WithCache(time.Hour, 70000)))
|
|
//
|
|
func (provider *ClientProvider) WithCache(ttl time.Duration, maxSize int) *ClientProvider {
|
|
provider.cache = gocache.NewCache().WithEvictionPolicy(gocache.LeastRecentlyUsed).WithMaxSize(maxSize)
|
|
provider.ttl = ttl
|
|
return provider
|
|
}
|
|
|
|
// StartCacheJanitor starts the cache janitor, which passively deletes expired cache entries in the background.
|
|
//
|
|
// Not really necessary unless you have a lot of clients (100000+).
|
|
//
|
|
// Even without the janitor, active eviction will still happen (i.e. when GetClientByToken is called, but the cache
|
|
// entry for the given token has expired, the cache entry will be automatically deleted and re-fetched from the
|
|
// user-defined getClientByTokenFunc)
|
|
func (provider *ClientProvider) StartCacheJanitor() error {
|
|
if provider.cache == nil {
|
|
// Can't start the cache janitor if there's no cache
|
|
return ErrCacheNotInitialized
|
|
}
|
|
if provider.ttl != gocache.NoExpiration {
|
|
return provider.cache.StartJanitor()
|
|
}
|
|
return ErrNoExpiration
|
|
}
|
|
|
|
// StopCacheJanitor stops the cache janitor
|
|
//
|
|
// Not required unless your application initializes multiple providers over the course of its lifecycle.
|
|
// In English, that means if you initialize a ClientProvider only once on application start and it stays up
|
|
// until your application shuts down, you don't need to call this function.
|
|
func (provider *ClientProvider) StopCacheJanitor() {
|
|
if provider.cache != nil {
|
|
provider.cache.StopJanitor()
|
|
}
|
|
}
|
|
|
|
// GetClientByToken retrieves a client by its token through the provided getClientByTokenFunc.
|
|
func (provider *ClientProvider) GetClientByToken(token string) *Client {
|
|
if provider.cache == nil {
|
|
return provider.getClientByTokenFunc(token)
|
|
}
|
|
if cachedClient, exists := provider.cache.Get(token); exists {
|
|
if cachedClient == nil {
|
|
return nil
|
|
}
|
|
// Safely typecast the client.
|
|
// Regardless of whether the typecast is successful or not, we return client since it'll be either client or
|
|
// nil. Technically, it should never be nil, but it's better to be safe than sorry.
|
|
client, _ := cachedClient.(*Client)
|
|
return client
|
|
}
|
|
client := provider.getClientByTokenFunc(token)
|
|
provider.cache.SetWithTTL(token, client, provider.ttl)
|
|
return client
|
|
}
|