Skip to content

Commit

Permalink
🚧 add cache to golim
Browse files Browse the repository at this point in the history
  • Loading branch information
khalil committed Mar 22, 2024
1 parent 26918c0 commit df810bf
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 30 deletions.
66 changes: 66 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"context"
"encoding/json"
"github.com/khalil-farashiani/golim/role"
"github.com/redis/go-redis/v9"
"os"
"time"
)

type cache struct {
*redis.Client
}

func initRedis() *cache {
url := os.Getenv("REDIS_URI")
opts, err := redis.ParseURL(url)
if err != nil {
panic(err)
}
return &cache{
redis.NewClient(opts),
}
}

func (c *cache) increaseCap(ctx context.Context, rl *limiterRole, amount int64) {
key := operationIdToString[rl.operation] + " " + rl.endPoint
c.Do(ctx, "INCRBY", key, amount)
}

func (c *cache) decreaseCap(ctx context.Context, rl *limiterRole) {
key := operationIdToString[rl.operation] + " " + rl.endPoint
c.Do(ctx, "DECR", key)
}

func (c *cache) setLimiter(ctx context.Context, params *role.GetRoleParams, val *role.GetRoleRow) {
key := params.Operation + " " + params.Endpoint
err := c.Set(ctx, key, val, time.Minute*60).Err()
if err != nil {
panic(err)
}
}

func (c *cache) getLimiter(ctx context.Context, params role.GetRoleParams) *role.GetRoleRow {
var res role.GetRoleRow
var key = params.Operation + " " + params.Endpoint
val, err := c.Get(ctx, key).Result()
if err != nil && err != redis.Nil {
panic(err)
}
json.Unmarshal([]byte(val), &res)
return &res
}

func (c *cache) getUserRequestCap(ctx context.Context, ipAddr string, rl *limiterRole) int64 {
key := ipAddr + rl.endPoint + rl.endPoint
var res = new(int64)
val, err := c.Get(ctx, key).Result()

if err != nil && err != redis.Nil {
panic(err)
}
json.Unmarshal([]byte(val), res)
return *res
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ require (
)

require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/jedib0t/go-pretty/v6 v6.5.5 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.16.0 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/jedib0t/go-pretty/v6 v6.5.5 h1:PpIU8lOjxvVYGGKule0QxxJfNysUSbC9lggQU2cpZJc=
github.com/jedib0t/go-pretty/v6 v6.5.5/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
Expand All @@ -13,6 +17,8 @@ github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 h1:aiqS8aBlF9PsAKeMddMSfbwp3smONCn3
github.com/peterbourgon/ff/v4 v4.0.0-alpha.4/go.mod h1:H/13DK46DKXy7EaIxPhk2Y0EC8aubKm35nBjBe8AAGc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
128 changes: 102 additions & 26 deletions golim.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,47 @@ type limiterRole struct {
}

type limiter struct {
id interface{}
name string
operation int
id interface{}
name string
destination string
operation int
}

type golim struct {
limiter *limiter
limiterRole *limiterRole
port int64
skip bool
}

func (g *golim) getRoles(ctx context.Context, db *sql.DB) ([]role.Role, error) {
var operationIdToString = map[int]string{
1: "GET",
2: "POST",
3: "PUT",
4: "PATCH",
5: "DELETE",
}

func (g *golim) getRole(ctx context.Context, db *sql.DB, cache *cache) (role.GetRoleRow, error) {
params := toGetRole(g)
query := role.New(db)
return query.GetRoles(ctx)
data := cache.getLimiter(ctx, params)
if data != nil {
return *data, nil
}
row, err := query.GetRole(ctx, params)
if err != nil {
return role.GetRoleRow{}, err
}
go func() {
cache.setLimiter(ctx, params, &row)

Check failure on line 54 in golim.go

View workflow job for this annotation

GitHub Actions / build

cannot use params (variable of type role.GetRoleParams) as *role.GetRoleParams value in argument to cache.setLimiter
}()
return row, nil
}

func (g *golim) getRoles(ctx context.Context, db *sql.DB) ([]role.GetRolesRow, error) {
query := role.New(db)
return query.GetRoles(ctx, int64(g.limiterRole.limiterID))
}

func (g *golim) addRole(ctx context.Context, db *sql.DB) error {
Expand All @@ -47,8 +74,9 @@ func (g *golim) removeRole(ctx context.Context, db *sql.DB) error {
}

func (g *golim) createRateLimiter(ctx context.Context, db *sql.DB) error {
params := toCreateRateLimiter(g)
query := role.New(db)
_, err := query.CrateRateLimiter(ctx, g.limiter.name)
_, err := query.CrateRateLimiter(ctx, params)
return err
}

Expand All @@ -57,7 +85,11 @@ func (g *golim) removeRateLimiter(ctx context.Context, db *sql.DB) error {
return query.DeleteRateLimiter(ctx, g.limiter.id.(int64))
}

func (g *golim) ExecCMD(ctx context.Context, db *sql.DB) (interface{}, error) {
func (g *golim) ExecCMD(ctx context.Context, db *sql.DB, cache *cache) (interface{}, error) {

if g.port != 0 {
return startServer(g, db, cache)
}
if g.limiter != nil {
switch g.limiter.operation {
case createLimiterOperationID:
Expand All @@ -66,13 +98,15 @@ func (g *golim) ExecCMD(ctx context.Context, db *sql.DB) (interface{}, error) {
return nil, g.removeRateLimiter(ctx, db)
}
}
switch g.limiterRole.operation {
case addRoleOperationID:
return nil, g.addRole(ctx, db)
case removeRoleOperationID:
return nil, g.removeRole(ctx, db)
case getRolesOperationID:
return g.getRoles(ctx, db)
if g.limiterRole != nil {
switch g.limiterRole.operation {
case addRoleOperationID:
return nil, g.addRole(ctx, db)
case removeRoleOperationID:
return nil, g.removeRole(ctx, db)
case getRolesOperationID:
return g.getRoles(ctx, db)
}
}
return nil, errors.New("unsupported operation")
}
Expand All @@ -93,9 +127,29 @@ func (g *golim) createHelpCMD() *ff.Command {
}
}

func (g *golim) createRunCMD() *ff.Command {
runFlags := ff.NewFlagSet("run")
portNumber := runFlags.Int('p', "port", 8080, "The name of the golim to initialize")
return &ff.Command{
Name: "run",
Usage: "golim run -p <port number>",
ShortHelp: "Initializes a standalone rate golim",
Flags: runFlags,
Exec: func(ctx context.Context, args []string) error {
if g.skip {
return nil
}
g.port = int64(*portNumber)
g.skip = true
return nil
},
}
}

func (g *golim) createInitCMD() *ff.Command {
initFlags := ff.NewFlagSet("init")
limiterName := initFlags.String('n', "name", "", "The name of the golim to initialize")
destinationAddress := initFlags.String('d', "destination", "", "The name of the golim to initialize")
return &ff.Command{
Name: "init",
Usage: "golim init -n <limiter_name>",
Expand All @@ -105,11 +159,14 @@ func (g *golim) createInitCMD() *ff.Command {
if g.skip {
return nil
}
if *limiterName != "" {
if *limiterName != "" && *destinationAddress != "" {
g.limiter = &limiter{
name: *limiterName,
operation: createLimiterOperationID,
name: *limiterName,
destination: *destinationAddress,
operation: createLimiterOperationID,
}
} else {
return errors.New("name and destination is required")
}
g.skip = true
return nil
Expand Down Expand Up @@ -212,11 +269,13 @@ func (g *golim) createGetRolesCMD() *ff.Command {
if g.skip {
return nil
}
if *limiterID == 0 {
if *limiterID != 0 {
g.limiterRole = &limiterRole{
operation: getRolesOperationID,
limiterID: *limiterID,
}
} else {
return errors.New("limiter id is required")
}
g.skip = true
return nil
Expand All @@ -225,19 +284,36 @@ func (g *golim) createGetRolesCMD() *ff.Command {
}

func toCreateRoleParam(g *golim) role.CreateRoleParams {
var operation = map[int]string{
1: "GET",
2: "POST",
3: "PUT",
4: "PATCH",
5: "DELETE",
}
return role.CreateRoleParams{
Endpoint: g.limiterRole.endPoint,
Operation: operation[g.limiterRole.operation],
Operation: operationIdToString[g.limiterRole.operation],
BucketSize: int64(g.limiterRole.bucketSize),
AddTokenPerMin: int64(g.limiterRole.addToken),
InitialTokens: int64(g.limiterRole.initialToken),
RateLimiterID: int64(g.limiterRole.limiterID),
}
}

func toCreateRateLimiter(g *golim) role.CrateRateLimiterParams {
return role.CrateRateLimiterParams{
Name: g.limiter.name,
Destination: g.limiter.destination,
}
}

func toGetRole(g *golim) role.GetRoleParams {
return role.GetRoleParams{
Endpoint: g.limiterRole.endPoint,
Operation: operationIdToString[g.limiterRole.operation],
}
}

func toRole(row role.GetRoleRow) role.Role {
return role.Role{
Endpoint: row.Endpoint,
Operation: row.Operation,
BucketSize: row.BucketSize,
AddTokenPerMin: row.AddTokenPerMin,
InitialTokens: row.InitialTokens,
}
}
11 changes: 7 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,23 @@ func main() {
ctx := context.Background()

db := initDB(ctx)
cache := initRedis()
limiter, err := initFlags(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
data, err := limiter.ExecCMD(ctx, db)
data, err := limiter.ExecCMD(ctx, db, cache)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if data != nil {
fmt.Fprintf(os.Stdout, "%v", data)
makeTable(toSlice(data))
fmt.Fprintf(os.Stdout, "DONE")
return
}
fmt.Printf("%s", "DONE")
fmt.Printf("DONE")
}

// initFlags get command and flags from std input to create a golim or role
Expand All @@ -68,8 +70,9 @@ func initFlags(ctx context.Context) (*golim, error) {
removeCMD := golim.createRemoveCMD()
getCMD := golim.createGetRolesCMD()
removeLimiterCMD := golim.createRemoveCMD()
runCMD := golim.createRunCMD()

rootCmd.Subcommands = []*ff.Command{helpCMD, initCMD, addCMD, removeCMD, getCMD, removeLimiterCMD}
rootCmd.Subcommands = []*ff.Command{helpCMD, initCMD, addCMD, removeCMD, getCMD, removeLimiterCMD, runCMD}
if err := rootCmd.ParseAndRun(ctx, os.Args[1:]); err != nil {
return nil, fmt.Errorf("%s\n%s", ffhelp.Command(rootCmd), err)
}
Expand Down
Loading

0 comments on commit df810bf

Please sign in to comment.