apisession

package module
v1.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 1, 2025 License: MIT Imports: 7 Imported by: 0

README

go-api-session

Handle session for API, supports rate limitting

1. Features

  • Validating session
  • Rate limiting
    • request too frequently (HTTP code 429 Too Many Requests) using Fixed Window algorithm
    • request too fast (HTTP code 425 Too Early)
  • Session payload: session can store extra data
  • Online users: keep online users in a Redis's sorted set

2. Usage

Install
go get github.com/zeroboo/go-api-session
Setup
	//Create session manager
	sessionManager := apisession.NewRedisSessionManager(client,
		"session", //keys will have format of `session:<sessionId>``
		86400000,  //session last for 1 day
		60000,     //Time window is 1 minute
		10,        //Max 10 calls per minute
		1000,      //2 calls must be at least 1 second apart
		true,      //Track online users
	)

Create new session
	owner := "owner"
	///...
	//User starts a session: create new
	sessionId, errGet := sessionManager.StartSession(context.TODO(), owner)
	if errGet != nil {
		log.Printf("Failed to get session: %v", errGet)
	}
	//...
Use session to verify API calls
	owner:= "owner"

	//...
	//Update in api call
	session, errSession := sessionManager.RecordAPICall(context.TODO(), sessionValue, owner, "url1")
	if errSession == nil {
		//Valid api call
		//Next processing...
		//...
	} else {
		//Invalid api call
		log.Printf("Failed to update session: %v", errGet)
	}
	
Session payload
//Init session with extra data
session := NewAPISessionWithPayload("user1", map[string]any{
		"nickname": "Jaian",
		"oldTokens":      []string{"token1", "token2"},
	})
//... or set payload directly
session.SetPayload("nickname","Jaian")


//Retrieve old tokens from session by generic helper
knownTokens, ok := GetPayloadSlice[string](session, "oldTokens")//value: []string{"token1", "token2"}

//Retrieve nickname by type assertion
value := session.GetPayload("nickname")
nickname, ok := value.(string)
Clear session
errDelete := manager.DeleteSession(context.TODO(), sessionId)
Get online users
onlineUsers, errGet := manager.GetOnlineUsers(context.TODO(), sessionId)
// onlineUsers is a map of userId to his/her last activity time
	

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidSession = fmt.Errorf("invalid session")
View Source
var ErrTooFast = fmt.Errorf("request too fast")
View Source
var ErrTooMany = fmt.Errorf("too many requests")

Functions

func GenerateSessionValue added in v1.0.0

func GenerateSessionValue(ownerId string) string

func GetOrCreatePayloadMap added in v1.2.0

func GetOrCreatePayloadMap[K comparable, V any](sess *APISession, key string) (map[K]V, bool)

GetOrCreatePayloadMap returns a map from session payload, if not exist, create a new map.

  • If the value of key is not a map, replace it with a new map.
  • If any value of the map is not the correct type, it will be ignored.

Return the map and a boolean indicate if the map is created

func GetOrCreatePayloadSlice added in v1.2.0

func GetOrCreatePayloadSlice[V any](sess *APISession, key string) ([]V, bool)

func GetPayloadMap added in v1.1.0

func GetPayloadMap[K comparable, V any](sess *APISession, key string) map[K]V

GetPayloadMap returns a map from session payload, if not exist, return nil

func GetPayloadSlice added in v1.3.0

func GetPayloadSlice[V any](sess *APISession, key string) ([]V, bool)

func GetRedisSessionKey

func GetRedisSessionKey(prefix string, sessionId string) string

func Hash

func Hash(value string) string

returns sha256 hash of the value

func SetPayloadMap added in v1.1.0

func SetPayloadMap[K comparable, V any](sess *APISession, key string, value map[K]V)

SetPayloadMap init a map in session payload

Types

type APICallRecord

type APICallRecord struct {

	//calls in current window
	Count int64 `json:"c" msgpack:"c"`

	//Last call in milliseconds
	Last int64 `json:"l" msgpack:"l"`
}

Tracks how an api is being called

func NewAPICallRecord

func NewAPICallRecord() *APICallRecord

type APIRequest added in v1.0.0

type APIRequest struct {
	Owner     string
	SessionId string
	URL       string
}

type APISession

type APISession struct {
	Id    string `json:"i" msgpack:"i"`
	Owner string `json:"o" msgpack:"o"`
	//Map of url to API call track
	Records map[string]*APICallRecord `json:"r" msgpack:"r"`

	//Current time window
	Window int64 `json:"w" msgpack:"w"`

	//Payload are extra data of session
	Payload map[string]any `json:"p" msgpack:"p"`

	Created int64 `json:"c" msgpack:"c"` //Created time in milliseconds
	Updated int64 `json:"u" msgpack:"u"` //Updated time in milliseconds
}

func NewAPISession

func NewAPISession(owner string) *APISession

func NewAPISessionWithPayload added in v1.0.9

func NewAPISessionWithPayload(owner string, payload map[string]any) *APISession

func (*APISession) GetCallRecord

func (ses *APISession) GetCallRecord(url string) *APICallRecord

func (*APISession) GetPayload added in v1.0.8

func (ses *APISession) GetPayload(key string) any

func (*APISession) GetPayloadInt added in v1.0.8

func (ses *APISession) GetPayloadInt(key string) int

Returns metadata as int, 0 if not found

func (*APISession) GetPayloadInt64 added in v1.0.8

func (ses *APISession) GetPayloadInt64(key string) int64

Returns metadata as int64, 0 if not found

func (*APISession) GetPayloadString added in v1.0.8

func (ses *APISession) GetPayloadString(key string) string

Returns metadata as string, empty string if not found

func (*APISession) RecordCall

func (session *APISession) RecordCall(url string) error

func (*APISession) SetPayload added in v1.1.0

func (ses *APISession) SetPayload(key string, value any)

func (*APISession) SetWindow

func (ses *APISession) SetWindow(window int64)

func (*APISession) ValidateSession added in v1.0.0

func (ses *APISession) ValidateSession(session string) bool

type ISessionManager

type ISessionManager interface {
	// Records an API call: load session, update the session with the API call, validate it and save it back if api call is valid.
	//
	// Validating is performed by calling ValidateAPICall
	//
	// Params:
	//   - sessionId string: session id
	//   - owner string: owner of the session
	//   - url: url of the api call
	//
	// Returns:
	//   - session: updated session
	//   - error: nil if success, an error instance if any
	RecordAPICall(ctx context.Context, sessionId string, owner string, url string) (*APISession, error)
	// Validates an API call to a session. Only validating, doesn't perform any update to database.
	//
	// Params:
	//   - request *APIRequest: api call
	//   - session *APISession
	//   - now: current time in milliseconds
	//
	// Returns:
	//   - error: nil if success, an error instance if any
	ValidateAPICall(request *APIRequest, session *APISession, now time.Time) error

	// Loads session of a user from database
	GetSession(ctx context.Context, owner string) (*APISession, error)
	// Deletes session of a user from database
	DeleteSession(ctx context.Context, owner string) error
	// Saves session of a user to database
	SetSession(ctx context.Context, owner string, session *APISession) error

	// StartSession creates a new session for the owner and insert to db
	// Returns:
	//   - sessionId string: id of new session
	//   - error: error if exists, nil is successful
	StartSession(ctx context.Context, owner string) (string, error)

	// StartSession creates a new session for the owner and insert to db
	// Returns:
	//   - session *APISession: new session with payload
	//   - error: error if exists, nil is successful
	StartSessionWithPayload(ctx context.Context, owner string, payload map[string]any) (*APISession, error)

	//GetRequestInterval returns the interval in milliseconds for API requests
	GetRequestInterval() int64

	// GetMaxCallPerWindow returns the maximum number of API calls allowed per time window
	GetMaxCallPerWindow() int64

	// GetWindowSize returns the size of the time window in milliseconds
	GetWindowSize() int64

	// GetOnlineUsers returns a map of online users with their last activity timestamp
	GetOnlineUsers(ctx context.Context) (map[string]int64, error)
}

type RedisSessionManager

type RedisSessionManager struct {
	// contains filtered or unexported fields
}

func NewRedisSessionManager

func NewRedisSessionManager(redisClient *redis.Client,
	sessionKeyPrefix string,
	sessionTTL int64,
	windowSize int64,
	maxCallPerWindow int64,
	requestInterval int64,
	trackOnlineUsers bool) *RedisSessionManager

Create redis session manager Params: - redisClient: redis client

- sessionKeyPrefix: prefix for session key

- sessionTTL: session time to live in milliseconds

- windowSize: time window in milliseconds

- maxCallPerWindow: max calls allowed per window

- minRequestInterval: minimum milliseconds between 2 request, 0 mean no limit

- trackOnlineUsers: if true, track online users in redis sorted set

func (*RedisSessionManager) DeleteSession

func (sm *RedisSessionManager) DeleteSession(ctx context.Context, owner string) error

func (*RedisSessionManager) GetMaxCallPerWindow added in v1.4.0

func (sm *RedisSessionManager) GetMaxCallPerWindow() int64

func (*RedisSessionManager) GetOnlineUsers added in v1.5.0

func (sm *RedisSessionManager) GetOnlineUsers(ctx context.Context) (map[string]int64, error)

func (*RedisSessionManager) GetRequestInterval added in v1.4.0

func (sm *RedisSessionManager) GetRequestInterval() int64

func (*RedisSessionManager) GetSession

func (sm *RedisSessionManager) GetSession(ctx context.Context, owner string) (*APISession, error)

func (*RedisSessionManager) GetSessionKey

func (sm *RedisSessionManager) GetSessionKey(sessionId string) string

func (*RedisSessionManager) GetWindowSize added in v1.4.0

func (sm *RedisSessionManager) GetWindowSize() int64

func (*RedisSessionManager) RecordAPICall

func (sm *RedisSessionManager) RecordAPICall(ctx context.Context, sessionValue string, owner string, url string) (*APISession, error)

func (*RedisSessionManager) SetSession

func (sm *RedisSessionManager) SetSession(ctx context.Context, owner string, session *APISession) error

func (*RedisSessionManager) StartSession added in v1.0.3

func (sm *RedisSessionManager) StartSession(ctx context.Context, owner string) (string, error)

StartSession creates a new session for the owner and insert to db

Returns:

  • sessionId string: id of new session
  • error: error if exists, nil is successful

func (*RedisSessionManager) StartSessionWithPayload added in v1.6.1

func (sm *RedisSessionManager) StartSessionWithPayload(ctx context.Context, owner string, payload map[string]any) (*APISession, error)

func (*RedisSessionManager) UpdateSession

func (sm *RedisSessionManager) UpdateSession(currentMillis int64, session *APISession) error

func (*RedisSessionManager) ValidateAPICall added in v1.0.0

func (sm *RedisSessionManager) ValidateAPICall(request *APIRequest, session *APISession, currentTime time.Time) error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL