WebSocket-клиент

WebSocket-клиент для двунаправленной связи с серверами в реальном времени.

Загрузка

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
})
Параметр Тип Описание
url string WebSocket URL (ws:// или wss://)
options table Опции подключения (опционально)

Возвращает: Client, error

Опции подключения

Опция Тип Описание
headers table HTTP-заголовки для рукопожатия
protocols table WebSocket-подпротоколы
dial_timeout number/string Таймаут подключения (мс или "5s")
read_timeout number/string Таймаут чтения
write_timeout number/string Таймаут записи
compression number Режим сжатия (см. Константы)
compression_threshold number Мин. размер для сжатия (0-100MB)
read_limit number Макс. размер сообщения (0-128MB)
channel_capacity number Буфер канала приёма (1-10000)

Формат таймаута: Числа в миллисекундах, строки в формате Go duration ("5s", "1m").

Отправка сообщений

Текстовые сообщения

local ok, err = client:send("Hello, Server!")
if err then
    return nil, err
end

-- Отправка JSON
client:send(json.encode({
    type = "subscribe",
    channel = "orders"
}))

Бинарные сообщения

client:send(binary_data, websocket.BINARY)
Параметр Тип Описание
data string Содержимое сообщения
type number websocket.TEXT (1) или websocket.BINARY (2)

Возвращает: boolean, error

Ping

client:ping()

Возвращает: boolean, error

Приём сообщений

Метод channel() возвращает канал для приёма сообщений. Работает с channel.select для мультиплексирования.

Базовый приём

local ch = client:channel()

local msg, ok = ch:receive()
if ok then
    print("Type:", msg.type)  -- "text" или "binary"
    print("Data:", msg.data)
end

Цикл сообщений

local ch = client:channel()

while true do
    local msg, ok = ch:receive()
    if not ok then
        break  -- Соединение закрыто
    end

    if msg.type == "text" then
        local data = json.decode(msg.data)
        handle_message(data)
    end
end

С select

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

Объект сообщения

Поле Тип Описание
type string "text" или "binary"
data string Содержимое сообщения

Закрытие соединения

-- Нормальное закрытие (код 1000)
client:close()

-- С кодом и причиной
client:close(websocket.CLOSE_CODES.NORMAL, "Session ended")

-- Закрытие с ошибкой
client:close(websocket.CLOSE_CODES.INTERNAL_ERROR, "Processing failed")
Параметр Тип Описание
code number Код закрытия (1000-4999), по умолчанию 1000
reason string Причина закрытия (опционально)

Возвращает: boolean, error

Константы

Типы сообщений

-- Числовые (для отправки)
websocket.TEXT    -- 1
websocket.BINARY  -- 2

-- Строковые (поле type полученного сообщения)
websocket.TYPE_TEXT    -- "text"
websocket.TYPE_BINARY  -- "binary"
websocket.TYPE_PING    -- "ping"
websocket.TYPE_PONG    -- "pong"
websocket.TYPE_CLOSE   -- "close"

Режимы сжатия

websocket.COMPRESSION.DISABLED         -- 0 (без сжатия)
websocket.COMPRESSION.CONTEXT_TAKEOVER -- 1 (скользящее окно)
websocket.COMPRESSION.NO_CONTEXT       -- 2 (по-сообщенийно)

Коды закрытия

Константа Код Описание
NORMAL 1000 Нормальное закрытие
GOING_AWAY 1001 Сервер завершает работу
PROTOCOL_ERROR 1002 Ошибка протокола
UNSUPPORTED_DATA 1003 Неподдерживаемый тип данных
NO_STATUS 1005 Статус не получен
ABNORMAL_CLOSURE 1006 Соединение потеряно
INVALID_PAYLOAD 1007 Некорректные данные фрейма
POLICY_VIOLATION 1008 Нарушение политики
MESSAGE_TOO_BIG 1009 Сообщение слишком большое
INTERNAL_ERROR 1011 Ошибка сервера
SERVICE_RESTART 1012 Сервер перезапускается
TRY_AGAIN_LATER 1013 Сервер перегружен
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

    -- Вход в комнату
    client:send(json.encode({
        type = "join",
        room = room_id
    }))

    -- Цикл сообщений
    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

Поток цен с keep-alive

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  -- Соединение закрыто
    else
        local price = json.decode(r.value.data)
        update_price(price.symbol, price.value)
    end
end

client:close()

Разрешения

WebSocket-соединения подчиняются вычислению политики безопасности.

Действия безопасности

Действие Ресурс Описание
websocket.connect - Разрешить/запретить WebSocket-соединения
websocket.connect.url URL Разрешить/запретить соединения с конкретными URL

См. Модель безопасности для настройки политик.

Ошибки

Условие Kind Повторяемо
Соединения отключены errors.PERMISSION_DENIED нет
URL не разрешён errors.PERMISSION_DENIED нет
Нет контекста errors.INTERNAL нет
Ошибка подключения errors.INTERNAL да
Некорректный ID соединения errors.INTERNAL нет
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

См. Обработка ошибок для работы с ошибками.