From c5d402d10ec5ddb8947a5e4b5bcaa7b2dfe66273 Mon Sep 17 00:00:00 2001 From: Daniel Menet Date: Mon, 21 Aug 2023 08:56:35 +0200 Subject: [PATCH] cache rendered svgs when serving --- cache.go | 46 +++++++++++++++++++++++++ cache_test.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++ cli.go | 7 ++-- server.go | 19 +++++++++-- 4 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 cache.go create mode 100644 cache_test.go diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..286f9d5 --- /dev/null +++ b/cache.go @@ -0,0 +1,46 @@ +package main + +import ( + "time" +) + +type cache struct { + timeout time.Duration + store map[string]cacheElement +} + +func NewCache(timeout time.Duration) *cache { + return &cache{ + timeout: timeout, + store: map[string]cacheElement{}, + } +} + +func (c *cache) Add(key string, data []byte) { + c.store[key] = cacheElement{ + cachedAt: time.Now(), + data: data, + } +} + +func (c *cache) Get(key string) ([]byte, bool) { + elem, ok := c.store[key] + if !ok { + return nil, false + } + age := time.Since(elem.cachedAt) + if age >= c.timeout { + delete(c.store, key) + return nil, false + } + return elem.data, true +} + +func (c *cache) Purge() { + c.store = make(map[string]cacheElement) +} + +type cacheElement struct { + data []byte + cachedAt time.Time +} diff --git a/cache_test.go b/cache_test.go new file mode 100644 index 0000000..8ea13db --- /dev/null +++ b/cache_test.go @@ -0,0 +1,93 @@ +package main + +import ( + "bytes" + "testing" + "time" +) + +func TestCache_Add(t *testing.T) { + // Create a new cache instance + cache := NewCache(time.Minute) + + // Add a cache element with a key and data + key := "test" + data := []byte("test data") + cache.Add(key, data) + + // Check if the cache element exists in the store + if _, ok := cache.store[key]; !ok { + t.Errorf("Cache element with key '%s' not found", key) + } + + // Check if the data of the cache element is correct + if got := cache.store[key].data; !bytes.Equal(got, data) { + t.Errorf("Cache element data mismatch, got %v, want %v", got, data) + } +} + +func TestCache_Get(t *testing.T) { + // Create a new cache instance + cache := NewCache(time.Minute) + + // Add a cache element with a key and data + key := "test" + data := []byte("test data") + cache.Add(key, data) + + // Retrieve the cache element using the Get method + got, ok := cache.Get(key) + + // Check if the cache element is found + if !ok { + t.Errorf("Cache element with key '%s' not found", key) + } + + // Check if the retrieved data is correct + if string(got) != string(data) { + t.Errorf("Retrieved data mismatch, got %s, want %s", got, data) + } +} + +func TestCache_Get_Expired(t *testing.T) { + // Create a new cache instance with a short timeout duration + cache := NewCache(1 * time.Millisecond) + + // Add a cache element with a key and data + key := "test" + data := []byte("test data") + cache.Add(key, data) + + // Wait for the cache element to expire + time.Sleep(2 * time.Millisecond) + + // Retrieve the cache element using the Get method + got, ok := cache.Get(key) + + // Check if the cache element is not found + if ok { + t.Errorf("Cache element with key '%s' found, expected not found", key) + } + + // Check if the retrieved data is nil + if got != nil { + t.Errorf("Retrieved data mismatch, got %v, want nil", got) + } +} + +func TestCache_Purge(t *testing.T) { + // Create a new cache instance + cache := NewCache(time.Minute) + + // Add multiple cache elements + cache.Add("key1", []byte("data1")) + cache.Add("key2", []byte("data2")) + + // Purge the cache + cache.Purge() + + // Check if the store is empty + if len(cache.store) != 0 { + t.Errorf("Cache store is not empty after purging") + } +} diff --git a/cli.go b/cli.go index d20b13d..c2c8322 100644 --- a/cli.go +++ b/cli.go @@ -22,8 +22,9 @@ type App struct { out string } serve struct { - renderer string - listener string + renderer string + listener string + cacheTimeout string } } @@ -82,6 +83,7 @@ called 'custom' using the following URL: http://localhost:8080/A.AB+C?renderer=c } serveCmd.PersistentFlags().StringVar(&a.flags.serve.listener, "listener", "127.0.0.1:8080", "listener to be used by the http server") serveCmd.PersistentFlags().StringVar(&a.flags.serve.renderer, "renderer", "serve", "name of the default renderer to be used") + serveCmd.PersistentFlags().StringVar(&a.flags.serve.cacheTimeout, "cache-timeout", "10m", "timeout of the internal cache") rootCmd.AddCommand(serveCmd) // version @@ -147,6 +149,7 @@ func (a *App) serveCmd(cmd *cobra.Command, args []string) { a.flags.configfile, a.flags.base, a.flags.glob, + a.flags.serve.cacheTimeout, ) exitOnErr(err) err = s.Run() diff --git a/server.go b/server.go index 030ef83..4532d8a 100644 --- a/server.go +++ b/server.go @@ -8,6 +8,7 @@ import ( "net/http" "strings" "text/template" + "time" ) //go:embed server/templates/* @@ -23,17 +24,22 @@ type server struct { configfile string base string glob string + cache cache } -func NewServer(listener, defaultRenderer, configfile, base, glob string) (*server, error) { +func NewServer(listener, defaultRenderer, configfile, base, glob, cacheTimeout string) (*server, error) { + durr, err := time.ParseDuration(cacheTimeout) + if err != nil { + return nil, err + } s := &server{ listener: listener, defaultRenderer: defaultRenderer, configfile: configfile, base: base, glob: glob, + cache: *NewCache(durr), } - var err error s.indexTemplate, err = template.ParseFS(templateFS, "server/templates/index.html.tmpl") return s, err } @@ -56,6 +62,11 @@ func (s *server) Run() error { } func (s *server) HandleIndex(w http.ResponseWriter, r *http.Request) { + key := r.URL.RawQuery + if cached, ok := s.cache.Get(key); ok { + _, _ = w.Write(cached) + return + } focusElems := r.URL.Query().Get("focus") focus := strings.Split(focusElems, "_") @@ -113,5 +124,7 @@ func (s *server) HandleIndex(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(err.Error())) return } - _, _ = w.Write(buf.Bytes()) + data := buf.Bytes() + s.cache.Add(key, data) + _, _ = w.Write(data) }