forked from cortesi/devd
/
livereload.go
138 lines (124 loc) · 3.19 KB
/
livereload.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Package livereload allows HTML pages to be dynamically reloaded. It includes
// both the server and client implementations required.
package livereload
import (
"net/http"
"regexp"
"strings"
"sync"
"github.com/GeertJohan/go.rice"
"github.com/milanaleksic/devd/inject"
"github.com/cortesi/termlog"
"github.com/gorilla/websocket"
)
// Reloader triggers a reload
type Reloader interface {
Reload(paths []string)
Watch(ch chan []string)
}
const (
cmdPage = "page"
cmdCSS = "css"
// EndpointPath is the path to the websocket endpoint
EndpointPath = "/.devd.livereload"
// ScriptPath is the path to the livereload JavaScript asset
ScriptPath = "/.devd.livereload.js"
)
// Injector for the livereload script
var Injector = inject.CopyInject{
Within: 1024 * 30,
ContentType: "text/html",
Marker: regexp.MustCompile(`<\/head>`),
Payload: []byte(`<script src="/.devd.livereload.js"></script>`),
}
// Server implements a Livereload server
type Server struct {
sync.Mutex
broadcast chan<- string
logger termlog.Logger
name string
connections map[*websocket.Conn]bool
}
// NewServer createss a Server instance
func NewServer(name string, logger termlog.Logger) *Server {
broadcast := make(chan string, 50)
s := &Server{
name: name,
broadcast: broadcast,
connections: make(map[*websocket.Conn]bool),
logger: logger,
}
go s.run(broadcast)
return s
}
func (s *Server) run(broadcast <-chan string) {
for m := range broadcast {
s.Lock()
for conn := range s.connections {
if conn == nil {
continue
}
err := conn.WriteMessage(websocket.TextMessage, []byte(m))
if err != nil {
s.logger.Say("Error: %s", err)
delete(s.connections, conn)
}
}
s.Unlock()
}
s.Lock()
defer s.Unlock()
for conn := range s.connections {
delete(s.connections, conn)
conn.Close()
}
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", 405)
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
s.logger.Say("Error: %s", err)
http.Error(w, "Can't upgrade.", 500)
return
}
s.Lock()
s.connections[conn] = true
s.Unlock()
}
// Reload signals to connected clients that a given resource should be
// reloaded.
func (s *Server) Reload(paths []string) {
cmd := cmdCSS
for _, path := range paths {
if !strings.HasSuffix(path, ".css") {
cmd = cmdPage
}
}
s.logger.SayAs("debug", "livereload %s, files changed: %s", cmd, paths)
s.broadcast <- cmd
}
// Watch montors a channel of lists of paths for reload requests
func (s *Server) Watch(ch chan []string) {
for ei := range ch {
if len(ei) > 0 {
s.Reload(ei)
}
}
}
// ServeScript is a handler function that serves the livereload JavaScript file
func (s *Server) ServeScript(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", "application/javascript")
clientBox := rice.MustFindBox("static")
_, err := rw.Write(clientBox.MustBytes("client.js"))
if err != nil {
s.logger.Warn("Error serving livereload script: %s", err)
}
}