Compare commits

..

4 Commits

Author SHA1 Message Date
268cab5f79 Merge pull request 'develop-issue-4' (#8) from develop-issue-4 into develop
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://git.pbiernat.dev/golang/vegvisir/pulls/8
2021-07-25 20:10:28 +01:00
8c0f8d4151 [fix] Added support for headers cache
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-07-25 20:54:42 +02:00
1c2c0bb467 [fix] Added GC to memory cache
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-07-25 01:44:50 +02:00
58eaaef0b3 [refactor] Simplified selecting cache datasource and fail-safe mechanism
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-25 01:03:58 +02:00
7 changed files with 116 additions and 33 deletions

38
pkg/cache/cache.go vendored Normal file
View File

@ -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
}

View File

@ -11,25 +11,39 @@ package cache
import ( import (
"errors" "errors"
"time"
) )
type TtlItem struct {
ts int // timestamp
ttl int // ttl in seconds
}
func NewMemoryDatastore() *MemoryDatastore { func NewMemoryDatastore() *MemoryDatastore {
return &MemoryDatastore{ return &MemoryDatastore{
cache: make(map[string]interface{}), cache: make(map[string]interface{}),
ts: make(map[string]TtlItem),
} }
} }
type MemoryDatastore struct { type MemoryDatastore struct {
cache map[string]interface{} cache map[string]interface{}
ts map[string]TtlItem
} }
func (ds *MemoryDatastore) SetKey(key string, data interface{}, ttl int) error { func (ds *MemoryDatastore) SetKey(key string, data interface{}, ttl int) error {
ds.cache[key] = data ds.cache[key] = data
ds.ts[key] = TtlItem{
ts: time.Now().Second(),
ttl: ttl,
}
return nil return nil
} }
func (ds *MemoryDatastore) GetKey(key string) (interface{}, error) { 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 { if data, ok := ds.cache[key]; ok {
return data, nil return data, nil
} }
@ -40,3 +54,9 @@ func (ds *MemoryDatastore) GetKey(key string) (interface{}, error) {
func (ds *MemoryDatastore) IsConnected() bool { func (ds *MemoryDatastore) IsConnected() bool {
return true 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)
}
}

View File

@ -14,10 +14,12 @@ import (
"log" "log"
) )
type Headers map[string]string
type ResponseCache struct { type ResponseCache struct {
URL string URL string
Body string Body string
// Headers map[string]string Headers Headers
} }
type ResponseCacheManager struct { type ResponseCacheManager struct {
@ -49,7 +51,7 @@ func (rm *ResponseCacheManager) Save(name string, r ResponseCache) bool {
name = rm.prefix + name name = rm.prefix + name
err = rm.datastore.SetKey(name, string(data), rm.ttl) err = rm.datastore.SetKey(name, string(data), rm.ttl)
if err != nil { if err != nil {
log.Println("Response-cache:", err, name) // FIXME log.Println("Response-cache:", err, name)
return false return false
} }

View File

@ -17,6 +17,7 @@ import (
type Config struct { type Config struct {
Server Server Server Server
Cache Cache
Backends map[string]Backend Backends map[string]Backend
confPath string confPath string
@ -27,6 +28,17 @@ type Server struct {
Port int Port int
} }
type Cache struct {
Type string
Host string
Port int
Username string
Password string
Database string
RouteTtl int
ResponseTtl int
}
type Backend struct { type Backend struct {
PrefixUrl string PrefixUrl string
BackendAddress string BackendAddress string

View File

@ -19,15 +19,13 @@ import (
) )
var ( var (
cFile = flag.String("c", "vegvisir.json", "Path to config file") cPath = flag.String("c", "vegvisir.json", "Path to config file")
// for profiling... // for profiling...
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
memprofile = flag.String("memprofile", "", "write memory profile to `file`") memprofile = flag.String("memprofile", "", "write memory profile to `file`")
) )
func main() { func main() {
flag.Parse() flag.Parse()
// cpu profiling // cpu profiling
if *cpuprofile != "" { if *cpuprofile != "" {
@ -42,7 +40,7 @@ func main() {
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
} }
server.NewServer(*cFile).Run() server.NewServer(*cPath).Run()
// memory profiling // memory profiling
if *memprofile != "" { if *memprofile != "" {

View File

@ -23,8 +23,8 @@ import (
) )
const ( const (
Version = "0.1-dev" ServerVersion = "0.1-dev"
Name = "Vegvisir/" + Version ServerName = "Vegvisir/" + ServerVersion
) )
type Server struct { type Server struct {
@ -40,23 +40,13 @@ func NewServer(cPath string) *Server {
log.Fatalln("Unable to find config file: ", cPath, err) log.Fatalln("Unable to find config file: ", cPath, err)
} }
server := &Server{ datastore := cache.GetCacheDatastore(config.Cache)
return &Server{
config: config, 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() { 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) { 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.... // move all below logic to concrete handler or sth....
reqUrl, sReqUrl, sReqMethod := ctx.RequestURI(), string(ctx.RequestURI()), string(ctx.Method()) 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) found, route := s.router.FindByRequestURL(reqUrl)
if !found { if !found {
@ -106,7 +96,12 @@ func (s *Server) mainHandler(ctx *fasthttp.RequestCtx) {
if ok, data := s.respCM.Load(sReqUrl); ok { if ok, data := s.respCM.Load(sReqUrl); ok {
log.Println("Read resp from cache: ", route.TargetUrl) 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 { } else {
log.Println("Send req to backend url: ", route.TargetUrl) log.Println("Send req to backend url: ", route.TargetUrl)
@ -126,15 +121,23 @@ func (s *Server) mainHandler(ctx *fasthttp.RequestCtx) {
return return
} }
ctx.Response.Header.SetBytesV(fasthttp.HeaderContentType, bckResp.Header.ContentType()) headers := make(cache.Headers)
ctx.SetStatusCode(bckResp.StatusCode()) // 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()) ctx.SetBody(bckResp.Body())
// save response to cache // save response to cache
respCache := cache.ResponseCache{ respCache := cache.ResponseCache{
URL: sReqUrl, URL: sReqUrl,
Body: string(bckResp.Body()), Body: string(bckResp.Body()),
// Headers: [] Headers: headers,
} // FIXME: prepare resp cache struct in respCM.Save method or other service... } // FIXME: prepare resp cache struct in respCM.Save method or other service...
s.respCM.Save(sReqUrl, respCache) s.respCM.Save(sReqUrl, respCache)
} }

View File

@ -29,5 +29,15 @@
"target": "article-global/$1" "target": "article-global/$1"
}] }]
} }
},
"cache": {
"type": "redis",
"host": "localhost",
"port": 6379,
"username": "",
"password": "",
"database": "0",
"routeTtl": 300,
"responseTtl": 300
} }
} }