Skip to content

Commit

Permalink
Added flag -cacheControl which sets Cache-Control header (#6)
Browse files Browse the repository at this point in the history
Defaults to no-cache, which effectively tells browser to reload browser on every file refresh
  • Loading branch information
baalimago authored Oct 3, 2024
2 parents bab05c1 + 3104821 commit 2ae794e
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 45 deletions.
9 changes: 8 additions & 1 deletion cmd/serve/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
4 changes: 4 additions & 0 deletions cmd/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type command struct {
forceReload *bool
flagset *flag.FlagSet
fileserver Fileserver

cacheControl *string
}

func Command() *command {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
136 changes: 93 additions & 43 deletions cmd/serve/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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{}
Expand All @@ -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)
})
}
2 changes: 1 addition & 1 deletion cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 2ae794e

Please sign in to comment.