Skip to content

WebSocket Client

WebSocket client for real-time bidirectional communication with servers.

local websocket = require("websocket")
local client, err = websocket.connect("wss://api.example.com/ws")
if err then
return nil, err
end
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
})
ParameterTypeDescription
urlstringWebSocket URL (ws:// or wss://)
optionstableConnection options (optional)

Returns: Client, error

OptionTypeDescription
headerstableHTTP headers for handshake
protocolstableWebSocket subprotocols
dial_timeoutnumber/stringConnection timeout (ms or “5s”)
read_timeoutnumber/stringRead timeout
write_timeoutnumber/stringWrite timeout
compressionnumberCompression mode (see Constants)
compression_thresholdnumberMin size to compress (0-100MB)
read_limitnumberMax message size (0-128MB)
channel_capacitynumberReceive channel buffer (1-10000)

Timeout format: Numbers are milliseconds, strings use Go duration format (“5s”, “1m”).

client:send("Hello, Server!")
-- Send JSON
client:send(json.encode({
type = "subscribe",
channel = "orders"
}))
client:send(binary_data, websocket.BINARY)
ParameterTypeDescription
datastringMessage content
typenumberwebsocket.TEXT (1) or websocket.BINARY (2)

Yields until the message is sent.

Returns: boolean, error

client:ping()

Yields until the ping is sent.

Returns: boolean, error

The channel() method returns a channel for receiving messages. receive() is an alias for channel(). Works with channel.select for multiplexing.

local ch = client:channel()
local msg, ok = ch:receive()
if ok then
print("Type:", msg.type) -- "text" or "binary"
print("Data:", msg.data)
end
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
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
FieldTypeDescription
typestring"text" or "binary"
datastring?Message content (nil for unknown payload types)
-- 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")
ParameterTypeDescription
codenumberClose code (1000-4999), default 1000
reasonstringClose reason (optional)

Yields until the close frame is sent.

-- 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"
websocket.COMPRESSION.DISABLED -- 0 (no compression)
websocket.COMPRESSION.CONTEXT_TAKEOVER -- 1 (sliding window)
websocket.COMPRESSION.NO_CONTEXT -- 2 (per-message)
ConstantCodeDescription
NORMAL1000Normal closure
GOING_AWAY1001Server shutting down
PROTOCOL_ERROR1002Protocol error
UNSUPPORTED_DATA1003Unsupported data type
RESERVED1004Reserved
NO_STATUS1005No status received
ABNORMAL_CLOSURE1006Connection lost
INVALID_PAYLOAD1007Invalid frame payload
POLICY_VIOLATION1008Policy violation
MESSAGE_TOO_BIG1009Message too large
MANDATORY_EXTENSION1010Required extension not negotiated
INTERNAL_ERROR1011Server error
SERVICE_RESTART1012Server restarting
TRY_AGAIN_LATER1013Server overloaded
BAD_GATEWAY1014Gateway error
TLS_HANDSHAKE1015TLS handshake failure
client:close(websocket.CLOSE_CODES.NORMAL, "Done")
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
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()

WebSocket connections are subject to security policy evaluation.

ActionResourceDescription
websocket.connect-Allow/deny WebSocket connections
websocket.connect.urlURLAllow/deny connections to specific URLs

See Security Model for policy configuration.

ConditionKindRetryable
Connections disablederrors.PERMISSION_DENIEDno
URL not allowederrors.PERMISSION_DENIEDno
No contexterrors.INTERNALno
Connection failederrors.INTERNALyes
Invalid connection IDerrors.INTERNALno
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 for working with errors.