From 8d56ae726d6302ae60ef5b1f403f9fd9efb85687 Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 00:28:26 +0330 Subject: [PATCH 01/10] feat(cache): define interface for caching storage --- internal/cache/cache.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/internal/cache/cache.go b/internal/cache/cache.go index af6aad1..293438e 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -8,18 +8,17 @@ import ( "time" "github.com/pyr33x/proxio/pkg/err" - "github.com/redis/go-redis/v9" "go.uber.org/zap" ) -type Caching interface { - Get(ctx context.Context, key string) string - Put(ctx context.Context, key string, value any) error +type Store interface { + Get(ctx context.Context, key string) ([]byte, error) + Set(ctx context.Context, key string, value []byte, ttl time.Duration) error Clear(ctx context.Context) error } type Cache struct { - rdb *redis.Client + store Store logger *zap.Logger expiration time.Duration } @@ -30,11 +29,11 @@ type CacheValue struct { Body []byte } -func NewCacheRepository(rdb *redis.Client, logger *zap.Logger) *Cache { +func NewCacheRepository(store Store, logger *zap.Logger, ttl time.Duration) *Cache { return &Cache{ - rdb: rdb, + store: store, logger: logger, - expiration: 60 * time.Second, + expiration: ttl, } } @@ -44,7 +43,7 @@ func (c *Cache) Get(ctx context.Context, key string) (*CacheValue, bool) { return nil, false } - raw, err := c.rdb.Get(ctx, key).Bytes() + raw, err := c.store.Get(ctx, key) if err != nil { c.logger.Info("cache miss", zap.String("key", key), @@ -76,8 +75,7 @@ func (c *Cache) Put(ctx context.Context, key string, value CacheValue) error { return err } - err = c.rdb.Set(ctx, key, b, c.expiration).Err() - if err != nil { + if err := c.store.Set(ctx, key, b, c.expiration); err != nil { c.logger.Error("failed to write to cache", zap.String("key", key), zap.Duration("expiration", c.expiration), @@ -90,5 +88,5 @@ func (c *Cache) Put(ctx context.Context, key string, value CacheValue) error { } func (c *Cache) Clear(ctx context.Context) error { - return c.rdb.FlushAll(ctx).Err() + return c.store.Clear(ctx) } From f620c743fb1f0f08dd757227b8eb77a81b9f13e5 Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 00:28:42 +0330 Subject: [PATCH 02/10] feat(cache): implement redis over caching interface --- internal/cache/redis.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 internal/cache/redis.go diff --git a/internal/cache/redis.go b/internal/cache/redis.go new file mode 100644 index 0000000..466d1c2 --- /dev/null +++ b/internal/cache/redis.go @@ -0,0 +1,28 @@ +package cache + +import ( + "context" + "time" + + "github.com/redis/go-redis/v9" +) + +type redisStore struct { + client *redis.Client +} + +func NewRedisStore(client *redis.Client) *redisStore { + return &redisStore{client: client} +} + +func (r *redisStore) Get(ctx context.Context, key string) ([]byte, error) { + return r.client.Get(ctx, key).Bytes() +} + +func (r *redisStore) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error { + return r.client.Set(ctx, key, value, ttl).Err() +} + +func (r *redisStore) Clear(ctx context.Context) error { + return r.client.FlushAll(ctx).Err() +} From c5581cc13866c6bcd86825affe90352b6d0d6547 Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 00:29:20 +0330 Subject: [PATCH 03/10] feat(proxy): merge redis cache abstraction with caching layer --- internal/proxy/proxy.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index aa0c804..31e1ab4 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -28,6 +28,7 @@ type OriginServer struct { func NewProxyServer(ctx context.Context, cfg *config.Config, logger *zap.Logger) *http.Server { rdb := redis.New(ctx, &cfg.Redis, logger).GetClient() + rdbCache := cache.NewRedisStore(rdb) srv := &Server{ Proxy: ProxyServer{ @@ -36,7 +37,7 @@ func NewProxyServer(ctx context.Context, cfg *config.Config, logger *zap.Logger) Origin: OriginServer{ URL: cfg.Server.Origin.URL, }, - Cache: cache.NewCacheRepository(rdb, logger), + Cache: cache.NewCacheRepository(rdbCache, logger, 60*time.Second), logger: logger, } From ba4e36339e693afb8cd404f138b0d273c6a7f837 Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 00:32:57 +0330 Subject: [PATCH 04/10] feat(config): use configurable ttl using env --- internal/proxy/proxy.go | 2 +- pkg/config/config.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 31e1ab4..2fee046 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -37,7 +37,7 @@ func NewProxyServer(ctx context.Context, cfg *config.Config, logger *zap.Logger) Origin: OriginServer{ URL: cfg.Server.Origin.URL, }, - Cache: cache.NewCacheRepository(rdbCache, logger, 60*time.Second), + Cache: cache.NewCacheRepository(rdbCache, logger, time.Duration(cfg.Server.Proxy.TTL)*time.Second), logger: logger, } diff --git a/pkg/config/config.go b/pkg/config/config.go index ad4250f..74fbaea 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -19,6 +19,7 @@ type Origin struct { type Proxy struct { Port string + TTL int64 } type Zap struct { @@ -45,6 +46,7 @@ func New() *Config { }, Proxy: Proxy{ Port: envy.GetString("PROXY_PORT", "1337"), + TTL: envy.GetInt64("PROXY_TTL", 60), // in seconds }, }, Zap: Zap{ From 0d62ff0f11f03e04e5aa133b6b99ccbb9bec6e9b Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 00:44:58 +0330 Subject: [PATCH 05/10] feat(cache): implement memory abstraction for caching --- internal/cache/memory.go | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 internal/cache/memory.go diff --git a/internal/cache/memory.go b/internal/cache/memory.go new file mode 100644 index 0000000..0056cf3 --- /dev/null +++ b/internal/cache/memory.go @@ -0,0 +1,55 @@ +package cache + +import ( + "context" + "sync" + "time" +) + +type memoryEntry struct { + value []byte + expiration time.Time +} + +type memoryStore struct { + data sync.Map +} + +func NewMemoryStore() *memoryStore { + return &memoryStore{} +} + +func (m *memoryStore) Get(ctx context.Context, key string) ([]byte, error) { + raw, ok := m.data.Load(key) + if !ok { + return nil, nil + } + + entry := raw.(memoryEntry) + + if !entry.expiration.IsZero() && time.Now().After(entry.expiration) { + m.data.Delete(key) + return nil, nil + } + + return entry.value, nil +} + +func (m *memoryStore) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error { + var exp time.Time + if ttl > 0 { + exp = time.Now().Add(ttl) + } + + m.data.Store(key, memoryEntry{ + value: value, + expiration: exp, + }) + + return nil +} + +func (m *memoryStore) Clear(ctx context.Context) error { + m.data = sync.Map{} + return nil +} From e5810dc518bfe09b49371d904807f65dbcb749dd Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 00:45:20 +0330 Subject: [PATCH 06/10] fix(cache): check if raw is nil due to memory nil return when no key has found --- internal/cache/cache.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 293438e..a483439 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -52,6 +52,12 @@ func (c *Cache) Get(ctx context.Context, key string) (*CacheValue, bool) { return nil, false } + // raw is nil (key doesn't exist) + if raw == nil { + c.logger.Info("cache miss", zap.String("key", key), zap.String("state", "MISS")) + return nil, false + } + var val CacheValue if err := json.Unmarshal(raw, &val); err != nil { return nil, false From 0894285f750fa6ddd931a2a4b16ae6aaf6e366e8 Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 00:45:47 +0330 Subject: [PATCH 07/10] feat(config): use dynamic env to choose caching options --- internal/proxy/proxy.go | 11 ++++++++--- pkg/config/config.go | 10 ++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 2fee046..3d51ce9 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -27,8 +27,13 @@ type OriginServer struct { } func NewProxyServer(ctx context.Context, cfg *config.Config, logger *zap.Logger) *http.Server { - rdb := redis.New(ctx, &cfg.Redis, logger).GetClient() - rdbCache := cache.NewRedisStore(rdb) + var cacheStorage cache.Store + + if cfg.Server.Proxy.Cache == "redis" { + rdb := redis.New(ctx, &cfg.Redis, logger).GetClient() + cacheStorage = cache.NewRedisStore(rdb) + } + cacheStorage = cache.NewMemoryStore() srv := &Server{ Proxy: ProxyServer{ @@ -37,7 +42,7 @@ func NewProxyServer(ctx context.Context, cfg *config.Config, logger *zap.Logger) Origin: OriginServer{ URL: cfg.Server.Origin.URL, }, - Cache: cache.NewCacheRepository(rdbCache, logger, time.Duration(cfg.Server.Proxy.TTL)*time.Second), + Cache: cache.NewCacheRepository(cacheStorage, logger, time.Duration(cfg.Server.Proxy.TTL)*time.Second), logger: logger, } diff --git a/pkg/config/config.go b/pkg/config/config.go index 74fbaea..e8fabdd 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -18,8 +18,9 @@ type Origin struct { } type Proxy struct { - Port string - TTL int64 + Port string + Cache string + TTL int64 } type Zap struct { @@ -45,8 +46,9 @@ func New() *Config { URL: envy.GetString("ORIGIN_URL", "http://dummyjson.com"), }, Proxy: Proxy{ - Port: envy.GetString("PROXY_PORT", "1337"), - TTL: envy.GetInt64("PROXY_TTL", 60), // in seconds + Port: envy.GetString("PROXY_PORT", "1337"), + Cache: envy.GetString("PROXY_CACHE", "memory"), + TTL: envy.GetInt64("PROXY_TTL", 60), // in seconds }, }, Zap: Zap{ From cb8416020871674897e85b45be265ae108a1c903 Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 00:57:04 +0330 Subject: [PATCH 08/10] chore(proxio): update readme to notice new cache abstraction --- README.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e1b058d..080fa78 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,23 @@ You can set environment variables to configure the proxy before running it. |-----------------|-------------------------------------|------------------------| | `ORIGIN_URL` | The origin server URL to proxy | `http://dummyjson.com` | | `PROXY_PORT` | Port for the proxy server to listen | `1337` | +| `PROXY_CACHE` | Cache storage (redis/memory) | `memory` | +| `PROXY_TTL` | Cache TTL (seconds) | `60` | | `ZAP_ENV` | Logging environment (dev/prod) | `dev` | -| `REDIS_HOST` | Redis server host | `redis_proxio` | +| `REDIS_HOST` | Redis server host (used only if cache = redis) | `redis_proxio` | | `REDIS_PORT` | Redis server port | `6379` | | `REDIS_USERNAME` | Redis username | `proxio` | | `REDIS_PASSWORD` | Redis password | *(empty)* | | `REDIS_DATABASE` | Redis database number | `0` | +### Notes on Caching +- **Default cache storage is `memory`** +- When using memory cache, **you do not need a Redis instance** +- Switch to Redis by setting: +```env +PROXY_CACHE="redis" +``` + ## Run Proxio ```bash @@ -40,7 +50,11 @@ You can set environment variables to configure the proxy before running it. ./proxio # Or set custom variables inline -ORIGIN_URL="http://example.com" PROXY_PORT="8080" ./proxio +ORIGIN_URL="http://example.com" \ +PROXY_PORT="8080" \ +PROXY_CACHE="redis" \ +PROXY_TTL="120" \ +./proxio ``` Cached responses will be stored in Redis and served quickly on repeated requests. @@ -56,12 +70,20 @@ docker build -t proxio . docker run -d \ -e ORIGIN_URL="http://example.com" \ -e PROXY_PORT="8080" \ + -p 8080:8080 \ + proxio +``` + +If using redis: +```bash +docker run -d \ + -e PROXY_CACHE="redis" \ -e REDIS_HOST="redis" \ -p 8080:8080 \ proxio ``` -## Using Docker +## Using Container Registries ```bash # Pull from Docker Hub docker pull me3di/proxio:latest @@ -71,8 +93,6 @@ docker pull ghcr.io/pyr33x/proxio:latest # Or build locally docker build -t proxio . - -# Run with Docker docker run -d -p 8080:8080 ghcr.io/pyr33x/proxio:latest ``` @@ -82,6 +102,7 @@ Run with: ```bash docker compose up -d --build ``` +If `PROXY_CACHE=memory`, Redis won't be used even if it’s running. ## Contributing Feel free to open issues or submit pull requests. Any help is appreciated! From 4336d809a3d8ffd5382560c9043e2e0d0177860b Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 01:02:49 +0330 Subject: [PATCH 09/10] fix(proxio): triggered by coderabbit --- internal/cache/memory.go | 5 ++++- internal/cache/redis.go | 2 +- internal/proxy/proxy.go | 10 +++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/internal/cache/memory.go b/internal/cache/memory.go index 0056cf3..91ce45a 100644 --- a/internal/cache/memory.go +++ b/internal/cache/memory.go @@ -50,6 +50,9 @@ func (m *memoryStore) Set(ctx context.Context, key string, value []byte, ttl tim } func (m *memoryStore) Clear(ctx context.Context) error { - m.data = sync.Map{} + m.data.Range(func(key, _ any) bool { + m.data.Delete(key) + return true + }) return nil } diff --git a/internal/cache/redis.go b/internal/cache/redis.go index 466d1c2..e6b06ae 100644 --- a/internal/cache/redis.go +++ b/internal/cache/redis.go @@ -24,5 +24,5 @@ func (r *redisStore) Set(ctx context.Context, key string, value []byte, ttl time } func (r *redisStore) Clear(ctx context.Context) error { - return r.client.FlushAll(ctx).Err() + return r.client.FlushDB(ctx).Err() } diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 3d51ce9..6f4ae26 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -30,10 +30,14 @@ func NewProxyServer(ctx context.Context, cfg *config.Config, logger *zap.Logger) var cacheStorage cache.Store if cfg.Server.Proxy.Cache == "redis" { - rdb := redis.New(ctx, &cfg.Redis, logger).GetClient() - cacheStorage = cache.NewRedisStore(rdb) + adapter := redis.New(ctx, &cfg.Redis, logger).GetClient() + if adapter == nil { + logger.Warn("failed to connect to redis, falling back to memory store") + cacheStorage = cache.NewMemoryStore() + } + } else { + cacheStorage = cache.NewMemoryStore() } - cacheStorage = cache.NewMemoryStore() srv := &Server{ Proxy: ProxyServer{ From f9baf6553b29dea49722e1f88cfe0140b6755b7a Mon Sep 17 00:00:00 2001 From: pyr33x Date: Sat, 6 Dec 2025 01:08:12 +0330 Subject: [PATCH 10/10] fix(proxy): triggered by coderabbit --- internal/proxy/proxy.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 6f4ae26..13ea02a 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -30,10 +30,12 @@ func NewProxyServer(ctx context.Context, cfg *config.Config, logger *zap.Logger) var cacheStorage cache.Store if cfg.Server.Proxy.Cache == "redis" { - adapter := redis.New(ctx, &cfg.Redis, logger).GetClient() + adapter := redis.New(ctx, &cfg.Redis, logger) if adapter == nil { logger.Warn("failed to connect to redis, falling back to memory store") cacheStorage = cache.NewMemoryStore() + } else { + cacheStorage = cache.NewRedisStore(adapter.GetClient()) } } else { cacheStorage = cache.NewMemoryStore()