diff --git a/.env b/.env new file mode 100644 index 0000000..d74206e --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +MAIN_CONTAINER_PORT=8001 +CACHE_TTL=30s diff --git a/Dockerfile b/Dockerfile index 59cb83c..e77b084 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ ENV LANG C.UTF-8 ENV MAIN_CONTAINER_PORT "80" ENV REDIS_ADDRESS "127.0.0.1:6379" ENV REDIS_PASSWORD "" +ENV CACHE_TTL "30s" COPY --from=builder /app/main /app/main diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d5d822c --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +-include .env +export + +.PHONY: run +run: + go run -v cmd/sidecache/main.go diff --git a/pkg/cache/redis.go b/pkg/cache/redis.go index 47c3be0..fc4d2c4 100644 --- a/pkg/cache/redis.go +++ b/pkg/cache/redis.go @@ -3,7 +3,6 @@ package cache import ( "go.uber.org/zap" "os" - "strconv" "time" "github.com/go-redis/redis" @@ -27,9 +26,8 @@ func NewRedisRepository(logger *zap.Logger) (*RedisRepository, error) { return &RedisRepository{client: client, logger: logger}, nil } -func (repository *RedisRepository) SetKey(key string, value []byte, ttl int) { - duration, _ := time.ParseDuration(strconv.FormatInt(int64(ttl), 10)) - status := repository.client.Set(key, string(value), duration) +func (repository *RedisRepository) SetKey(key string, value []byte, ttl time.Duration) { + status := repository.client.Set(key, string(value), ttl) _, err := status.Result() if err != nil { repository.logger.Error(err.Error()) @@ -40,8 +38,11 @@ func (repository *RedisRepository) Get(key string) []byte { status := repository.client.Get(key) stringResult, err := status.Result() - if err != nil && err != redis.Nil { - repository.logger.Error(err.Error()) + if err != nil { + if err != redis.Nil { + repository.logger.Error(err.Error()) + } + return nil } return []byte(stringResult) diff --git a/pkg/cache/repository.go b/pkg/cache/repository.go index a88ff2a..30a8322 100644 --- a/pkg/cache/repository.go +++ b/pkg/cache/repository.go @@ -1,6 +1,10 @@ package cache +import ( + "time" +) + type Repository interface { - SetKey(key string, value []byte, ttl int) + SetKey(key string, value []byte, ttl time.Duration) Get(key string) []byte } diff --git a/pkg/server/server.go b/pkg/server/server.go index 2deb8c9..3c2966c 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -14,15 +14,14 @@ import ( "net/http/httputil" "net/url" "os" - "strconv" "strings" + "time" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/zeriontech/sidecache/pkg/cache" "go.uber.org/zap" ) -const CacheHeaderKey = "tysidecarcachable" const CacheHeaderEnabledKey = "sidecache-headers-enabled" const applicationDefaultPort = ":9191" @@ -51,59 +50,61 @@ func NewServer(repo cache.Repository, proxy *httputil.ReverseProxy, prom *Promet func (server CacheServer) Start(stopChan chan int) { server.Proxy.ModifyResponse = func(r *http.Response) error { - cacheHeaderValue := r.Header.Get(CacheHeaderKey) - if cacheHeaderValue != "" { - cacheHeadersEnabled := r.Header.Get(CacheHeaderEnabledKey) - maxAgeInSecond := server.GetHeaderTTL(cacheHeaderValue) - r.Header.Del("Content-Length") // https://github.com/golang/go/issues/14975 - var b []byte - var err error - if r.Header.Get("content-encoding") == "gzip" { - reader, _ := gzip.NewReader(r.Body) - b, err = ioutil.ReadAll(reader) - } else { - b, err = ioutil.ReadAll(r.Body) - } - if err != nil { - server.Logger.Error("Error while reading response body", zap.Error(err)) - return err - } + cacheHeadersEnabled := r.Header.Get(CacheHeaderEnabledKey) + maxAgeInSecond, err := time.ParseDuration(os.Getenv("CACHE_TTL")) - buf := server.gzipWriter(b) - go func(reqUrl *url.URL, data []byte, ttl int, cacheHeadersEnabled string) { - hashedURL := server.HashURL(server.ReorderQueryString(reqUrl)) - cacheData := CacheData{Body: data} - - if cacheHeadersEnabled == "true" { - headers := make(map[string]string) - for h, v := range r.Header { - headers[h] = strings.Join(v, ";") - } - cacheData.Headers = headers - } + if err != nil { + server.Logger.Error("invalid cache TTL", zap.Error(err)) + return nil + } - cacheDataBytes, _ := json.Marshal(cacheData) - server.Logger.Info(strconv.FormatBool(server.Repo == nil)) - server.Repo.SetKey(hashedURL, cacheDataBytes, ttl) - }(r.Request.URL, buf.Bytes(), maxAgeInSecond, cacheHeadersEnabled) + r.Header.Del("Content-Length") // https://github.com/golang/go/issues/14975 + var b []byte + if r.Header.Get("content-encoding") == "gzip" { + reader, _ := gzip.NewReader(r.Body) + b, err = ioutil.ReadAll(reader) + } else { + b, err = ioutil.ReadAll(r.Body) + } - err = r.Body.Close() - if err != nil { - server.Logger.Error("Error while closing response body", zap.Error(err)) - return err - } + if err != nil { + server.Logger.Error("Error while reading response body", zap.Error(err)) + return err + } - var body io.ReadCloser - if r.Header.Get("content-encoding") == "gzip" { - body = ioutil.NopCloser(buf) - } else { - body = ioutil.NopCloser(bytes.NewReader(b)) + buf := server.gzipWriter(b) + go func(reqUrl *url.URL, data []byte, ttl time.Duration, cacheHeadersEnabled string) { + hashedURL := server.HashURL(server.ReorderQueryString(reqUrl)) + cacheData := CacheData{Body: data} + + if cacheHeadersEnabled == "true" { + headers := make(map[string]string) + for h, v := range r.Header { + headers[h] = strings.Join(v, ";") + } + cacheData.Headers = headers } - r.Body = body + cacheDataBytes, _ := json.Marshal(cacheData) + server.Repo.SetKey(hashedURL, cacheDataBytes, ttl) + }(r.Request.URL, buf.Bytes(), maxAgeInSecond, cacheHeadersEnabled) + + err = r.Body.Close() + if err != nil { + server.Logger.Error("Error while closing response body", zap.Error(err)) + return err + } + + var body io.ReadCloser + if r.Header.Get("content-encoding") == "gzip" { + body = ioutil.NopCloser(buf) + } else { + body = ioutil.NopCloser(bytes.NewReader(b)) } + r.Body = body + return nil } @@ -171,6 +172,8 @@ func (server CacheServer) CacheHandler(w http.ResponseWriter, r *http.Request) { hashedURL := server.HashURL(server.ReorderQueryString(r.URL)) cachedDataBytes := server.CheckCache(hashedURL) + server.Logger.Info("serve request", zap.String("url", r.URL.String()), zap.Bool("cached", cachedDataBytes != nil)) + if cachedDataBytes != nil { w.Header().Add("X-Cache-Response-For", r.URL.String()) w.Header().Add("Content-Type", "application/json;charset=UTF-8") //todo get from cache? @@ -229,15 +232,6 @@ func writeHeaders(w http.ResponseWriter, headers map[string]string) { } } -func (server CacheServer) GetHeaderTTL(cacheHeaderValue string) int { - cacheValues := strings.Split(cacheHeaderValue, "=") - var maxAgeInSecond = 0 - if len(cacheValues) > 1 { - maxAgeInSecond, _ = strconv.Atoi(cacheValues[1]) - } - return maxAgeInSecond -} - func (server CacheServer) HashURL(url string) string { hasher := md5.New() hasher.Write([]byte(server.CacheKeyPrefix + "/" + url))