-
Notifications
You must be signed in to change notification settings - Fork 0
/
websocket.go
executable file
·174 lines (155 loc) · 3.78 KB
/
websocket.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package main
import (
"encoding/json"
"html/template"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
)
const (
writeWait = 10 * time.Second
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
maxMessageSize = 512
)
var (
upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
)
// StatusUpdate is the payload sent the to frontend for status update
type StatusUpdate struct {
ID string `json:"id"`
Status string `json:"status"`
lastUpdateMillis int `json:"lastUpdateMillis"`
}
type tmpl struct {
routePath string
template *template.Template
data interface{}
}
// Display keeps track of the client to send updates
type Display struct {
clients map[*client]bool
tmpls []tmpl
}
// NewDisplay returns a new display
func NewDisplay(name string) *Display {
d := &Display{
clients: map[*client]bool{},
tmpls: []tmpl{},
}
d.tmpls = append(d.tmpls, tmpl{
routePath: "/components/container-header/container-header.html",
template: template.Must(template.ParseFiles("./public/components/container-header/container-header.html")),
data: name,
})
return d
}
// RouteStatic routes the static webpage ... must be routed last
func (d *Display) RouteStatic(router *mux.Router) {
d.addTemplatePages(router)
routeStaticWebApp(router, "./public")
}
// AddClient adds a client to be sent updates
func (d *Display) AddClient(c *client) {
d.clients[c] = true
}
// RemoveClient removes a client
func (d *Display) RemoveClient(c *client) {
for client := range d.clients {
if client == c {
delete(d.clients, client)
c.close()
}
}
}
// Send sends status update to all connected clients
func (d *Display) Send(su StatusUpdate) error {
payload, err := json.Marshal(su)
if err != nil {
return err
}
for c := range d.clients {
go func(c *client) {
c.send <- payload
}(c)
}
return nil
}
//LiveStatus is the handler for the websocket endpoint
func (d *Display) LiveStatus() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Debug("ws client connection opened")
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
logger.Error("Could not upgrade to ws", "error", err)
return
}
client := &client{
conn: conn,
send: make(chan []byte),
heartbeat: 1 * time.Second,
}
d.AddClient(client)
go client.process(d)
})
}
func (d *Display) addTemplatePages(r *mux.Router) {
for _, tmpl := range d.tmpls {
r.HandleFunc(tmpl.routePath, func(w http.ResponseWriter, r *http.Request) {
tmpl.template.Execute(w, tmpl.data)
})
}
}
func routeStaticWebApp(r *mux.Router, dir string) {
public := http.FileServer(http.Dir(dir))
r.PathPrefix("/").Handler(public)
}
// client handles the actual connection to the client/webpage
type client struct {
conn *websocket.Conn
send chan []byte
heartbeat time.Duration
}
// close closes connection and channel
func (c *client) close() {
close(c.send)
c.conn.Close()
}
// process sends messages from channel to the websocket connection
func (c *client) process(d *Display) {
defer func() {
logger.Debug("ws client connection closed")
d.RemoveClient(c)
}()
ticker := time.NewTicker(c.heartbeat)
for {
select {
case <-ticker.C:
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
case payload := <-c.send:
err := writeToConnection(c.conn, payload)
if err != nil {
logger.Error("Could not make ws writer", "error", err)
continue
}
}
}
}
func writeToConnection(conn *websocket.Conn, payload []byte) error {
w, err := conn.NextWriter(websocket.TextMessage)
if err != nil {
return err
}
w.Write(payload)
w.Close()
return nil
}