diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go new file mode 100644 index 0000000..526319b --- /dev/null +++ b/pkg/cache/cache.go @@ -0,0 +1,38 @@ +// ___ ____ ___ ___ +// \ \ / / | _ | __| \ \ / / || | __ || || _ | +// \ \/ / |___ | |__ \ \/ / || |___ || ||___| +// \ / | _ | _ | \ / || __ | || ||\\ +// \/ |___ |___ | \/ || ____| || || \\ +// +// Copyright (c) 2021 Piotr Biernat. https://pbiernat.dev. MIT License +// Repo: https://git.pbiernat.dev/golang/vegvisir + +package cache + +import ( + "log" + "vegvisir/pkg/config" +) + +const ( + TYPE_REDIS = "redis" + TYPE_MEMORY = "memory" +) + +func GetCacheDatastore(config config.Cache) *CacheDatastore { + var datastore CacheDatastore + + if config.Type == TYPE_REDIS { + datastore = NewRedisDatastore(config.Host, config.Port) + } else { + datastore = NewMemoryDatastore() + } + + // fail-safe switch to memory datasource + if !datastore.IsConnected() { + log.Println("Cache server is not responding, switching to memory cache.") + datastore = NewMemoryDatastore() + } + + return &datastore +} diff --git a/pkg/cache/memory_datastore.go b/pkg/cache/memory_datastore.go index 7c2a997..411c453 100644 --- a/pkg/cache/memory_datastore.go +++ b/pkg/cache/memory_datastore.go @@ -11,25 +11,39 @@ package cache import ( "errors" + "time" ) +type TtlItem struct { + ts int // timestamp + ttl int // ttl in seconds +} + func NewMemoryDatastore() *MemoryDatastore { return &MemoryDatastore{ cache: make(map[string]interface{}), + ts: make(map[string]TtlItem), } } type MemoryDatastore struct { cache map[string]interface{} + ts map[string]TtlItem } func (ds *MemoryDatastore) SetKey(key string, data interface{}, ttl int) error { ds.cache[key] = data + ds.ts[key] = TtlItem{ + ts: time.Now().Second(), + ttl: ttl, + } return nil } func (ds *MemoryDatastore) GetKey(key string) (interface{}, error) { + ds.gc(key) // remove key is time of creation is outdated + if data, ok := ds.cache[key]; ok { return data, nil } @@ -40,3 +54,9 @@ func (ds *MemoryDatastore) GetKey(key string) (interface{}, error) { func (ds *MemoryDatastore) IsConnected() bool { return true } + +func (ds *MemoryDatastore) gc(key string) { + if item, ok := ds.ts[key]; ok && item.ts < time.Now().Second()-item.ttl { + delete(ds.cache, key) + } +} diff --git a/pkg/cache/response.go b/pkg/cache/response.go index 52562e4..bd036e4 100644 --- a/pkg/cache/response.go +++ b/pkg/cache/response.go @@ -14,10 +14,12 @@ import ( "log" ) +type Headers map[string]string + type ResponseCache struct { - URL string - Body string - // Headers map[string]string + URL string + Body string + Headers Headers } type ResponseCacheManager struct { @@ -49,7 +51,7 @@ func (rm *ResponseCacheManager) Save(name string, r ResponseCache) bool { name = rm.prefix + name err = rm.datastore.SetKey(name, string(data), rm.ttl) if err != nil { - log.Println("Response-cache:", err, name) // FIXME + log.Println("Response-cache:", err, name) return false } diff --git a/pkg/config/config.go b/pkg/config/config.go index 23b6fff..b322d9f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -17,6 +17,7 @@ import ( type Config struct { Server Server + Cache Cache Backends map[string]Backend confPath string @@ -27,6 +28,17 @@ type Server struct { Port int } +type Cache struct { + Type string + Host string + Port int + Username string + Password string + Database string + RouteTtl int + ResponseTtl int +} + type Backend struct { PrefixUrl string BackendAddress string diff --git a/pkg/main.go b/pkg/main.go index 27e15bf..3d4ee7f 100644 --- a/pkg/main.go +++ b/pkg/main.go @@ -19,15 +19,13 @@ import ( ) var ( - cFile = flag.String("c", "vegvisir.json", "Path to config file") - + cPath = flag.String("c", "vegvisir.json", "Path to config file") // for profiling... cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") memprofile = flag.String("memprofile", "", "write memory profile to `file`") ) func main() { - flag.Parse() // cpu profiling if *cpuprofile != "" { @@ -42,7 +40,7 @@ func main() { defer pprof.StopCPUProfile() } - server.NewServer(*cFile).Run() + server.NewServer(*cPath).Run() // memory profiling if *memprofile != "" { diff --git a/pkg/server/server.go b/pkg/server/server.go index 31790d8..af33c57 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -23,8 +23,8 @@ import ( ) const ( - Version = "0.1-dev" - Name = "Vegvisir/" + Version + ServerVersion = "0.1-dev" + ServerName = "Vegvisir/" + ServerVersion ) type Server struct { @@ -40,23 +40,13 @@ func NewServer(cPath string) *Server { log.Fatalln("Unable to find config file: ", cPath, err) } - server := &Server{ + datastore := cache.GetCacheDatastore(config.Cache) + + return &Server{ config: config, + router: NewRouter(config, *datastore, config.Cache.RouteTtl), + respCM: cache.NewResponseCacheManager(*datastore, config.Cache.ResponseTtl), } - - redisDS := cache.NewRedisDatastore("127.0.0.1", 6379) // FIXME use config or env... - if !redisDS.IsConnected() { - log.Println("Redis server not responding, switching to memory cache...") - - memDS := cache.NewMemoryDatastore() - server.router = NewRouter(config, memDS, 30) //FIXME for memory datasource ttl is useles right now... - server.respCM = cache.NewResponseCacheManager(memDS, 30) //FIXME for memory datasource ttl is useles right now... - } else { - server.router = NewRouter(config, redisDS, 30) //FIXME use ttl(seconds) from config or env... - server.respCM = cache.NewResponseCacheManager(redisDS, 30) //FIXME use ttl(seconds) from config or env... - } - - return server } func (s *Server) Run() { @@ -89,11 +79,11 @@ func (s *Server) Shutdown(ctx context.Context) { // TODO: wait for all connectio } func (s *Server) mainHandler(ctx *fasthttp.RequestCtx) { - ctx.Response.Header.Add(fasthttp.HeaderServer, Name) + // http := client.NewHttpClient(ctx) // move all below logic to concrete handler or sth.... reqUrl, sReqUrl, sReqMethod := ctx.RequestURI(), string(ctx.RequestURI()), string(ctx.Method()) - log.Println("Incomming request:", sReqMethod, sReqUrl) + log.Println("Incoming request:", sReqMethod, sReqUrl) found, route := s.router.FindByRequestURL(reqUrl) if !found { @@ -106,7 +96,12 @@ func (s *Server) mainHandler(ctx *fasthttp.RequestCtx) { if ok, data := s.respCM.Load(sReqUrl); ok { log.Println("Read resp from cache: ", route.TargetUrl) - ctx.SetBody([]byte(data.Body)) // FIXME missing headers etc... + // copy headers and body from cache + ctx.Response.Header.DisableNormalizing() + for key, value := range data.Headers { + ctx.Response.Header.Set(key, value) + } + ctx.SetBody([]byte(data.Body)) } else { log.Println("Send req to backend url: ", route.TargetUrl) @@ -126,15 +121,23 @@ func (s *Server) mainHandler(ctx *fasthttp.RequestCtx) { return } - ctx.Response.Header.SetBytesV(fasthttp.HeaderContentType, bckResp.Header.ContentType()) - ctx.SetStatusCode(bckResp.StatusCode()) + headers := make(cache.Headers) + // rewrite headers from backend to gateway response + bckResp.Header.Set(fasthttp.HeaderServer, ServerName) + bckResp.Header.Del(fasthttp.HeaderXPoweredBy) + ctx.Response.Header.DisableNormalizing() + bckResp.Header.VisitAll(func(key, value []byte) { + headers[string(key)] = string(value) + ctx.Response.Header.SetBytesKV(key, value) + }) + // ctx.SetStatusCode(bckResp.StatusCode()) ctx.SetBody(bckResp.Body()) // save response to cache respCache := cache.ResponseCache{ - URL: sReqUrl, - Body: string(bckResp.Body()), - // Headers: [] + URL: sReqUrl, + Body: string(bckResp.Body()), + Headers: headers, } // FIXME: prepare resp cache struct in respCM.Save method or other service... s.respCM.Save(sReqUrl, respCache) } diff --git a/vegvisir.json.dist b/vegvisir.json.dist index 8394cc5..c98251b 100644 --- a/vegvisir.json.dist +++ b/vegvisir.json.dist @@ -29,5 +29,15 @@ "target": "article-global/$1" }] } + }, + "cache": { + "type": "redis", + "host": "localhost", + "port": 6379, + "username": "", + "password": "", + "database": "0", + "routeTtl": 300, + "responseTtl": 300 } } \ No newline at end of file