WebSocket connections can handle control messages (close, ping, pong) through customizable handler functions, and can enable per-message compression for efficient data transfer.
The WebSocket protocol defines three types of control messages: close, ping, and pong. The Conn type provides methods to set and get handlers for these messages.
Sets the handler for close messages received from the peer.
func (c *Conn) SetCloseHandler(h func(code int, text string) error)The code argument to h is the received close code or CloseNoStatusReceived if the close message is empty. The default close handler sends a close message back to the peer.
The handler function is called from the NextReader, ReadMessage, and message reader Read methods. The application must read the connection to process close messages.
The connection read methods return a CloseError when a close message is received. Most applications should handle close messages as part of their normal error handling. Applications should only set a close handler when the application must perform some action before sending a close message back to the peer.
Example:
conn.SetCloseHandler(func(code int, text string) error {
log.Printf("Received close message: code=%d, text=%s", code, text)
// Perform cleanup before closing
// ...
// Send close message back to peer
message := websocket.FormatCloseMessage(code, "")
conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second))
return nil
})Returns the current close handler.
func (c *Conn) CloseHandler() func(code int, text string) errorExample:
handler := conn.CloseHandler()
log.Printf("Current close handler: %T", handler)Sets the handler for ping messages received from the peer.
func (c *Conn) SetPingHandler(h func(appData string) error)The appData argument to h is the PING message application data. The default ping handler sends a pong to the peer.
The handler function is called from the NextReader, ReadMessage, and message reader Read methods. The application must read the connection to process ping messages.
Example:
conn.SetPingHandler(func(appData string) error {
log.Printf("Received ping: %s", appData)
// Send pong response
err := conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(time.Second))
if err != nil {
log.Println("Pong error:", err)
}
return err
})Returns the current ping handler.
func (c *Conn) PingHandler() func(appData string) errorExample:
handler := conn.PingHandler()
log.Printf("Current ping handler: %T", handler)Sets the handler for pong messages received from the peer.
func (c *Conn) SetPongHandler(h func(appData string) error)The appData argument to h is the PONG message application data. The default pong handler does nothing.
The handler function is called from the NextReader, ReadMessage, and message reader Read methods. The application must read the connection to process pong messages.
If an application sends ping messages, then the application should set a pong handler to receive the corresponding pong.
Example:
conn.SetPongHandler(func(appData string) error {
log.Printf("Received pong: %s", appData)
return nil
})Returns the current pong handler.
func (c *Conn) PongHandler() func(appData string) errorExample:
handler := conn.PongHandler()
log.Printf("Current pong handler: %T", handler)The package supports per-message compression extensions (RFC 7692) in an experimental capacity. Compression can reduce bandwidth usage but may increase CPU usage and latency.
Enables and disables write compression of subsequent text and binary messages.
func (c *Conn) EnableWriteCompression(enable bool)This function is a noop if compression was not negotiated with the peer during the handshake.
Example:
// Enable compression for outgoing messages
conn.EnableWriteCompression(true)
err := conn.WriteMessage(websocket.TextMessage, []byte("This will be compressed"))
if err != nil {
log.Println("Write error:", err)
}
// Disable compression
conn.EnableWriteCompression(false)
err = conn.WriteMessage(websocket.TextMessage, []byte("This will not be compressed"))
if err != nil {
log.Println("Write error:", err)
}Sets the flate compression level for subsequent text and binary messages.
func (c *Conn) SetCompressionLevel(level int) errorThis function is a noop if compression was not negotiated with the peer. See the compress/flate package for a description of compression levels.
Valid compression levels from the compress/flate package:
flate.NoCompression (0): No compressionflate.BestSpeed (1): Best speed, least compressionflate.BestCompression (9): Best compression, slowestflate.DefaultCompression (-1): Default compression levelExample:
import "compress/flate"
// Set compression to best speed
err := conn.SetCompressionLevel(flate.BestSpeed)
if err != nil {
log.Println("Set compression level error:", err)
}
conn.EnableWriteCompression(true)
err = conn.WriteMessage(websocket.TextMessage, []byte("Compressed at best speed"))
if err != nil {
log.Println("Write error:", err)
}
// Set compression to best compression
err = conn.SetCompressionLevel(flate.BestCompression)
if err != nil {
log.Println("Set compression level error:", err)
}
err = conn.WriteMessage(websocket.TextMessage, []byte("Compressed at best compression"))
if err != nil {
log.Println("Write error:", err)
}import (
"time"
"github.com/gorilla/websocket"
)
func keepalive(conn *websocket.Conn) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
// Set pong handler to receive responses
pongReceived := make(chan struct{})
conn.SetPongHandler(func(appData string) error {
pongReceived <- struct{}{}
return nil
})
// Start read loop in goroutine
go func() {
for {
if _, _, err := conn.NextReader(); err != nil {
return
}
}
}()
for {
select {
case <-ticker.C:
// Send ping
err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(10*time.Second))
if err != nil {
log.Println("Ping error:", err)
return
}
// Wait for pong response with timeout
select {
case <-pongReceived:
log.Println("Pong received")
case <-time.After(5 * time.Second):
log.Println("Pong timeout")
conn.Close()
return
}
}
}
}func heartbeat(conn *websocket.Conn) {
const (
writeWait = 10 * time.Second
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
)
// Set initial read deadline
conn.SetReadDeadline(time.Now().Add(pongWait))
// Reset deadline on pong
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(pongWait))
return nil
})
// Start read loop
go func() {
for {
_, _, err := conn.ReadMessage()
if err != nil {
log.Println("Read error:", err)
return
}
}
}()
// Send pings periodically
ticker := time.NewTicker(pingPeriod)
defer ticker.Stop()
for {
select {
case <-ticker.C:
conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
log.Println("Ping error:", err)
return
}
}
}
}func gracefulShutdown(conn *websocket.Conn) {
// Set up close handler
conn.SetCloseHandler(func(code int, text string) error {
log.Printf("Received close: code=%d, text=%s", code, text)
// Perform cleanup
// ... save state, close resources, etc.
// Send close response
message := websocket.FormatCloseMessage(code, "")
conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second))
return nil
})
// Main read loop
for {
_, message, err := conn.ReadMessage()
if err != nil {
if closeErr, ok := err.(*websocket.CloseError); ok {
log.Printf("Connection closed: %v", closeErr)
} else {
log.Printf("Read error: %v", err)
}
break
}
// Process message
log.Printf("Received: %s", message)
}
}func conditionalCompression(conn *websocket.Conn) {
// Enable compression if negotiated
conn.EnableWriteCompression(true)
for {
_, message, err := conn.ReadMessage()
if err != nil {
break
}
// Disable compression for small messages
if len(message) < 1024 {
conn.EnableWriteCompression(false)
} else {
conn.EnableWriteCompression(true)
}
err = conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
break
}
}
}func sendCustomClose(conn *websocket.Conn) {
// Send custom close message with code and text
closeMessage := websocket.FormatCloseMessage(
websocket.CloseNormalClosure,
"Server shutting down",
)
deadline := time.Now().Add(5 * time.Second)
err := conn.WriteControl(websocket.CloseMessage, closeMessage, deadline)
if err != nil {
log.Println("Close error:", err)
}
// Close the connection
conn.Close()
}func applicationPingPong(conn *websocket.Conn) {
// Custom ping/pong with application data
conn.SetPingHandler(func(appData string) error {
log.Printf("Ping received with data: %s", appData)
// Send pong with same data
deadline := time.Now().Add(5 * time.Second)
return conn.WriteControl(websocket.PongMessage, []byte(appData), deadline)
})
// Read loop
go func() {
for {
if _, _, err := conn.NextReader(); err != nil {
return
}
}
}()
// Send ping with custom data
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
appData := []byte("timestamp:" + time.Now().String())
deadline := time.Now().Add(10 * time.Second)
err := conn.WriteControl(websocket.PingMessage, appData, deadline)
if err != nil {
log.Println("Ping error:", err)
return
}
}
}
}Connections handle received close messages by:
The default close handler sends a close message to the peer.
Connections handle received ping messages by:
The default ping handler sends a pong message to the peer.
Connections handle received pong messages by:
The default pong handler does nothing. If an application sends ping messages, it should set a pong handler to receive the corresponding pong.
Control message handler functions are called from the NextReader, ReadMessage, and message reader Read methods. This means:
The Close and WriteControl methods can be called concurrently with all other methods, making them safe for use in signal handlers or separate goroutines.
Example:
// Goroutine sending periodic pings
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
deadline := time.Now().Add(10 * time.Second)
conn.WriteControl(websocket.PingMessage, []byte{}, deadline)
}
}()
// Main goroutine reading messages
for {
_, message, err := conn.ReadMessage()
if err != nil {
break
}
// Process message
}Current compression support:
For more details, refer to RFC 7692.