Hijack-based WebSocket API reference. The current native Linux io_uring worker runtime
does not support WebSocket upgrades because they rely on connection hijacking.
Upgrade an HTTP request to a WebSocket connection using UpgradeWebSocket(). This
hijacks the underlying TCP connection:
WebSocket upgrade is currently a compatibility feature. The native Linux
io_uring worker backends reject hijacked connections, so this API is not
available on that production path yet.
s.Router.GET("/ws", func(req *core.Request, resp *core.Response) { ws := core.UpgradeWebSocket(req, resp) if ws == nil { return } defer ws.Close() })
The upgrade validates:
Upgrade: websocket headerConnection: Upgrade headerSec-WebSocket-Version: 13Sec-WebSocket-Key presentIf any check fails, nil is returned and a 400 Bad Request is sent.
The simplest WebSocket handler — echo back every message received:
s.Router.GET("/ws/echo", func(req *core.Request, resp *core.Response) { ws := core.UpgradeWebSocket(req, resp) if ws == nil { return } defer ws.Close() for { opcode, data, err := ws.ReadMessage() if err != nil { break } if err := ws.WriteMessage(opcode, data); err != nil { break } } })
ReadMessage() returns the opcode, data, and error. It handles frame
fragmentation, unmasking, and control frames automatically:
opcode, data, err := ws.ReadMessage() switch opcode { case 0x1: msg := string(data) fmt.Println("Text:", msg) case 0x2: fmt.Println("Binary:", len(data), "bytes") }
Send messages with specific types:
ws.WriteText("Hello from the server!") ws.WriteBinary(imageBytes) ws.WriteMessage(0x1, []byte("text message")) ws.WriteMessage(0x2, binaryData)
| Opcode | Type | Description |
|---|---|---|
0x0 |
Continuation | Continues a fragmented message |
0x1 |
Text | UTF-8 text frame |
0x2 |
Binary | Binary data frame |
0x8 |
Close | Connection close (auto-echoed) |
0x9 |
Ping | Ping (auto-responds with Pong) |
0xA |
Pong | Pong response |
Automatic handling: PING frames are automatically responded with PONG. CLOSE frames are echoed with the status code per RFC 6455 §5.5.1. You don't need to handle control frames manually.
Send manual pings to check the connection is alive:
if err := ws.Ping(); err != nil { return }
Set read/write deadlines on the WebSocket connection:
ws.SetDeadline(time.Now().Add(30 * time.Second)) for { ws.SetDeadline(time.Now().Add(30 * time.Second)) opcode, data, err := ws.ReadMessage() if err != nil { break } handleMessage(opcode, data) }
A more complete example showing a broadcast-style chat room:
var ( clients = make(map[*core.WSConn]bool) mu sync.RWMutex ) func broadcast(msg string) { mu.RLock() defer mu.RUnlock() for ws := range clients { ws.WriteText(msg) } } s.Router.GET("/ws/chat", func(req *core.Request, resp *core.Response) { ws := core.UpgradeWebSocket(req, resp) if ws == nil { return } mu.Lock() clients[ws] = true mu.Unlock() defer func() { mu.Lock() delete(clients, ws) mu.Unlock() ws.Close() }() for { _, data, err := ws.ReadMessage() if err != nil { break } broadcast(string(data)) } })
| Method | Description |
|---|---|
ReadMessage() |
Read next message (opcode, data, error) |
WriteMessage(op, data) |
Write frame with opcode |
WriteText(msg) |
Write text frame (0x1) |
WriteBinary(data) |
Write binary frame (0x2) |
Ping() |
Send ping frame |
Close() |
Close the connection |
SetDeadline(t) |
Set read/write deadline |
Parse and send JSON messages over WebSocket connections:
type Message struct { Type string `json:"type"` Payload string `json:"payload"` } s.Router.GET("/ws/json", func(req *core.Request, resp *core.Response) { ws := core.UpgradeWebSocket(req, resp) if ws == nil { return } defer ws.Close() for { _, data, err := ws.ReadMessage() if err != nil { break } var msg Message if err := json.Unmarshal(data, &msg); err != nil { ws.WriteText(`{"error":"invalid json"}`) continue } response, _ := json.Marshal(Message{ Type: "response", Payload: "received: " + msg.Payload, }) ws.WriteText(string(response)) } })
Keep connections alive with periodic pings in a goroutine:
s.Router.GET("/ws/heartbeat", func(req *core.Request, resp *core.Response) { ws := core.UpgradeWebSocket(req, resp) if ws == nil { return } defer ws.Close() done := make(chan struct{}) go func() { ticker := time.NewTicker(15 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: if err := ws.Ping(); err != nil { return } case <-done: return } } }() for { ws.SetDeadline(time.Now().Add(30 * time.Second)) opcode, data, err := ws.ReadMessage() if err != nil { break } ws.WriteMessage(opcode, data) } close(done) })
Handle binary data (images, protocol buffers, etc.):
s.Router.GET("/ws/binary", func(req *core.Request, resp *core.Response) { ws := core.UpgradeWebSocket(req, resp) if ws == nil { return } defer ws.Close() for { opcode, data, err := ws.ReadMessage() if err != nil { break } if opcode == 0x2 { processed := processImage(data) ws.WriteBinary(processed) } else { ws.WriteText("send binary data only") } } })
Combine text commands with binary data by checking the opcode:
for { opcode, data, err := ws.ReadMessage() if err != nil { break } switch opcode { case 0x1: handleCommand(string(data)) case 0x2: handleUpload(data) } }