gws (Go WebSocket)

repository·main·Indexed 23 days ago

https://github.com/lxzan/gws

A high-performance, event-driven WebSocket library for Go designed for high-concurrency scenarios such as API gateways, real-time streaming, and IM systems. It features an Event interface for lifecycle management, multiple reading models (ReadLoop, ReadMessage, and NextReader), and efficient message broadcasting via gws.NewBroadcaster. The library includes support for permessage-deflate compression, SOCKS5 proxy dialing, and integration with net/http.

Tokens
8.1K
Snippets
16
Records
58
Agent score
78%

What's inside gws

  1. How the Event interface works

    main

    GWS uses an event-driven model via the Event interface. You can implement this interface to handle the WebSocket lifecycle. The methods are:

    • OnOpen(socket *Conn): Triggered when a connection is established.
    • OnClose(socket *Conn, err error): Triggered when a close frame is received or an I/O error occurs.
    • OnPing(socket *Conn, payload []byte): Triggered when a ping frame is received.
    • OnPong(socket *Conn, payload []byte): Triggered when a pong frame is received.
    • OnMessage(socket *Conn, message *Message): Triggered when a text or binary frame is received.
    type Event interface {
        OnOpen(socket *Conn)                        // connection is established
        OnClose(socket *Conn, err error)            // received a close frame or input/output error occurs
        OnPing(socket *Conn, payload []byte)        // received a ping frame
        OnPong(socket *Conn, payload []byte)        // received a pong frame
        OnMessage(socket *Conn, message *Message)   // received a text/binary frame
    }
  2. Choose a WebSocket reading method

    main

    GWS provides three distinct ways to read data from a Conn. Do not mix multiple reading APIs or read concurrently on the same connection.

    1. ReadLoop: The recommended event-driven approach for most server-side business logic. It automatically triggers Event interface methods.
    2. ReadMessage: Used for manually pulling a single complete message. Note: You must call message.Close() after use to recycle the buffer.
    3. NextReader: Used for streaming large messages or reducing memory overhead by avoiding full packet copies. It returns an io.Reader. Note: It does not trigger OnMessage and does not check UTF-8 encoding for text messages. If the previous message was not fully read, the next call to NextReader will discard the remaining content.
  3. Choose a WebSocket reading model

    main

    GWS provides three distinct ways to read data from a connection. Do not mix multiple read APIs on the same connection and do not read concurrently.

    1. ReadLoop (Event-driven): The most common model for servers. It automatically triggers the Event interface methods (OnMessage, etc.).
    2. ReadMessage (Manual): Manually pulls one complete message. Returns a *Message; you must call message.Close() after use to recycle its buffer.
    3. NextReader (Streaming): Returns an io.Reader for streaming payloads. This helps avoid holding a whole message in memory but does not trigger OnMessage or validate UTF-8 text payloads.
  4. Best Practice: Integrating GWS with net/http

    main

    When using gws.Upgrader inside a standard net/http handler, you must run socket.ReadLoop() in a separate goroutine. If you run it in the main handler goroutine, it will block, preventing the request context from being garbage-collected in time.

    // ... inside http.HandleFunc ...
    	socket, err := upgrader.Upgrade(writer, request)
    	if err != nil {
    		return
    	}
    	go func() {
    		socket.ReadLoop() // Run in a new goroutine to allow GC
    	}()
    // ...
  5. Best Practice: Integrate GWS with net/http

    main

    When upgrading an existing net/http connection using gws.NewUpgrader, it is strongly recommended to call socket.ReadLoop() in a new goroutine. This prevents the request context from being held indefinitely, allowing it to be garbage collected properly.

    package main
    
    import (
    	"net/http"
    	"time"
    
    	"github.com/lxzan/gws"
    )
    
    const (
    	PingInterval = 5 * time.Second
    	PingWait     = 10 * time.Second
    )
    
    func main() {
    	upgrader := gws.NewUpgrader(&Handler{}, &gws.ServerOption{
    		ParallelEnabled:  true,                                 // Enable parallel message processing
    		Recovery:          gws.Recovery,                         // Enable panic recovery
    		PermessageDeflate: gws.PermessageDeflate{Enabled: true}, // Enable compression
    	})
    	http.HandleFunc("/connect", func(writer http.ResponseWriter, request *http.Request) {
    		socket, err := upgrader.Upgrade(writer, request)
    		if err != nil {
    			return
    		}
    		go func() {
    			socket.ReadLoop() // Run in a new goroutine to allow request context GC
    		}()
    	})
    	http.ListenAndServe(":6666", nil)
    }
    
    type Handler struct{}
    
    func (c *Handler) OnOpen(socket *gws.Conn) {
    	_ = socket.SetDeadline(time.Now().Add(PingInterval + PingWait))
    }
    
    func (c *Handler) OnClose(socket *gws.Conn, err error) {}
    
    func (c *Handler) OnPing(socket *gws.Conn, payload []byte) {
    	_ = socket.SetDeadline(time.Now().Add(PingInterval + PingWait))
    	_ = socket.WritePong(nil)
    }
    
    func (c *Handler) OnPong(socket *gws.Conn, payload []byte) {}
    
    func (c *Handler) OnMessage(socket *gws.Conn, message *gws.Message) {
    	defer message.Close()
    	socket.WriteMessage(message.Opcode, message.Bytes())
    }
  6. Quick Start: Create a simple GWS Server

    main

    To start a basic WebSocket server with default settings, use gws.NewServer with gws.BuiltinEventHandler and call Run on a specific address.

    package main
    
    import "github.com/lxzan/gws"
    
    func main() {
    	gws.NewServer(&gws.BuiltinEventHandler{}, nil).Run(":6666")
    }
  7. Customize Session Storage

    main
    Both ServerOption and ClientOption provide a NewSession field. This allows you to inject a custom implementation of the SessionStorage interface. This is useful for managing stateful information associated with a specific WebSocket connection/session.
  8. Configure WebSocket Server options

    main

    The ServerOption (passed to NewUpgrader or NewServer) controls the behavior of the handshake and the resulting connection. Key configuration areas include:

    • Authorization: Use Authorize to define a function that validates the *http.Request before upgrading.
    • Subprotocols: Define a list of supported subprotocols via SubProtocols.
    • Compression: Enable PermessageDeflate to support WebSocket per-message compression.
    • Timeouts: Set HandshakeTimeout for the upgrade process.
    • Headers: Add custom headers to the handshake response using ResponseHeader.
  9. Use Broadcaster for efficient mass messaging

    main

    When sending the same message to many clients, using NewBroadcaster is significantly more efficient than calling WriteAsync in a loop. Broadcaster compresses the message only once, saving substantial CPU overhead.

    To use it:

    1. Create a broadcaster with NewBroadcaster(opcode, payload).
    2. Call Broadcast(socket, callback) for each client connection.
    3. Call Close() on the broadcaster once all broadcasts are finished to release resources.
  10. Configure permessage-deflate options

    main

    The PermessageDeflate struct controls the compression parameters used during the connection handshake. Key fields include:

    • ServerContextTakeover: Whether the server can use the compression context across multiple messages.
    • ClientContextTakeover: Whether the client can use the compression context across multiple messages.
    • ServerMaxWindowBits: The maximum window bits allowed for the server.
    • ClientMaxWindowBits: The maximum window bits allowed for the client.