liveBook · Manning

WebSocket

As explained in Go in Practice, Second Edition

WebSocket is a protocol that provides persistent, bidirectional communication between a client and server over a single connection.

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

How Go in Practice, Second Edition covers it

How deep it goes
Explains persistent TCP connections, HTTP upgrade handling, and a working chat implementation, while deferring advanced package features.
Examples
Demonstrates a multi-client chat application with real-time messages and active-member updates.
What it assumes
Builds on HTTP server handling and TCP connection concepts while contrasting WebSocket with REST and polling.
What you can do after
Build it from scratch as a small Go chat service with upgraded HTTP connections and multiplexed client messaging.
How it connects
Connects WebSocket with HTTP servers, TCP, REST, polling, concurrency, and server-sent events.

Also explained in

Compare how 2 books explain WebSocket →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go in Practice, Second Edition. The section links point to the parts of the book it draws on.