or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client.mdconnection.mdhandlers.mdindex.mdserver.mdtypes.md
tile.json

client.mddocs/

Client-Side WebSocket

Client-side WebSocket functionality allows establishing WebSocket connections to servers using the Dialer type. The Dialer provides support for custom headers, proxy configuration, TLS settings, connection timeouts, and subprotocol negotiation.

Dialer Type

The Dialer type contains options for connecting to a WebSocket server. It is safe to call Dialer's methods concurrently.

type Dialer struct {
    // NetDial specifies the dial function for creating TCP connections. If
    // NetDial is nil, net.Dial is used.
    NetDial func(network, addr string) (net.Conn, error)

    // NetDialContext specifies the dial function for creating TCP connections. If
    // NetDialContext is nil, NetDial is used.
    NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)

    // NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If
    // NetDialTLSContext is nil, NetDialContext is used.
    // If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and
    // TLSClientConfig is ignored.
    NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)

    // Proxy specifies a function to return a proxy for a given
    // Request. If the function returns a non-nil error, the
    // request is aborted with the provided error.
    // If Proxy is nil or returns a nil *URL, no proxy is used.
    Proxy func(*http.Request) (*url.URL, error)

    // TLSClientConfig specifies the TLS configuration to use with tls.Client.
    // If nil, the default configuration is used.
    // If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake
    // is done there and TLSClientConfig is ignored.
    TLSClientConfig *tls.Config

    // HandshakeTimeout specifies the duration for the handshake to complete.
    HandshakeTimeout time.Duration

    // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
    // size is zero, then a useful default size is used. The I/O buffer sizes
    // do not limit the size of the messages that can be sent or received.
    ReadBufferSize  int
    WriteBufferSize int

    // WriteBufferPool is a pool of buffers for write operations. If the value
    // is not set, then write buffers are allocated to the connection for the
    // lifetime of the connection.
    //
    // A pool is most useful when the application has a modest volume of writes
    // across a large number of connections.
    //
    // Applications should use a single pool for each unique value of
    // WriteBufferSize.
    WriteBufferPool BufferPool

    // Subprotocols specifies the client's requested subprotocols.
    Subprotocols []string

    // EnableCompression specifies if the client should attempt to negotiate
    // per message compression (RFC 7692). Setting this value to true does not
    // guarantee that compression will be supported. Currently only "no context
    // takeover" modes are supported.
    EnableCompression bool

    // Jar specifies the cookie jar.
    // If Jar is nil, cookies are not sent in requests and ignored
    // in responses.
    Jar http.CookieJar
}

DefaultDialer

A pre-configured dialer with default values.

var DefaultDialer = &Dialer{
    Proxy:            http.ProxyFromEnvironment,
    HandshakeTimeout: 45 * time.Second,
}

Dial Methods

Dial

Creates a new client connection by calling DialContext with a background context.

func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error)

DialContext

Creates a new client connection with context support.

func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error)

Use requestHeader to specify the origin (Origin), subprotocols (Sec-WebSocket-Protocol), and cookies (Cookie). Use response.Header to get the selected subprotocol (Sec-WebSocket-Protocol) and cookies (Set-Cookie).

The context will be used in the request and in the Dialer.

If the WebSocket handshake fails, ErrBadHandshake is returned along with a non-nil *http.Response so that callers can handle redirects, authentication, etc. The response body may not contain the entire response and does not need to be closed by the application.

Usage Examples

Basic Client Connection

package main

import (
    "log"
    "net/url"
    "github.com/gorilla/websocket"
)

func main() {
    u := url.URL{Scheme: "ws", Host: "localhost:8080", Path: "/ws"}

    conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
    if err != nil {
        log.Fatal("Dial error:", err)
    }
    defer conn.Close()

    // Send a message
    err = conn.WriteMessage(websocket.TextMessage, []byte("Hello"))
    if err != nil {
        log.Fatal("Write error:", err)
    }

    // Read response
    _, message, err := conn.ReadMessage()
    if err != nil {
        log.Fatal("Read error:", err)
    }
    log.Printf("Received: %s", message)
}

Client with Custom Headers

import (
    "net/http"
    "net/url"
    "github.com/gorilla/websocket"
)

func connectWithHeaders() (*websocket.Conn, error) {
    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}

    // Set custom headers
    headers := http.Header{}
    headers.Set("Origin", "https://example.com")
    headers.Set("Authorization", "Bearer token123")

    conn, resp, err := websocket.DefaultDialer.Dial(u.String(), headers)
    if err != nil {
        log.Printf("Dial failed: %v, Response: %v", err, resp)
        return nil, err
    }

    return conn, nil
}

Client with Context and Timeout

import (
    "context"
    "time"
    "net/url"
    "github.com/gorilla/websocket"
)

func connectWithTimeout() (*websocket.Conn, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}

    conn, _, err := websocket.DefaultDialer.DialContext(ctx, u.String(), nil)
    if err != nil {
        return nil, err
    }

    return conn, nil
}

Client with TLS Configuration

import (
    "crypto/tls"
    "net/url"
    "github.com/gorilla/websocket"
)

func connectTLS() (*websocket.Conn, error) {
    u := url.URL{Scheme: "wss", Host: "secure.example.com", Path: "/ws"}

    dialer := websocket.Dialer{
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: false,
            ServerName:         "secure.example.com",
        },
        HandshakeTimeout: 45 * time.Second,
    }

    conn, _, err := dialer.Dial(u.String(), nil)
    if err != nil {
        return nil, err
    }

    return conn, nil
}

Client with Proxy

import (
    "net/http"
    "net/url"
    "github.com/gorilla/websocket"
)

func connectViaProxy() (*websocket.Conn, error) {
    proxyURL, _ := url.Parse("http://proxy.example.com:8080")

    dialer := websocket.Dialer{
        Proxy: http.ProxyURL(proxyURL),
        HandshakeTimeout: 45 * time.Second,
    }

    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}
    conn, _, err := dialer.Dial(u.String(), nil)
    if err != nil {
        return nil, err
    }

    return conn, nil
}

Client with Subprotocol Negotiation

func connectWithSubprotocol() (*websocket.Conn, error) {
    dialer := websocket.Dialer{
        Subprotocols: []string{"v1.chat.example.com", "v2.chat.example.com"},
    }

    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}
    conn, _, err := dialer.Dial(u.String(), nil)
    if err != nil {
        return nil, err
    }

    // Check which subprotocol was negotiated
    protocol := conn.Subprotocol()
    log.Printf("Using subprotocol: %s", protocol)

    return conn, nil
}

Client with Compression

func connectWithCompression() (*websocket.Conn, error) {
    dialer := websocket.Dialer{
        EnableCompression: true,
    }

    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}
    conn, _, err := dialer.Dial(u.String(), nil)
    if err != nil {
        return nil, err
    }

    // Messages will be automatically decompressed on read
    // Use conn.EnableWriteCompression(true) to compress writes

    return conn, nil
}

Client with Cookie Jar

import (
    "net/http"
    "net/http/cookiejar"
    "net/url"
    "github.com/gorilla/websocket"
)

func connectWithCookies() (*websocket.Conn, error) {
    jar, err := cookiejar.New(nil)
    if err != nil {
        return nil, err
    }

    dialer := websocket.Dialer{
        Jar: jar,
    }

    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}
    conn, resp, err := dialer.Dial(u.String(), nil)
    if err != nil {
        return nil, err
    }

    // Cookies from resp are automatically stored in jar
    cookies := jar.Cookies(u.URL())
    log.Printf("Received cookies: %v", cookies)

    return conn, nil
}

Client with Custom Dial Function

import (
    "net"
    "context"
    "time"
    "github.com/gorilla/websocket"
)

func connectWithCustomDial() (*websocket.Conn, error) {
    dialer := websocket.Dialer{
        NetDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
            // Custom dialing logic (e.g., binding to specific interface)
            d := &net.Dialer{
                Timeout:   10 * time.Second,
                LocalAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100")},
            }
            return d.DialContext(ctx, network, addr)
        },
    }

    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}
    conn, _, err := dialer.Dial(u.String(), nil)
    if err != nil {
        return nil, err
    }

    return conn, nil
}

Handling Connection Errors and Redirects

func connectWithErrorHandling() (*websocket.Conn, error) {
    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}

    conn, resp, err := websocket.DefaultDialer.Dial(u.String(), nil)
    if err != nil {
        if err == websocket.ErrBadHandshake {
            log.Printf("Handshake failed with status %d", resp.StatusCode)

            // Handle redirects
            if resp.StatusCode == http.StatusMovedPermanently ||
               resp.StatusCode == http.StatusFound {
                location := resp.Header.Get("Location")
                log.Printf("Redirect to: %s", location)
                // Follow redirect manually if needed
            }

            // Handle authentication
            if resp.StatusCode == http.StatusUnauthorized {
                log.Println("Authentication required")
                // Add authentication and retry
            }
        }
        return nil, err
    }

    return conn, nil
}

Deprecated Client Functions

NewClient

func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error)

Deprecated: Use Dialer instead.

Creates a new client connection using the given net connection. The URL u specifies the host and request URI.

Error Handling

ErrBadHandshake

var ErrBadHandshake = errors.New("websocket: bad handshake")

Returned when the server response to the opening handshake is invalid. When this error is returned, a non-nil *http.Response is also returned, allowing callers to handle redirects, authentication, etc.

WebSocket URL Schemes

  • ws:// - Unencrypted WebSocket connection
  • wss:// - TLS-encrypted WebSocket connection

Example:

// Unencrypted
u1 := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}

// TLS-encrypted
u2 := url.URL{Scheme: "wss", Host: "secure.example.com", Path: "/ws"}

Connection Lifecycle

  1. Dial: Establish TCP connection
  2. Handshake: Perform WebSocket handshake
  3. Communication: Exchange messages
  4. Close: Send close message and close connection

Example complete lifecycle:

func clientLifecycle() error {
    // 1. Dial
    u := url.URL{Scheme: "ws", Host: "example.com", Path: "/ws"}
    conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
    if err != nil {
        return err
    }
    defer conn.Close()

    // 2. Communication
    err = conn.WriteMessage(websocket.TextMessage, []byte("Hello"))
    if err != nil {
        return err
    }

    _, msg, err := conn.ReadMessage()
    if err != nil {
        return err
    }
    log.Printf("Received: %s", msg)

    // 3. Close (defer handles it, or explicit close message)
    err = conn.WriteMessage(websocket.CloseMessage,
        websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
    if err != nil {
        return err
    }

    return nil
}