From 310482143b51dfbb65d0cf7e5633efe5c2393d95 Mon Sep 17 00:00:00 2001 From: imago Date: Fri, 27 Sep 2024 22:05:45 +0300 Subject: [PATCH] Setup Cache-Control header, default no-cache Configure it with the new flag -cacheControl --- cmd/serve/handlers.go | 9 ++- cmd/serve/serve.go | 4 ++ cmd/serve/serve_test.go | 136 +++++++++++++++++++++++++++------------- cmd/setup.go | 2 +- 4 files changed, 106 insertions(+), 45 deletions(-) diff --git a/cmd/serve/handlers.go b/cmd/serve/handlers.go index ce9a494..8a76a2e 100644 --- a/cmd/serve/handlers.go +++ b/cmd/serve/handlers.go @@ -8,10 +8,17 @@ import ( func slogHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Perform your middleware action (e.g., logging) ancli.PrintfOK("%s - %s", r.Method, r.URL.Path) // Call the next handler next.ServeHTTP(w, r) }) } + +func cacheHandler(next http.Handler, cacheControl string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Cache-Control", cacheControl) + // Call the next handler + next.ServeHTTP(w, r) + }) +} diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go index 182cabd..08a3746 100644 --- a/cmd/serve/serve.go +++ b/cmd/serve/serve.go @@ -30,6 +30,8 @@ type command struct { forceReload *bool flagset *flag.FlagSet fileserver Fileserver + + cacheControl *string } func Command() *command { @@ -68,6 +70,7 @@ func (c *command) Run(ctx context.Context) error { mux := http.NewServeMux() fsh := http.FileServer(http.Dir(c.mirrorPath)) fsh = slogHandler(fsh) + fsh = cacheHandler(fsh, *c.cacheControl) mux.Handle("/", fsh) ancli.PrintfOK("setting up websocket host on path: '%v'", *c.wsPath) @@ -123,6 +126,7 @@ func (c *command) Flagset() *flag.FlagSet { c.port = fs.Int("port", 8080, "port to serve http server on") c.wsPath = fs.String("wsPort", "/delta-streamer-ws", "the path which the delta streamer websocket should be hosted on") c.forceReload = fs.Bool("forceReload", false, "set to true if you wish to reload all attached browser pages on any file change") + c.cacheControl = fs.String("cacheControl", "no-cache", "set to configure the cache-control header") c.flagset = fs return fs } diff --git a/cmd/serve/serve_test.go b/cmd/serve/serve_test.go index ab4be52..f71cf5e 100644 --- a/cmd/serve/serve_test.go +++ b/cmd/serve/serve_test.go @@ -2,12 +2,13 @@ package serve import ( "context" + "fmt" "net/http" "net/http/httptest" "testing" "time" - "github.com/baalimago/go_away_boilerplate/pkg/ancli" + "github.com/baalimago/go_away_boilerplate/pkg/testboil" "golang.org/x/net/websocket" ) @@ -48,6 +49,25 @@ func Test_Setup(t *testing.T) { t.Fatalf("expected: %v, got: %v", want, got) } }) + + t.Run("it should set cacheControl arg", func(t *testing.T) { + want := "test" + c := command{} + givenArgs := []string{"-cacheControl", want} + err := c.Flagset().Parse(givenArgs) + if err != nil { + t.Fatalf("failed to parse flagset: %v", err) + } + err = c.Setup() + if err != nil { + t.Fatalf("failed to setup: %v", err) + } + + got := *c.cacheControl + if got != want { + t.Fatalf("expected: %v, got: %v", want, got) + } + }) } type mockFileServer struct{} @@ -64,56 +84,86 @@ func (m *mockFileServer) Start(ctx context.Context) error { func (m *mockFileServer) WsHandler(ws *websocket.Conn) {} func TestRun(t *testing.T) { - ancli.Newline = true - cmd := command{} - cmd.fileserver = &mockFileServer{} - fs := cmd.Flagset() - fs.Parse([]string{"--port=8081", "--wsPort=/test-ws"}) - - err := cmd.Setup() - if err != nil { - t.Fatalf("Setup failed: %v", err) + setup := func() command { + cmd := command{} + cmd.fileserver = &mockFileServer{} + fs := cmd.Flagset() + fs.Parse([]string{"--port=8081", "--wsPort=/test-ws"}) + + err := cmd.Setup() + if err != nil { + t.Fatalf("Setup failed: %v", err) + } + return cmd } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + t.Run("it should setup websocket handler on wsPort", func(t *testing.T) { + cmd := setup() + ctx, ctxCancel := context.WithCancel(context.Background()) + + ready := make(chan struct{}) + go func() { + close(ready) + err := cmd.Run(ctx) + if err != nil { + t.Errorf("Run returned error: %v", err) + } + }() - // Run the server in a separate goroutine - go func() { - err := cmd.Run(ctx) + t.Cleanup(ctxCancel) + + <-ready + // Test if the HTTP server is working + resp, err := http.Get("http://localhost:8081/") if err != nil { - t.Errorf("Run returned error: %v", err) + t.Fatalf("Failed to send GET request: %v", err) } - }() + t.Cleanup(func() { resp.Body.Close() }) - time.Sleep(time.Second) + if resp.StatusCode != http.StatusOK { + t.Fatalf("Expected status OK, got: %v", resp.Status) + } - // Test if the HTTP server is working - resp, err := http.Get("http://localhost:8081/") - if err != nil { - t.Fatalf("Failed to send GET request: %v", err) - } - defer resp.Body.Close() + // Test the websocket handler + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s := websocket.Server{Handler: cmd.fileserver.WsHandler} + s.ServeHTTP(w, r) + })) - if resp.StatusCode != http.StatusOK { - t.Fatalf("Expected status OK, got: %v", resp.Status) - } + t.Cleanup(func() { server.Close() }) - // Test the websocket handler - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - s := websocket.Server{Handler: cmd.fileserver.WsHandler} - s.ServeHTTP(w, r) - })) - defer server.Close() - - wsURL := "ws" + server.URL[len("http"):] - ws, err := websocket.Dial(wsURL+"/test-ws", "", "http://localhost/") - if err != nil { - t.Fatalf("websocket dial failed: %v", err) - } - defer ws.Close() + wsURL := "ws" + server.URL[len("http"):] + ws, err := websocket.Dial(wsURL+"/test-ws", "", "http://localhost/") + if err != nil { + t.Fatalf("websocket dial failed: %v", err) + } + t.Cleanup(func() { ws.Close() }) + }) - // Cleanup - cancel() - time.Sleep(time.Second) + t.Run("it should respond with correct cache control", func(t *testing.T) { + cmd := setup() + ctx, ctxCancel := context.WithCancel(context.Background()) + t.Cleanup(ctxCancel) + want := "test" + port := 13337 + cmd.cacheControl = &want + cmd.port = &port + + ready := make(chan struct{}) + go func() { + close(ready) + err := cmd.Run(ctx) + if err != nil { + t.Errorf("Run returned error: %v", err) + } + }() + <-ready + time.Sleep(time.Millisecond) + resp, err := http.Get(fmt.Sprintf("http://localhost:%v", port)) + if err != nil { + t.Fatal(err) + } + got := resp.Header.Get("Cache-Control") + testboil.FailTestIfDiff(t, got, want) + }) } diff --git a/cmd/setup.go b/cmd/setup.go index f839c0b..9088b38 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -63,7 +63,7 @@ const usage = `== Web Development 41 == This tool is designed to enable live reload for statically hosted web development. It injects a websocket script in a mirrored version of html pages and uses the fsnotify (cross-platform 'inotify' wrapper) package to detect filechanges. -On filechanges, the websocket will trigger a reload of the page. +On filechanges, the websocket will trigger a reload of the page. The 41 (formerly "40", before I got spooked by potential lawyers) is only to enable rust-repellant properties.