Skip to content

Commit 32f438e

Browse files
joeybloggsjoeybloggs
authored andcommitted
add websocket chat example
1 parent 5318d07 commit 32f438e

File tree

1 file changed

+317
-0
lines changed

1 file changed

+317
-0
lines changed

examples/websockets/main.go

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
// Copyright 2016 Dean Karn.
2+
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package main
7+
8+
import (
9+
"bytes"
10+
"html/template"
11+
"log"
12+
"net/http"
13+
"time"
14+
15+
"github.com/go-playground/lars"
16+
"github.com/go-playground/lars/examples/middleware/logging-recovery"
17+
"github.com/gorilla/websocket"
18+
)
19+
20+
func main() {
21+
22+
go hub.run()
23+
24+
l := lars.New()
25+
l.Use(middleware.LoggingAndRecovery)
26+
27+
l.Get("/", homeHandler)
28+
l.WebSocket(upgrader, "/ws", ws)
29+
30+
err := http.ListenAndServe(":4444", l.Serve())
31+
if err != nil {
32+
log.Fatal(err)
33+
}
34+
}
35+
36+
func ws(c lars.Context) {
37+
38+
client := &Client{hub: hub, conn: c.WebSocket(), send: make(chan []byte, 256)}
39+
client.hub.register <- client
40+
41+
go client.writePump()
42+
client.readPump()
43+
}
44+
45+
func homeHandler(c lars.Context) {
46+
47+
t, err := template.New("home").Parse(tmpl)
48+
if err != nil {
49+
log.Println("Unable to parse upgrade html")
50+
http.Error(c.Response(), http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
51+
return
52+
}
53+
54+
err = t.Execute(c.Response(), c.Request().Host)
55+
if err != nil {
56+
log.Println("Unable to Execute Upgrade Template")
57+
http.Error(c.Response(), http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
58+
return
59+
}
60+
}
61+
62+
// normally the below would be split into multiple files, but for the sake of an easily runnable
63+
// example is all in one file.
64+
65+
const (
66+
// Time allowed to write a message to the peer.
67+
writeWait = 10 * time.Second
68+
69+
// Time allowed to read the next pong message from the peer.
70+
pongWait = 60 * time.Second
71+
72+
// Send pings to peer with this period. Must be less than pongWait.
73+
pingPeriod = (pongWait * 9) / 10
74+
75+
// Maximum message size allowed from peer.
76+
maxMessageSize = 512
77+
)
78+
79+
var (
80+
newline = []byte{'\n'}
81+
space = []byte{' '}
82+
upgrader = websocket.Upgrader{
83+
ReadBufferSize: 1024,
84+
WriteBufferSize: 1024,
85+
CheckOrigin: func(r *http.Request) bool {
86+
o := r.Header.Get(lars.Origin)
87+
return o == "http://localhost:4444"
88+
},
89+
}
90+
tmpl = `<!DOCTYPE html>
91+
<html lang="en">
92+
<head>
93+
<title>Chat Example</title>
94+
<script type="text/javascript">
95+
window.onload = function () {
96+
var conn;
97+
var msg = document.getElementById("msg");
98+
var log = document.getElementById("log");
99+
100+
function appendLog(item) {
101+
var doScroll = log.scrollTop === log.scrollHeight - log.clientHeight;
102+
log.appendChild(item);
103+
if (doScroll) {
104+
log.scrollTop = log.scrollHeight - log.clientHeight;
105+
}
106+
}
107+
108+
document.getElementById("form").onsubmit = function () {
109+
if (!conn) {
110+
return false;
111+
}
112+
if (!msg.value) {
113+
return false;
114+
}
115+
conn.send(msg.value);
116+
msg.value = "";
117+
return false;
118+
};
119+
120+
if (window["WebSocket"]) {
121+
conn = new WebSocket("ws://{{$}}/ws");
122+
conn.onclose = function (evt) {
123+
var item = document.createElement("div");
124+
item.innerHTML = "<b>Connection closed.</b>";
125+
appendLog(item);
126+
};
127+
conn.onmessage = function (evt) {
128+
var messages = evt.data.split('\n');
129+
for (var i = 0; i < messages.length; i++) {
130+
var item = document.createElement("div");
131+
item.innerText = messages[i];
132+
appendLog(item);
133+
}
134+
};
135+
} else {
136+
var item = document.createElement("div");
137+
item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
138+
appendLog(item);
139+
}
140+
};
141+
</script>
142+
<style type="text/css">
143+
html {
144+
overflow: hidden;
145+
}
146+
147+
body {
148+
overflow: hidden;
149+
padding: 0;
150+
margin: 0;
151+
width: 100%;
152+
height: 100%;
153+
background: gray;
154+
}
155+
156+
#log {
157+
background: white;
158+
margin: 0;
159+
padding: 0.5em 0.5em 0.5em 0.5em;
160+
position: absolute;
161+
top: 0.5em;
162+
left: 0.5em;
163+
right: 0.5em;
164+
bottom: 3em;
165+
overflow: auto;
166+
}
167+
168+
#form {
169+
padding: 0 0.5em 0 0.5em;
170+
margin: 0;
171+
position: absolute;
172+
bottom: 1em;
173+
left: 0px;
174+
width: 100%;
175+
overflow: hidden;
176+
}
177+
178+
</style>
179+
</head>
180+
<body>
181+
<div id="log"></div>
182+
<form id="form">
183+
<input type="submit" value="Send" />
184+
<input type="text" id="msg" size="64"/>
185+
</form>
186+
</body>
187+
</html>`
188+
hub = newHub()
189+
)
190+
191+
// Client is an middleman between the websocket connection and the hub.
192+
type Client struct {
193+
hub *Hub
194+
195+
// The websocket connection.
196+
conn *websocket.Conn
197+
198+
// Buffered channel of outbound messages.
199+
send chan []byte
200+
}
201+
202+
// readPump pumps messages from the websocket connection to the hub.
203+
func (c *Client) readPump() {
204+
defer func() {
205+
c.hub.unregister <- c
206+
c.conn.Close()
207+
}()
208+
c.conn.SetReadLimit(maxMessageSize)
209+
c.conn.SetReadDeadline(time.Now().Add(pongWait))
210+
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
211+
for {
212+
_, message, err := c.conn.ReadMessage()
213+
if err != nil {
214+
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
215+
log.Printf("error: %v", err)
216+
}
217+
break
218+
}
219+
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
220+
c.hub.broadcast <- message
221+
}
222+
}
223+
224+
// write writes a message with the given message type and payload.
225+
func (c *Client) write(mt int, payload []byte) error {
226+
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
227+
return c.conn.WriteMessage(mt, payload)
228+
}
229+
230+
// writePump pumps messages from the hub to the websocket connection.
231+
func (c *Client) writePump() {
232+
ticker := time.NewTicker(pingPeriod)
233+
defer func() {
234+
ticker.Stop()
235+
c.conn.Close()
236+
}()
237+
for {
238+
select {
239+
case message, ok := <-c.send:
240+
if !ok {
241+
// The hub closed the channel.
242+
c.write(websocket.CloseMessage, []byte{})
243+
return
244+
}
245+
246+
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
247+
w, err := c.conn.NextWriter(websocket.TextMessage)
248+
if err != nil {
249+
return
250+
}
251+
w.Write(message)
252+
253+
// Add queued chat messages to the current websocket message.
254+
n := len(c.send)
255+
for i := 0; i < n; i++ {
256+
w.Write(newline)
257+
w.Write(<-c.send)
258+
}
259+
260+
if err := w.Close(); err != nil {
261+
return
262+
}
263+
case <-ticker.C:
264+
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
265+
return
266+
}
267+
}
268+
}
269+
}
270+
271+
// hub maintains the set of active clients and broadcasts messages to the
272+
// clients.
273+
type Hub struct {
274+
// Registered clients.
275+
clients map[*Client]bool
276+
277+
// Inbound messages from the clients.
278+
broadcast chan []byte
279+
280+
// Register requests from the clients.
281+
register chan *Client
282+
283+
// Unregister requests from clients.
284+
unregister chan *Client
285+
}
286+
287+
func newHub() *Hub {
288+
return &Hub{
289+
broadcast: make(chan []byte),
290+
register: make(chan *Client),
291+
unregister: make(chan *Client),
292+
clients: make(map[*Client]bool),
293+
}
294+
}
295+
296+
func (h *Hub) run() {
297+
for {
298+
select {
299+
case client := <-h.register:
300+
h.clients[client] = true
301+
case client := <-h.unregister:
302+
if _, ok := h.clients[client]; ok {
303+
delete(h.clients, client)
304+
close(client.send)
305+
}
306+
case message := <-h.broadcast:
307+
for client := range h.clients {
308+
select {
309+
case client.send <- message:
310+
default:
311+
close(client.send)
312+
delete(h.clients, client)
313+
}
314+
}
315+
}
316+
}
317+
}

0 commit comments

Comments
 (0)