diff --git a/middles/cache.go b/middles/cache.go new file mode 100644 index 0000000..7680b10 --- /dev/null +++ b/middles/cache.go @@ -0,0 +1,36 @@ +package middles + +import ( + "net/http" + "strings" + "time" + + "cattlecloud.net/go/webtools" + "cattlecloud.net/go/webtools/runtime" + "github.com/shoenig/lang" +) + +type Cache struct { + Env runtime.Environment + H http.Handler +} + +func (c *Cache) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var ttl time.Duration + + switch c.Env.String() { + case runtime.Local: + ttl = 5 * time.Second + + case runtime.Staging: + css := strings.HasSuffix(r.URL.Path, ".css") + ttl = lang.Maybe(css, 1*time.Minute, 1*time.Hour) + + case runtime.Production: + css := strings.HasSuffix(r.URL.Path, ".css") + ttl = lang.Maybe(css, 3*time.Hour, 24*time.Hour) + } + + webtools.SetCacheControl(w, ttl) + c.H.ServeHTTP(w, r) +} diff --git a/middles/cache_test.go b/middles/cache_test.go new file mode 100644 index 0000000..2f2cc94 --- /dev/null +++ b/middles/cache_test.go @@ -0,0 +1,126 @@ +package middles + +import ( + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + + "cattlecloud.net/go/webtools/runtime" + "github.com/shoenig/test/must" +) + +func TestCache_ServeHTTP_local(t *testing.T) { + t.Parallel() + + run := new(atomic.Bool) + c := &Cache{ + Env: runtime.Setup(&runtime.Config{ + Environment: runtime.Local, + Domain: "example.com", + }), + H: http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + run.Store(true) + }), + } + + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/abc.css", nil) + c.ServeHTTP(w, r) + + must.Eq(t, 200, w.Code) + must.True(t, run.Load()) + must.Eq(t, "private, max-age=5", w.Header().Get("Cache-Control")) +} + +func TestCache_ServeHTTP_staging_css(t *testing.T) { + t.Parallel() + + run := new(atomic.Bool) + c := &Cache{ + Env: runtime.Setup(&runtime.Config{ + Environment: runtime.Staging, + Domain: "example.com", + }), + H: http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + run.Store(true) + }), + } + + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/abc.css", nil) + c.ServeHTTP(w, r) + + must.Eq(t, 200, w.Code) + must.True(t, run.Load()) + must.Eq(t, "private, max-age=60", w.Header().Get("Cache-Control")) +} + +func TestCache_ServeHTTP_staging_txt(t *testing.T) { + t.Parallel() + + run := new(atomic.Bool) + c := &Cache{ + Env: runtime.Setup(&runtime.Config{ + Environment: runtime.Staging, + Domain: "example.com", + }), + H: http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + run.Store(true) + }), + } + + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/abc.txt", nil) + c.ServeHTTP(w, r) + + must.Eq(t, 200, w.Code) + must.True(t, run.Load()) + must.Eq(t, "private, max-age=3600", w.Header().Get("Cache-Control")) +} + +func TestCache_ServeHTTP_production_css(t *testing.T) { + t.Parallel() + + run := new(atomic.Bool) + c := &Cache{ + Env: runtime.Setup(&runtime.Config{ + Environment: runtime.Production, + Domain: "example.com", + }), + H: http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + run.Store(true) + }), + } + + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/abc.css", nil) + c.ServeHTTP(w, r) + + must.Eq(t, 200, w.Code) + must.True(t, run.Load()) + must.Eq(t, "private, max-age=10800", w.Header().Get("Cache-Control")) +} + +func TestCache_ServeHTTP_production_txt(t *testing.T) { + t.Parallel() + + run := new(atomic.Bool) + c := &Cache{ + Env: runtime.Setup(&runtime.Config{ + Environment: runtime.Production, + Domain: "example.com", + }), + H: http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + run.Store(true) + }), + } + + w := httptest.NewRecorder() + r, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/abc.txt", nil) + c.ServeHTTP(w, r) + + must.Eq(t, 200, w.Code) + must.True(t, run.Load()) + must.Eq(t, "private, max-age=86400", w.Header().Get("Cache-Control")) +}