WebSocket provides a persistent TCP connection through which clients and servers exchange messages bidirectionally. It reduces the repeated identity, query, and timing overhead of polling when applications need immediate updates. section 7.4.1
Use WebSocket when the application needs:
- Real-time responses rather than updates delayed by timed polling. section 7.4.1
- Messages distributed from one connection to multiple clients as they arrive. section 7.4.1
- Long-lived, multiplexed communication instead of independent HTTP requests. section 7.5
Real-time chat
A Go chat service wraps an HTTP handler with websocket.Handler, which manages the handshake and upgrades the HTTP connection. Connected clients can exchange messages, while the service distributes incoming messages and updates the active-member list in real time. section 7.4.1
Listing 7.9 A chat application using websockets
package main
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
"golang.org/x/net/websocket"
)
var chars = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
var clients map[string]*websocket.Conn #1
func init() {
clients = make(map[string]*websocket.Conn)
}
func generateId() string { #2
r := make([]rune, 16)
for i := range r {
r[i] = chars[rand.Intn(len(chars))]
}
return string(r)
}
func chatHandler(w http.ResponseWriter,
r *http.Request) { #3
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head>
<title>Let's Chat</title>
<style>
#chat {
max-width: 400px;
margin: auto;
font-family: system-ui, sans-serif;
}
.message {
padding: 1rem 0.25rem;
border: 1px solid black;
margin-bottom: 0.5rem;
}
</style>
</head>
<body>
<div id="chat">
<h1>Chat</h1>
<div id="messages"></div>
<input id="message" autofocus type="text"
placeholder="Enter message ..." />
<div>
<p>Chat members:</p>
<ul id="chat-members"></ul>
</div>
</div>
<script>
const text = document.getElementById('message');
const messages = document.getElementById('messages');
const members = document.getElementById('chat-members');
const ws = new WebSocket('ws://localhost:8081/ws');
ws.onmessage = e => {
const msg = JSON.parse(e.data);
if (msg.message_type == 'joinleave') {
members.innerHTML = '';
msg.chat_members.forEach(member => {
const li = document.createElement('li');
li.innerHTML = member;
members.appendChild(li);
});
return;
}
if (msg.message_type === 'message') {
const message = document.createElement('div');
message.classList.add('message');
message.innerHTML = msg.sender_id + " said: " +
msg.message;
messages.appendChild(message);
return;
}
}
document.getElementById('message').addEventListener('keyup',
e => {
if (e.key == 'Enter') {
ws.send(e.target.value);
message.value = '';
}
});
</script>
</body>
</html>
`)
}
type servermsg struct {
MessageType string `json:"message_type"`
Message string `json:"message,omitempty"`
Id string `json:"id,omitempty"`
SenderId string `json:"sender_id,omitempty"`
ChatMembers []string `json:"chat_members"`
}
func compileChatMembers() []string { #4
var chatMembers []string
for k, _ := range clients {
chatMembers = append(chatMembers, k)
}
return chatMembers
}
func sendToClients(msg servermsg) error { #5
msgJSON, err := json.Marshal(msg)
if err != nil {
return err
}
for k := range clients {
if err := websocket.Message.Send(clients[k], string(msgJSON));
err != nil {
return err
}
}
return nil
}
func disconnectClient(id string) error { #6
delete(clients, id)
if err := sendToClients(servermsg{
MessageType: "joinleave",
Message: "",
Id: id,
SenderId: "",
ChatMembers: compileChatMembers(),
}); err != nil {
return err
}
return nil
}
func ws(ws *websocket.Conn) { #7
id := generateId()
clients[id] = ws
join := servermsg{
MessageType: "joinleave",
Message: "",
Id: id,
SenderId: "",
ChatMembers: compileChatMembers(),
}
sendToClients(join)
for { #8
var incoming string
if err := websocket.Message.Receive(ws,
&incoming); err != nil { #9
if err := disconnectClient(id); err != nil {
log.Println(err)
}
break
}
if err := sendToClients(servermsg{ #10
MessageType: "message",
Message: incoming,
Id: "",
SenderId: id,
ChatMembers: compileChatMembers(),
}); err != nil {
if err := disconnectClient(id); err != nil {
log.Println(err)
}
break
}
}
}
func main() {
http.HandleFunc("/chat", chatHandler)
http.Handle("/ws", websocket.Handler(ws)) #11
if err := http.ListenAndServe(":8081", nil); err != nil {
panic(err)
}
}
Inspect a complete Go chat application using x/net/websocket, including client tracking, connection handling, and the browser interface. section 7.4.1

