Skip to content

Commit 4b501d4

Browse files
committed
wip: scope headers
1 parent bdc187a commit 4b501d4

File tree

13 files changed

+293
-167
lines changed

13 files changed

+293
-167
lines changed

api/handlers/cache_handlers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ const CACHE_STATUS_BYPASS = "BYPASS"
1717
const CACHE_STATUS_HIT = "HIT"
1818
const CACHE_STATUS_MISS = "MISS"
1919

20-
func GetCacheHandler(cache *graphcache.GraphCache, cfg *config.Config) http.HandlerFunc {
20+
func GetCacheHandler(cfg *config.Config) http.HandlerFunc {
2121
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22-
22+
cache := graphcache.NewGraphCacheWithOptions(GetCacheOptions(cfg, GetScopeValues(cfg, r)))
2323
w.Header().Add("Content-Type", "application/json")
2424

2525
// Create a new HTTP request with the same method, URL, and body as the original request

api/handlers/cache_purge_handlers.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,29 @@ import (
77
"net/http"
88
)
99

10-
func GetFlushCacheHandler(Cache *graphcache.GraphCache, cfg *config.Config) http.HandlerFunc {
10+
type FlushCacheByTypeRequest struct {
11+
Type string `json:"type"`
12+
ID string `json:"id"`
13+
}
14+
15+
func GetFlushCacheHandler(cfg *config.Config) http.HandlerFunc {
1116
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12-
Cache.Flush()
17+
cache := graphcache.NewGraphCacheWithOptions(GetCacheOptions(cfg, GetScopeValues(cfg, r)))
18+
cache.Flush()
1319
w.WriteHeader(http.StatusOK)
1420
w.Write([]byte("success"))
1521
})
1622
}
1723

18-
func GetFlushCacheByTypeHandler(Cache *graphcache.GraphCache, cfg *config.Config) http.HandlerFunc {
24+
func GetFlushCacheByTypeHandler(cfg *config.Config) http.HandlerFunc {
1925
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20-
flushByTypeRequest := struct {
21-
Type string `json:"type"`
22-
ID string `json:"id"`
23-
}{}
26+
cache := graphcache.NewGraphCacheWithOptions(GetCacheOptions(cfg, GetScopeValues(cfg, r)))
27+
flushByTypeRequest := FlushCacheByTypeRequest{}
2428
err := json.NewDecoder(r.Body).Decode(&flushByTypeRequest)
2529
if err != nil {
2630
http.Error(w, "error decoding request", http.StatusBadRequest)
2731
}
28-
Cache.FlushByType(flushByTypeRequest.Type, flushByTypeRequest.ID)
32+
cache.FlushByType(flushByTypeRequest.Type, flushByTypeRequest.ID)
2933
w.WriteHeader(http.StatusOK)
3034
w.Write([]byte("success"))
3135
})

api/handlers/debug_handlers.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import (
77
"net/http"
88
)
99

10-
func GetDebugHandler(Cache *graphcache.GraphCache, cfg *config.Config) http.HandlerFunc {
10+
func GetDebugHandler(cfg *config.Config) http.HandlerFunc {
1111
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12-
resp := Cache.Look()
12+
cache := graphcache.NewGraphCacheWithOptions(GetCacheOptions(cfg, GetScopeValues(cfg, r)))
13+
resp := cache.Look()
1314
br, err := json.Marshal(resp)
1415
if err != nil {
1516
http.Error(w, "error marshalling response", http.StatusInternalServerError)

api/handlers/handlers.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,44 @@
11
package handlers
22

33
import (
4+
"encoding/base64"
5+
"fmt"
46
"graphql_cache/config"
57
"graphql_cache/graphcache"
68
"net/http"
9+
"strings"
710
)
811

9-
func GetHandlers(cache *graphcache.GraphCache, cfg *config.Config) *http.ServeMux {
12+
func GetHandlers(cfg *config.Config) *http.ServeMux {
1013
api := http.NewServeMux()
11-
api.Handle(cfg.Handlers.DebugPath, GetDebugHandler(cache, cfg))
12-
api.Handle(cfg.Handlers.FlushAllPath, GetFlushCacheHandler(cache, cfg))
13-
api.Handle(cfg.Handlers.FlushByTypePath, GetFlushCacheByTypeHandler(cache, cfg))
14-
api.Handle(cfg.Handlers.GraphQLPath, GetCacheHandler(cache, cfg))
14+
api.Handle(cfg.Handlers.DebugPath, GetDebugHandler(cfg))
15+
api.Handle(cfg.Handlers.FlushAllPath, GetFlushCacheHandler(cfg))
16+
api.Handle(cfg.Handlers.FlushByTypePath, GetFlushCacheByTypeHandler(cfg))
17+
api.Handle(cfg.Handlers.GraphQLPath, GetCacheHandler(cfg))
1518
return api
1619
}
20+
21+
func GetCacheOptions(cfg *config.Config, values []interface{}) *graphcache.GraphCacheOptions {
22+
valueStr := make([]string, 0)
23+
for _, val := range values {
24+
valueStr = append(valueStr, fmt.Sprintf("%v", val))
25+
}
26+
valueHash := base64.StdEncoding.EncodeToString([]byte(strings.Join(valueStr, "::")))
27+
28+
return &graphcache.GraphCacheOptions{
29+
Backend: graphcache.CacheBackend(cfg.CacheBackend),
30+
RedisHost: cfg.Redis.Host,
31+
RedisPort: cfg.Redis.Port,
32+
Prefix: valueHash,
33+
}
34+
}
35+
36+
func GetScopeValues(cfg *config.Config, r *http.Request) []interface{} {
37+
values := make([]interface{}, 0)
38+
for _, header := range cfg.ScopeHeaders {
39+
if header != "" {
40+
values = append(values, r.Header.Get(header))
41+
}
42+
}
43+
return values
44+
}

api/server.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,16 @@ type Server struct {
1414
cfg *config.Config
1515
}
1616

17-
func NewGraphCache(cfg *config.Config) *graphcache.GraphCache {
18-
return graphcache.NewGraphCache(cfg)
17+
func NewGraphCache(opts *graphcache.GraphCacheOptions) *graphcache.GraphCache {
18+
return graphcache.NewGraphCacheWithOptions(opts)
1919
}
2020

2121
func NewServer(cfg *config.Config) *Server {
22-
cache := NewGraphCache(cfg)
2322
return &Server{
24-
cache: cache,
25-
cfg: cfg,
23+
cfg: cfg,
2624
httpServer: &http.Server{
2725
Addr: ":" + strconv.Itoa(cfg.Port),
28-
Handler: handlers.GetHandlers(cache, cfg),
26+
Handler: handlers.GetHandlers(cfg),
2927
},
3028
}
3129
}

cache/inmemory_cache.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,41 @@ import (
99
)
1010

1111
type InMemoryCache struct {
12-
data map[string]interface{}
12+
data map[string]interface{}
13+
prefix string
1314
}
1415

15-
func NewInMemoryCache() *InMemoryCache {
16+
func NewInMemoryCache(prefix string) *InMemoryCache {
1617
return &InMemoryCache{
17-
data: make(map[string]interface{}),
18+
data: make(map[string]interface{}),
19+
prefix: prefix,
1820
}
1921
}
2022

23+
func (c *InMemoryCache) Key(key string) string {
24+
return c.prefix + key
25+
}
26+
2127
func (c *InMemoryCache) Set(key string, value interface{}) error {
22-
c.data[key] = deepCopy(value)
28+
c.data[c.Key(key)] = deepCopy(value)
2329
return nil
2430
}
2531

2632
func (c *InMemoryCache) Get(key string) (interface{}, error) {
27-
value, exists := c.data[key]
33+
value, exists := c.data[c.Key(key)]
2834
if !exists {
2935
return nil, errors.New("key not found")
3036
}
3137
return deepCopy(value), nil
3238
}
3339

3440
func (c *InMemoryCache) Del(key string) error {
35-
c.Set(key, nil)
41+
c.Set(c.Key(key), nil)
3642
return nil
3743
}
3844

3945
func (c *InMemoryCache) Exists(key string) (bool, error) {
40-
_, exists := c.data[key]
46+
_, exists := c.data[c.Key(key)]
4147
return exists, nil
4248
}
4349

@@ -67,7 +73,7 @@ func (c *InMemoryCache) Flush() error {
6773
}
6874

6975
func (c *InMemoryCache) DeleteByPrefix(prefix string) error {
70-
var re = regexp.MustCompile(`(?m)` + strings.ReplaceAll(prefix, "*", ".*"))
76+
var re = regexp.MustCompile(`(?m)` + strings.ReplaceAll(c.Key(prefix), "*", ".*"))
7177

7278
for k := range c.data {
7379
// regex match the prefix to the key

cache/redis_cache.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,23 @@ var ctx = context.Background()
1212

1313
// RedisCache implements the Cache interface and uses Redis as the cache store
1414
type RedisCache struct {
15-
cache *redis.Client
15+
cache *redis.Client
16+
prefix string
1617
}
1718

18-
func NewRedisCache(host, port string) Cache {
19+
func (c *RedisCache) Key(key string) string {
20+
return c.prefix + key
21+
}
22+
23+
func NewRedisCache(host, port, prefix string) Cache {
1924
c := redis.NewClient(&redis.Options{
2025
Addr: host + ":" + port,
2126
Password: "", // no password set
2227
DB: 0, // use default DB
2328
})
2429
return &RedisCache{
25-
cache: c,
30+
cache: c,
31+
prefix: prefix,
2632
}
2733
}
2834

@@ -31,21 +37,21 @@ func (c *RedisCache) Set(key string, value interface{}) error {
3137
switch valueType.Kind() {
3238
case reflect.Map:
3339
br, _ := json.Marshal(value)
34-
c.cache.Set(ctx, key, string(br), 0)
35-
c.cache.Set(ctx, key+"_type", "reflect.Map", 0)
40+
c.cache.Set(ctx, c.Key(key), string(br), 0)
41+
c.cache.Set(ctx, c.Key(key+"_type"), "reflect.Map", 0)
3642
case reflect.Slice:
3743
br, _ := json.Marshal(value)
38-
c.cache.Set(ctx, key, string(br), 0)
39-
c.cache.Set(ctx, key+"_type", "reflect.Slice", 0)
44+
c.cache.Set(ctx, c.Key(key), string(br), 0)
45+
c.cache.Set(ctx, c.Key(key+"_type"), "reflect.Slice", 0)
4046
default:
41-
c.cache.Set(ctx, key, value, 0)
47+
c.cache.Set(ctx, c.Key(key), value, 0)
4248
}
4349
return nil
4450
}
4551

4652
func (c *RedisCache) Get(key string) (interface{}, error) {
47-
typeValue, _ := c.cache.Get(ctx, key+"_type").Result()
48-
val, err := c.cache.Get(ctx, key).Result()
53+
typeValue, _ := c.cache.Get(ctx, c.Key(key+"_type")).Result()
54+
val, err := c.cache.Get(ctx, c.Key(key)).Result()
4955
if err != nil {
5056
return nil, err
5157
}
@@ -65,12 +71,12 @@ func (c *RedisCache) Get(key string) (interface{}, error) {
6571
}
6672

6773
func (c *RedisCache) Del(key string) error {
68-
c.cache.Del(ctx, key)
74+
c.cache.Del(ctx, c.Key(key))
6975
return nil
7076
}
7177

7278
func (c *RedisCache) Exists(key string) (bool, error) {
73-
val, err := c.cache.Exists(ctx, key).Result()
79+
val, err := c.cache.Exists(ctx, c.Key(key)).Result()
7480
if err != nil {
7581
return false, err
7682
}
@@ -95,7 +101,7 @@ func (c *RedisCache) Flush() error {
95101
}
96102

97103
func (c *RedisCache) DeleteByPrefix(prefix string) error {
98-
allKeys := c.cache.Keys(ctx, prefix+"*")
104+
allKeys := c.cache.Keys(ctx, c.Key(prefix+"*"))
99105
if allKeys == nil {
100106
return nil
101107
}

config.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,10 @@ port=6379
3131
# flush_by_type_path="/flush.type"
3232
# debug_path="/debug"
3333
# health_path="/health"
34-
# graphql_path="/graphql"
34+
# graphql_path="/graphql"
35+
36+
37+
# If you have an authenticated API and want to separate the cache based on what the headers are, you can configure the headers here.
38+
# The system will scope the cache based on the unique values of the headers you provide
39+
# For example, if you want to scope the cache based on the Authorization header, you can set the following:
40+
scope_headers=["Authorization", "X-API-Key"]

config/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ type Config struct {
2323
Host string `toml:"host"`
2424
Port int `toml:"port"`
2525
} `toml:"redis"`
26-
CacheHeaderName string `toml:"cache_header_name"`
26+
CacheHeaderName string `toml:"cache_header_name"`
27+
ScopeHeaders []string `toml:"scope_headers"`
2728
}
2829

2930
const CONFIG_FILE = "./config.toml"

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module graphql_cache
22

3-
go 1.22
4-
5-
toolchain go1.22.5
3+
go 1.20
64

75
require (
86
github.com/99designs/gqlgen v0.17.49

0 commit comments

Comments
 (0)