-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreload.go
101 lines (87 loc) · 2.23 KB
/
reload.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Package reload provides an HTTP handler that implements simple live
// reloading for Go web servers.
//
// To use, register this handler in your HTTP server, then load it via
// a <script> tag in your HTML. The page will be reloaded whenever the
// server restarts, and when explicitly requested by invoking the
// handler's Reload function.
package reload
import (
_ "embed"
"fmt"
"net/http"
"strconv"
"sync"
"time"
"github.com/gorilla/websocket"
)
// Reloader is a live-reloading HTTP handler.
type Reloader struct {
mu sync.Mutex
cookie []byte
refresh chan struct{}
}
//go:embed reload.js
var js []byte
// ServeHTTP serves the live reloading script and websocket handler.
func (rl *Reloader) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if websocket.IsWebSocketUpgrade(r) {
rl.socket(w, r)
return
}
w.Header().Set("Content-Type", "text/javascript")
w.Header().Set("Cache-Control", "no-store")
cookie, _ := rl.getState()
fmt.Fprintf(w, "(%s)(%q)", js, cookie)
}
// Reload triggers an immediate reload of all clients.
func (rl *Reloader) Reload() {
rl.mu.Lock()
defer rl.mu.Unlock()
rl.reloadLocked()
}
func (rl *Reloader) socket(w http.ResponseWriter, r *http.Request) {
upgrader := websocket.Upgrader{
HandshakeTimeout: 5 * time.Second,
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
// upgrader has already responded to client
return
}
defer conn.Close()
cookie, refresh := rl.getState()
for {
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
if err := conn.WriteMessage(websocket.TextMessage, cookie); err != nil {
return
}
cookie = nil
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
if _, _, err := conn.ReadMessage(); err != nil {
return
}
select {
case <-time.After(5 * time.Second):
case <-refresh:
cookie, refresh = rl.getState()
case <-r.Context().Done():
return
}
}
}
func (rl *Reloader) getState() (cookie []byte, refresh <-chan struct{}) {
rl.mu.Lock()
defer rl.mu.Unlock()
if rl.cookie == nil {
rl.reloadLocked()
}
return rl.cookie, rl.refresh
}
func (rl *Reloader) reloadLocked() {
if rl.refresh != nil {
close(rl.refresh)
}
rl.cookie = []byte(strconv.FormatInt(time.Now().UnixNano(), 36))
rl.refresh = make(chan struct{})
}