# WebSocket
_Path: en/lua/http/websocket_
## Table of Contents
- WebSocket Client
## Content
# WebSocket Client
WebSocket client for real-time bidirectional communication with servers.
## Loading
```lua
local websocket = require("websocket")
```
### Basic Connection
```lua
local client, err = websocket.connect("wss://api.example.com/ws")
if err then
return nil, err
end
```
### With Options
```lua
local client, err = websocket.connect("wss://api.example.com/ws", {
headers = {
["Authorization"] = "Bearer " .. token
},
protocols = {"graphql-ws"},
dial_timeout = "10s",
read_timeout = "30s",
compression = websocket.COMPRESSION.CONTEXT_TAKEOVER
})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `url` | string | WebSocket URL (ws:// or wss://) |
| `options` | table | Connection options (optional) |
**Returns:** `Client, error`
### Connection Options
| Option | Type | Description |
|--------|------|-------------|
| `headers` | table | HTTP headers for handshake |
| `protocols` | table | WebSocket subprotocols |
| `dial_timeout` | number/string | Connection timeout (ms or "5s") |
| `read_timeout` | number/string | Read timeout |
| `write_timeout` | number/string | Write timeout |
| `compression` | number | Compression mode (see Constants) |
| `compression_threshold` | number | Min size to compress (0-100MB) |
| `read_limit` | number | Max message size (0-128MB) |
| `channel_capacity` | number | Receive channel buffer (1-10000) |
**Timeout format:** Numbers are milliseconds, strings use Go duration format ("5s", "1m").
### Text Messages
```lua
local ok, err = client:send("Hello, Server!")
if err then
return nil, err
end
-- Send JSON
client:send(json.encode({
type = "subscribe",
channel = "orders"
}))
```
### Binary Messages
```lua
client:send(binary_data, websocket.BINARY)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | string | Message content |
| `type` | number | `websocket.TEXT` (1) or `websocket.BINARY` (2) |
**Returns:** `boolean, error`
### Ping
```lua
client:ping()
```
**Returns:** `boolean, error`
## Receiving Messages
The `channel()` method returns a channel for receiving messages. Works with `channel.select` for multiplexing.
### Basic Receive
```lua
local ch = client:channel()
local msg, ok = ch:receive()
if ok then
print("Type:", msg.type) -- "text" or "binary"
print("Data:", msg.data)
end
```
### Message Loop
```lua
local ch = client:channel()
while true do
local msg, ok = ch:receive()
if not ok then
break -- Connection closed
end
if msg.type == "text" then
local data = json.decode(msg.data)
handle_message(data)
end
end
```
### With Select
```lua
local ch = client:channel()
local timeout = time.after("30s")
while true do
local r = channel.select {
ch:case_receive(),
timeout:case_receive()
}
if r.channel == timeout then
client:ping() -- Keep-alive
timeout = time.after("30s")
else
local data = json.decode(r.value.data)
process(data)
end
end
```
### Message Object
| Field | Type | Description |
|-------|------|-------------|
| `type` | string | `"text"` or `"binary"` |
| `data` | string | Message content |
## Closing Connection
```lua
-- Normal close (code 1000)
client:close()
-- With code and reason
client:close(websocket.CLOSE_CODES.NORMAL, "Session ended")
-- Error close
client:close(websocket.CLOSE_CODES.INTERNAL_ERROR, "Processing failed")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `code` | number | Close code (1000-4999), default 1000 |
| `reason` | string | Close reason (optional) |
**Returns:** `boolean, error`
### Message Types
```lua
-- Numeric (for send)
websocket.TEXT -- 1
websocket.BINARY -- 2
-- String (received message type field)
websocket.TYPE_TEXT -- "text"
websocket.TYPE_BINARY -- "binary"
websocket.TYPE_PING -- "ping"
websocket.TYPE_PONG -- "pong"
websocket.TYPE_CLOSE -- "close"
```
### Compression Modes
```lua
websocket.COMPRESSION.DISABLED -- 0 (no compression)
websocket.COMPRESSION.CONTEXT_TAKEOVER -- 1 (sliding window)
websocket.COMPRESSION.NO_CONTEXT -- 2 (per-message)
```
### Close Codes
| Constant | Code | Description |
|----------|------|-------------|
| `NORMAL` | 1000 | Normal closure |
| `GOING_AWAY` | 1001 | Server shutting down |
| `PROTOCOL_ERROR` | 1002 | Protocol error |
| `UNSUPPORTED_DATA` | 1003 | Unsupported data type |
| `NO_STATUS` | 1005 | No status received |
| `ABNORMAL_CLOSURE` | 1006 | Connection lost |
| `INVALID_PAYLOAD` | 1007 | Invalid frame payload |
| `POLICY_VIOLATION` | 1008 | Policy violation |
| `MESSAGE_TOO_BIG` | 1009 | Message too large |
| `INTERNAL_ERROR` | 1011 | Server error |
| `SERVICE_RESTART` | 1012 | Server restarting |
| `TRY_AGAIN_LATER` | 1013 | Server overloaded |
```lua
client:close(websocket.CLOSE_CODES.NORMAL, "Done")
```
### Real-Time Chat
```lua
local function connect_chat(room_id, on_message)
local client, err = websocket.connect("wss://chat.example.com/ws", {
headers = {["Authorization"] = "Bearer " .. token}
})
if err then
return nil, err
end
-- Join room
client:send(json.encode({
type = "join",
room = room_id
}))
-- Message loop
local ch = client:channel()
while true do
local msg, ok = ch:receive()
if not ok then break end
local data = json.decode(msg.data)
on_message(data)
end
client:close()
end
```
### Price Stream with Keep-Alive
```lua
local client = websocket.connect("wss://stream.example.com/prices")
client:send(json.encode({
action = "subscribe",
symbols = {"BTC-USD", "ETH-USD"}
}))
local ch = client:channel()
local heartbeat = time.after("30s")
while true do
local r = channel.select {
ch:case_receive(),
heartbeat:case_receive()
}
if r.channel == heartbeat then
client:ping()
heartbeat = time.after("30s")
elseif not r.ok then
break -- Connection closed
else
local price = json.decode(r.value.data)
update_price(price.symbol, price.value)
end
end
client:close()
```
## Permissions
WebSocket connections are subject to security policy evaluation.
### Security Actions
| Action | Resource | Description |
|--------|----------|-------------|
| `websocket.connect` | - | Allow/deny WebSocket connections |
| `websocket.connect.url` | URL | Allow/deny connections to specific URLs |
See [Security Model](system/security.md) for policy configuration.
## Errors
| Condition | Kind | Retryable |
|-----------|------|-----------|
| Connections disabled | `errors.PERMISSION_DENIED` | no |
| URL not allowed | `errors.PERMISSION_DENIED` | no |
| No context | `errors.INTERNAL` | no |
| Connection failed | `errors.INTERNAL` | yes |
| Invalid connection ID | `errors.INTERNAL` | no |
```lua
local client, err = websocket.connect(url)
if err then
if errors.is(err, errors.PERMISSION_DENIED) then
print("Access denied:", err:message())
elseif err:retryable() then
print("Temporary error:", err:message())
end
return nil, err
end
```
See [Error Handling](lua/core/errors.md) for working with errors.
## Navigation
Previous: HTTP Client (lua/http/client)
Next: HTML (lua/http/html)