diff --git a/go.mod b/go.mod index e1bf25eba0b..8cf7ceed732 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/h2non/filetype v1.1.3 github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef github.com/justincampbell/bigduration v0.0.0-20160531141349-e45bf03c0666 github.com/labstack/echo/v4 v4.11.2 diff --git a/go.sum b/go.sum index 111337d1a1a..aae84723103 100644 --- a/go.sum +++ b/go.sum @@ -214,6 +214,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= diff --git a/pkg/appfs/server.go b/pkg/appfs/server.go index 973572d70c0..ba7d92ad582 100644 --- a/pkg/appfs/server.go +++ b/pkg/appfs/server.go @@ -16,11 +16,13 @@ import ( "path" "strconv" "strings" + "sync" "time" "github.com/andybalholm/brotli" "github.com/cozy/cozy-stack/pkg/consts" web_utils "github.com/cozy/cozy-stack/pkg/utils" + lru "github.com/hashicorp/golang-lru/v2" "github.com/labstack/echo/v4" "github.com/ncw/swift/v2" "github.com/spf13/afero" @@ -99,9 +101,24 @@ func (g gzipReadCloser) Close() error { return nil } +type cacheEntry struct { + content []byte + headers swift.Headers +} + +var cache *lru.Cache[string, cacheEntry] +var initCacheOnce sync.Once + // NewSwiftFileServer returns provides the apps.FileServer implementation // using the swift backend as file server. func NewSwiftFileServer(conn *swift.Connection, appsType consts.AppType) FileServer { + initCacheOnce.Do(func() { + c, err := lru.New[string, cacheEntry](128) + if err != nil { + panic(err) + } + cache = c + }) return &swiftServer{ c: conn, container: containerName(appsType), @@ -109,9 +126,27 @@ func NewSwiftFileServer(conn *swift.Connection, appsType consts.AppType) FileSer } } +func (s *swiftServer) openWithCache(objName string) (io.ReadCloser, swift.Headers, error) { + entry, ok := cache.Get(objName) + if !ok { + f, h, err := s.c.ObjectOpen(s.ctx, s.container, objName, false, nil) + if err != nil { + return f, h, err + } + entry.headers = h + entry.content, err = io.ReadAll(f) + if err != nil { + return nil, h, err + } + cache.Add(objName, entry) + } + f := io.NopCloser(bytes.NewReader(entry.content)) + return f, entry.headers, nil +} + func (s *swiftServer) Open(slug, version, shasum, file string) (io.ReadCloser, error) { objName := s.makeObjectName(slug, version, shasum, file) - f, h, err := s.c.ObjectOpen(s.ctx, s.container, objName, false, nil) + f, h, err := s.openWithCache(objName) if err != nil { return nil, wrapSwiftErr(err) } @@ -127,7 +162,7 @@ func (s *swiftServer) Open(slug, version, shasum, file string) (io.ReadCloser, e func (s *swiftServer) ServeFileContent(w http.ResponseWriter, req *http.Request, slug, version, shasum, file string) error { objName := s.makeObjectName(slug, version, shasum, file) - f, h, err := s.c.ObjectOpen(s.ctx, s.container, objName, false, nil) + f, h, err := s.openWithCache(objName) if err != nil { return wrapSwiftErr(err) }