or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

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

connection.mddocs/

Connection Management and I/O

The Conn type represents a WebSocket connection and provides methods for reading and writing messages, managing connection lifecycle, and configuring connection behavior.

Conn Type

type Conn struct {
    // unexported fields
}

The Conn type has no exported fields. All interaction is through methods.

Reading Messages

ReadMessage

Helper method for reading a complete message into a byte slice.

func (c *Conn) ReadMessage() (messageType int, p []byte, err error)

Returns the message type (TextMessage or BinaryMessage) and the message payload. This is a convenience method that internally uses NextReader.

Example:

for {
    messageType, message, err := conn.ReadMessage()
    if err != nil {
        log.Println("Read error:", err)
        break
    }
    log.Printf("Received %d: %s", messageType, message)
}

NextReader

Returns the next data message received from the peer as an io.Reader.

func (c *Conn) NextReader() (messageType int, r io.Reader, err error)

The returned messageType is either TextMessage or BinaryMessage. There can be at most one open reader on a connection. NextReader discards the previous message if the application has not already consumed it.

Applications must break out of the read loop when this method returns a non-nil error value. Errors returned from this method are permanent. Once this method returns a non-nil error, all subsequent calls return the same error.

Example:

for {
    messageType, reader, err := conn.NextReader()
    if err != nil {
        log.Println("NextReader error:", err)
        break
    }

    // Read message using io.Reader interface
    message, err := io.ReadAll(reader)
    if err != nil {
        log.Println("Read error:", err)
        break
    }
    log.Printf("Received %d: %s", messageType, message)
}

ReadJSON

Reads the next JSON-encoded message from the connection and stores it in the value pointed to by v.

func (c *Conn) ReadJSON(v interface{}) error

Uses the encoding/json package for unmarshaling.

Example:

type Message struct {
    Type string `json:"type"`
    Data string `json:"data"`
}

var msg Message
err := conn.ReadJSON(&msg)
if err != nil {
    log.Println("ReadJSON error:", err)
    return
}
log.Printf("Received: %+v", msg)

Writing Messages

WriteMessage

Helper method for writing a complete message from a byte slice.

func (c *Conn) WriteMessage(messageType int, data []byte) error

This is a convenience method that internally uses NextWriter. The messageType can be TextMessage, BinaryMessage, or any control message type (CloseMessage, PingMessage, PongMessage).

Example:

err := conn.WriteMessage(websocket.TextMessage, []byte("Hello"))
if err != nil {
    log.Println("Write error:", err)
    return
}

NextWriter

Returns a writer for the next message to send.

func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error)

The writer's Close method flushes the complete message to the network. There can be at most one open writer on a connection. NextWriter closes the previous writer if the application has not already done so.

All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage, PongMessage) are supported.

Example:

writer, err := conn.NextWriter(websocket.TextMessage)
if err != nil {
    log.Println("NextWriter error:", err)
    return
}

_, err = writer.Write([]byte("Hello "))
if err != nil {
    log.Println("Write error:", err)
    return
}

_, err = writer.Write([]byte("World"))
if err != nil {
    log.Println("Write error:", err)
    return
}

err = writer.Close()
if err != nil {
    log.Println("Close error:", err)
    return
}

WriteJSON

Writes the JSON encoding of v as a message.

func (c *Conn) WriteJSON(v interface{}) error

Uses the encoding/json package for marshaling.

Example:

type Message struct {
    Type string `json:"type"`
    Data string `json:"data"`
}

msg := Message{Type: "greeting", Data: "Hello"}
err := conn.WriteJSON(msg)
if err != nil {
    log.Println("WriteJSON error:", err)
    return
}

WriteControl

Writes a control message with the given deadline.

func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error

The allowed message types are CloseMessage, PingMessage, and PongMessage. This method can be called concurrently with other write methods.

Example sending a ping:

deadline := time.Now().Add(10 * time.Second)
err := conn.WriteControl(websocket.PingMessage, []byte(""), deadline)
if err != nil {
    log.Println("Ping error:", err)
}

WritePreparedMessage

Writes a prepared message into the connection.

func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error

See the PreparedMessage section below for details on creating prepared messages.

Prepared Messages

PreparedMessage Type

Caches wire representations of a message payload for efficient sending to multiple connections.

type PreparedMessage struct {
    // unexported fields
}

PreparedMessage is especially useful when compression is used because the CPU and memory expensive compression operation can be executed once for a given set of compression options.

NewPreparedMessage

Returns an initialized PreparedMessage.

func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error)

Valid wire representation will be calculated lazily only once for a set of current connection options.

Example:

// Prepare a message once
pm, err := websocket.NewPreparedMessage(websocket.TextMessage, []byte("broadcast"))
if err != nil {
    log.Fatal(err)
}

// Send to multiple connections efficiently
for _, conn := range connections {
    err := conn.WritePreparedMessage(pm)
    if err != nil {
        log.Printf("Write error: %v", err)
    }
}

Connection Control

Close

Closes the underlying network connection without sending or waiting for a close message.

func (c *Conn) Close() error

For a graceful close, send a close message before calling Close:

err := conn.WriteMessage(websocket.CloseMessage,
    websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
    log.Println("Write close error:", err)
}
conn.Close()

SetReadDeadline

Sets the read deadline on the underlying network connection.

func (c *Conn) SetReadDeadline(t time.Time) error

After a read has timed out, the websocket connection state is corrupt and all future reads will return an error. A zero value for t means reads will not time out.

Example:

// Set 30 second read timeout
conn.SetReadDeadline(time.Now().Add(30 * time.Second))

messageType, message, err := conn.ReadMessage()
if err != nil {
    if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
        log.Println("Read timeout")
    } else {
        log.Println("Read error:", err)
    }
    return
}

SetWriteDeadline

Sets the write deadline on the underlying network connection.

func (c *Conn) SetWriteDeadline(t time.Time) error

After a write has timed out, the websocket state is corrupt and all future writes will return an error. A zero value for t means writes will not time out.

Example:

// Set 10 second write timeout
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))

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

SetReadLimit

Sets the maximum size in bytes for a message read from the peer.

func (c *Conn) SetReadLimit(limit int64)

If a message exceeds the limit, the connection sends a close message to the peer and returns ErrReadLimit to the application.

Example:

// Limit messages to 1MB
conn.SetReadLimit(1024 * 1024)

_, message, err := conn.ReadMessage()
if err == websocket.ErrReadLimit {
    log.Println("Message too large")
    return
}

Connection Information

LocalAddr

Returns the local network address.

func (c *Conn) LocalAddr() net.Addr

Example:

addr := conn.LocalAddr()
log.Printf("Local address: %s", addr.String())

RemoteAddr

Returns the remote network address.

func (c *Conn) RemoteAddr() net.Addr

Example:

addr := conn.RemoteAddr()
log.Printf("Remote address: %s", addr.String())

Subprotocol

Returns the negotiated protocol for the connection.

func (c *Conn) Subprotocol() string

Example:

protocol := conn.Subprotocol()
if protocol != "" {
    log.Printf("Using subprotocol: %s", protocol)
}

NetConn

Returns the underlying connection that is wrapped by the Conn.

func (c *Conn) NetConn() net.Conn

Warning: Writing to or reading from this connection directly will corrupt the WebSocket connection.

Example (for inspection only):

netConn := conn.NetConn()
log.Printf("Underlying connection type: %T", netConn)

UnderlyingConn (Deprecated)

func (c *Conn) UnderlyingConn() net.Conn

Deprecated: Use NetConn instead.

Usage Patterns

Echo Server

func echoHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }
    defer conn.Close()

    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }

        err = conn.WriteMessage(messageType, message)
        if err != nil {
            log.Println("Write error:", err)
            break
        }
    }
}

Streaming Read/Write

func streamHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    defer conn.Close()

    for {
        messageType, reader, err := conn.NextReader()
        if err != nil {
            break
        }

        writer, err := conn.NextWriter(messageType)
        if err != nil {
            break
        }

        if _, err := io.Copy(writer, reader); err != nil {
            break
        }

        if err := writer.Close(); err != nil {
            break
        }
    }
}

JSON Message Exchange

type Request struct {
    Action string `json:"action"`
    Data   string `json:"data"`
}

type Response struct {
    Status string `json:"status"`
    Result string `json:"result"`
}

func jsonHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    defer conn.Close()

    for {
        var req Request
        err := conn.ReadJSON(&req)
        if err != nil {
            log.Println("ReadJSON error:", err)
            break
        }

        // Process request
        resp := Response{
            Status: "ok",
            Result: "Processed: " + req.Data,
        }

        err = conn.WriteJSON(resp)
        if err != nil {
            log.Println("WriteJSON error:", err)
            break
        }
    }
}

Broadcasting with PreparedMessage

type Hub struct {
    connections map[*websocket.Conn]bool
    broadcast   chan []byte
}

func (h *Hub) run() {
    for {
        message := <-h.broadcast

        // Prepare message once
        pm, err := websocket.NewPreparedMessage(websocket.TextMessage, message)
        if err != nil {
            log.Println("Prepare error:", err)
            continue
        }

        // Send to all connections
        for conn := range h.connections {
            err := conn.WritePreparedMessage(pm)
            if err != nil {
                log.Println("Write error:", err)
                conn.Close()
                delete(h.connections, conn)
            }
        }
    }
}

Read Loop with Timeouts

func readLoop(conn *websocket.Conn) {
    // Set read deadline before each read
    conn.SetReadDeadline(time.Now().Add(60 * time.Second))

    // Set pong handler to reset deadline
    conn.SetPongHandler(func(string) error {
        conn.SetReadDeadline(time.Now().Add(60 * time.Second))
        return nil
    })

    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }

        // Reset deadline after successful read
        conn.SetReadDeadline(time.Now().Add(60 * time.Second))

        // Process message
        log.Printf("Received: %s", message)
    }
}

Concurrency

Connections support one concurrent reader and one concurrent writer. Applications are responsible for ensuring:

  • No more than one goroutine calls the read methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) concurrently
  • No more than one goroutine calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently

The Close and WriteControl methods can be called concurrently with all other methods.

Concurrent Reader and Writer

func handleConnection(conn *websocket.Conn) {
    defer conn.Close()

    // Start reader goroutine
    go func() {
        for {
            _, message, err := conn.ReadMessage()
            if err != nil {
                log.Println("Read error:", err)
                return
            }
            log.Printf("Received: %s", message)
        }
    }()

    // Writer in main goroutine
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            err := conn.WriteMessage(websocket.TextMessage, []byte("ping"))
            if err != nil {
                log.Println("Write error:", err)
                return
            }
        }
    }
}

Error Types

ErrCloseSent

var ErrCloseSent = errors.New("websocket: close sent")

Returned when the application writes a message to the connection after sending a close message.

ErrReadLimit

var ErrReadLimit = errors.New("websocket: read limit exceeded")

Returned when reading a message that is larger than the read limit set for the connection.

Deprecated Package-Level Functions

ReadJSON

func ReadJSON(c *Conn, v interface{}) error

Deprecated: Use c.ReadJSON instead.

WriteJSON

func WriteJSON(c *Conn, v interface{}) error

Deprecated: Use c.WriteJSON instead.

JoinMessages

func JoinMessages(c *Conn, term string) io.Reader

Concatenates received messages to create a single io.Reader. The string term is appended to each message. The returned reader does not support concurrent calls to the Read method.

Example:

reader := websocket.JoinMessages(conn, "\n")
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
    line := scanner.Text()
    log.Printf("Line: %s", line)
}