Gorilla WebSocket

repository·main·Indexed 12 days ago

https://github.com/gorilla/websocket

A complete and tested implementation of the WebSocket protocol (RFC 6455) for the Go programming language. It provides tools for establishing connections via Dialer, managing bidirectional communication, and implementing patterns like hub-and-spoke broadcasting. Includes support for per-message compression (RFC 7692) and a suite of examples including chat servers, file watchers, and command bridges.

Tokens
9.1K
Snippets
32
Records
48
Agent score
97%

What's inside Gorilla WebSocket

  1. Implement a Hub for Message Broadcasting

    main

    A Hub manages the lifecycle of clients and the distribution of messages.

    Key Responsibilities:

    • Registration: Adds client pointers to a clients map.
    • Unregistration: Removes clients from the map and closes the client's send channel to signal that no more messages will be sent.
    • Broadcasting: Iterates over registered clients and sends messages to their respective send channels.

    Error Handling: If a client's send buffer is full during a broadcast, the hub assumes the client is dead or stuck, unregisters them, and closes the WebSocket connection.

  2. How the Chat Server Architecture Works

    main

    The chat application implements a hub-and-spoke model to manage multiple WebSocket connections:

    • Hub: Acts as a central coordinator. It maintains a set of registered Client instances and broadcasts messages to all of them. It uses channels for register, unregister, and broadcast operations.
    • Client: Acts as an intermediary between a single WebSocket connection and the Hub. Each client runs two dedicated goroutines to satisfy WebSocket concurrency requirements:
      • readPump: Reads inbound messages from the WebSocket and sends them to the Hub.
      • writePump: Reads messages from the client's outbound channel and writes them to the WebSocket. To improve efficiency under high load, this method coalesces pending messages into a single WebSocket message to reduce system calls.

    Concurrency Note: WebSocket connections support exactly one concurrent reader and one concurrent writer. This architecture ensures compliance by isolating all reads to the readPump goroutine and all writes to the writePump goroutine.

  3. Explore Gorilla WebSocket examples

    main

    The repository provides several practical examples to help you understand different usage patterns:

    • Chat example: Implementing a real-time chat application.
    • Command example: Handling specific commands over a WebSocket connection.
    • Client and server example: A basic echo implementation demonstrating bidirectional communication.
    • File watch example: Using WebSockets to broadcast file system changes.
    • Autobahn Test Suite: An implementation in examples/autobahn used to verify protocol compliance against the Autobahn Test Suite.
  4. Run the Autobahn WebSocket Test Suite

    main

    The examples/autobahn directory provides a server implementation designed to be tested against the Autobahn WebSockets Test Suite.

    To perform a test run:

    1. Start the local test server using go run server.go.
    2. Run the Autobahn client test driver via Docker. The driver requires mounting a configuration directory and a reports directory to capture the results.
    3. Once the client finishes, the test report is generated as an HTML file in the reports/ directory.
    # 1. Start the server
    go run server.go
    
    # 2. Run the client test driver (in a separate terminal)
    mkdir -p reports
    docker run -it --rm \
        -v ${PWD}/config:/config \
        -v ${PWD}/reports:/reports \
        crossbario/autobahn-testsuite \
        wstest -m fuzzingclient -s /config/fuzzingclient.json
  5. Implement a Client for WebSocket Connections

    main

    A Client manages the connection between the network and the application logic.

    Lifecycle and Setup:

    1. Upgrade: Use an HTTP handler (like serveWs) to upgrade the HTTP connection to the WebSocket protocol.
    2. Registration: Create the Client instance and register it with the Hub.
    3. Concurrency Setup:
      • Start the writePump goroutine to handle outbound messages (from the send channel to the WebSocket).
      • Execute the readPump in the main handler goroutine to handle inbound messages (from the WebSocket to the Hub).
    4. Cleanup: Use a defer statement to ensure the client is unregistered when the connection terminates.
  6. Run the Chat Example

    main

    To run the chat example, ensure you have a working Go development environment installed. You can download, build, and run the example using the following commands:

    1. Get the websocket package.
    2. Navigate to the example directory.
    3. Run the application.

    Once running, access the chat interface at http://localhost:8080/ in your browser.

    $ go get github.com/gorilla/websocket
    $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat`
    $ go run *.go
  7. Handle Control Messages (Ping, Pong, and Close)

    main

    WebSocket connections use control messages for connection management:

    • Close: Handled by the function set via SetCloseHandler. By default, the connection sends a close message to the peer. ReadMessage or NextReader will return a *CloseError when a close is received.
    • Ping: Handled by the function set via SetPingHandler. The default handler automatically sends a pong message back.
    • Pong: Handled by the function set via SetPongHandler. The default handler does nothing. If your application sends pings, you should set a pong handler to respond to them.

    Note: You must actively read from the connection (e.g., via a loop calling NextReader or ReadMessage) to process these control messages. If you don't need the data, start a goroutine to read and discard messages.

    // Example: A goroutine to read and discard messages to keep control messages flowing
    func readLoop(c *websocket.Conn) {
        for {
            if _, _, err := c.NextReader(); err != nil {
                c.Close()
                break
            }
        }
    }
  8. Understand WebSocket message types

    main

    The WebSocket protocol distinguishes between two primary data message types:

    • websocket.TextMessage: Interpreted as UTF-8 encoded text. The application is responsible for ensuring text messages are valid UTF-8.
    • websocket.BinaryMessage: Interpreted as raw binary data. The interpretation of these messages is left to the application.
  9. Concurrency rules for WebSocket connections

    main

    A *Conn supports one concurrent reader and one concurrent writer.

    • Writer methods (do not call concurrently): NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel.
    • Reader methods (do not call concurrently): NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler.
    • Safe concurrent methods: Close and WriteControl can be called concurrently with any other method.
  10. Handle WebSocket handshake errors

    main

    When a WebSocket handshake fails, Dial or DialContext returns ErrBadHandshake along with a non-nil *http.Response. This allows the caller to inspect the response for details such as redirects, authentication requirements, or other HTTP-level errors. Note that the response body may not contain the entire response and does not need to be closed by the application.

    conn, resp, err := dialer.Dial("ws://example.com/ws", nil)
    if err != nil {
        if errors.Is(err, websocket.ErrBadHandshake) {
            // Inspect resp.StatusCode or resp.Header
            fmt.Printf("Handshake failed with status: %d\n", resp.StatusCode)
        }
    }
  11. Use PreparedMessage for efficient multi-connection broadcasting

    main

    A PreparedMessage caches the on-the-wire representation of a message payload. This is highly efficient when sending the same message to multiple connections, especially when compression is enabled. Instead of re-compressing the payload for every connection, the CPU-intensive compression is performed once per unique set of compression options and cached.

    To use it:

    1. Initialize a message using NewPreparedMessage(messageType, data).
    2. Send the resulting object to connections using the WritePreparedMessage method (available on the Conn type).
    // Example conceptual usage
    // messageType is typically websocket.TextMessage or websocket.BinaryMessage
    pm, err := websocket.NewPreparedMessage(websocket.TextMessage, []byte("hello world"))
    if err != nil {
    	return err
    }
    
    // Then use WritePreparedMessage on your connections
    err = conn.WritePreparedMessage(pm)