Перейти к содержанию

websocket: Поддержка WebSocket для модуля nginx-module-lua

Установка

Если вы еще не настроили подписку на репозиторий RPM, зарегистрируйтесь. Затем вы можете продолжить с следующими шагами.

CentOS/RHEL 7 или Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-websocket

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-websocket

Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.

Этот документ описывает lua-resty-websocket v0.13, выпущенный 11 февраля 2025 года.


Эта библиотека Lua реализует сервер и клиентские библиотеки WebSocket на основе модуля ngx_lua.

Эта библиотека Lua использует API cosocket модуля ngx_lua, который обеспечивает 100% неблокирующее поведение.

Обратите внимание, что поддерживается только RFC 6455. Более ранние версии протокола, такие как "hybi-10", "hybi-07" и "hybi-00", не поддерживаются и не будут рассматриваться.

Синопсис

    local server = require "resty.websocket.server"

    local wb, err = server:new{
        timeout = 5000,  -- в миллисекундах
        max_payload_len = 65535,
    }
    if not wb then
        ngx.log(ngx.ERR, "не удалось создать новый websocket: ", err)
        return ngx.exit(444)
    end

    local data, typ, err = wb:recv_frame()

    if not data then
        if not string.find(err, "timeout", 1, true) then
            ngx.log(ngx.ERR, "не удалось получить кадр: ", err)
            return ngx.exit(444)
        end
    end

    if typ == "close" then
        -- для типа "close", err содержит код состояния
        local code = err

        -- отправить кадр закрытия обратно:

        local bytes, err = wb:send_close(1000, "достаточно, достаточно!")
        if not bytes then
            ngx.log(ngx.ERR, "не удалось отправить кадр закрытия: ", err)
            return
        end
        ngx.log(ngx.INFO, "закрытие с кодом состояния ", code, " и сообщением ", data)
        return
    end

    if typ == "ping" then
        -- отправить кадр pong обратно:

        local bytes, err = wb:send_pong(data)
        if not bytes then
            ngx.log(ngx.ERR, "не удалось отправить кадр: ", err)
            return
        end
    elseif typ == "pong" then
        -- просто игнорировать входящий кадр pong

    else
        ngx.log(ngx.INFO, "получен кадр типа ", typ, " и полезная нагрузка ", data)
    end

    wb:set_timeout(1000)  -- изменить сетевой тайм-аут на 1 секунду

    bytes, err = wb:send_text("Привет, мир")
    if not bytes then
        ngx.log(ngx.ERR, "не удалось отправить текстовый кадр: ", err)
        return ngx.exit(444)
    end

    bytes, err = wb:send_binary("бла бла бла...")
    if not bytes then
        ngx.log(ngx.ERR, "не удалось отправить бинарный кадр: ", err)
        return ngx.exit(444)
    end

    local bytes, err = wb:send_close(1000, "достаточно, достаточно!")
    if not bytes then
        ngx.log(ngx.ERR, "не удалось отправить кадр закрытия: ", err)
        return
    end

Модули

resty.websocket.server

Чтобы загрузить этот модуль, просто выполните это

    local server = require "resty.websocket.server"

Методы

new

синтаксис: wb, err = server:new()

синтаксис: wb, err = server:new(opts)

Выполняет процесс рукопожатия WebSocket на стороне сервера и возвращает объект сервера WebSocket.

В случае ошибки возвращает nil и строку, описывающую ошибку.

Можно указать необязательную таблицу параметров. Следующие параметры следующие:

  • max_payload_len

    Указывает максимальную длину полезной нагрузки, разрешенную при отправке и получении кадров WebSocket. По умолчанию 65535. * max_recv_len

    Указывает максимальную длину полезной нагрузки, разрешенную при получении кадров WebSocket. По умолчанию значение max_payload_len. * max_send_len

    Указывает максимальную длину полезной нагрузки, разрешенную при отправке кадров WebSocket. По умолчанию значение max_payload_len. * send_masked

    Указывает, следует ли отправлять маскированные кадры WebSocket. Когда это true, маскированные кадры всегда отправляются. По умолчанию false. * timeout

    Указывает порог сетевого тайм-аута в миллисекундах. Вы можете изменить эту настройку позже с помощью вызова метода set_timeout. Обратите внимание, что эта настройка тайм-аута не влияет на процесс отправки заголовка HTTP-ответа для рукопожатия WebSocket; вам нужно одновременно настроить директиву send_timeout.

set_timeout

синтаксис: wb:set_timeout(ms)

Устанавливает задержку тайм-аута (в миллисекундах) для операций, связанных с сетью.

send_text

синтаксис: bytes, err = wb:send_text(text)

Отправляет аргумент text как неразделенный кадр данных типа text. Возвращает количество байтов, которые фактически были отправлены на уровне TCP.

В случае ошибок возвращает nil и строку, описывающую ошибку.

send_binary

синтаксис: bytes, err = wb:send_binary(data)

Отправляет аргумент data как неразделенный кадр данных типа binary. Возвращает количество байтов, которые фактически были отправлены на уровне TCP.

В случае ошибок возвращает nil и строку, описывающую ошибку.

send_ping

синтаксис: bytes, err = wb:send_ping()

синтаксис: bytes, err = wb:send_ping(msg)

Отправляет кадр ping с необязательным сообщением, указанным аргументом msg. Возвращает количество байтов, которые фактически были отправлены на уровне TCP.

В случае ошибок возвращает nil и строку, описывающую ошибку.

Обратите внимание, что этот метод не ждет кадр pong от удаленной стороны.

send_pong

синтаксис: bytes, err = wb:send_pong()

синтаксис: bytes, err = wb:send_pong(msg)

Отправляет кадр pong с необязательным сообщением, указанным аргументом msg. Возвращает количество байтов, которые фактически были отправлены на уровне TCP.

В случае ошибок возвращает nil и строку, описывающую ошибку.

send_close

синтаксис: bytes, err = wb:send_close()

синтаксис: bytes, err = wb:send_close(code, msg)

Отправляет кадр close с необязательным кодом состояния и сообщением.

В случае ошибок возвращает nil и строку, описывающую ошибку.

Для списка допустимых кодов состояния смотрите следующий документ:

http://tools.ietf.org/html/rfc6455#section-7.4.1

Обратите внимание, что этот метод не ждет кадр close от удаленной стороны.

send_frame

синтаксис: bytes, err = wb:send_frame(fin, opcode, payload)

Отправляет сырой кадр WebSocket, указывая поле fin (логическое значение), код операции и полезную нагрузку.

Для списка допустимых кодов операций смотрите

http://tools.ietf.org/html/rfc6455#section-5.2

В случае ошибок возвращает nil и строку, описывающую ошибку.

Чтобы контролировать максимальную длину полезной нагрузки, вы можете передать параметр max_payload_len в конструктор new.

Чтобы контролировать, следует ли отправлять маскированные кадры, вы можете передать true в параметр send_masked в методе конструктора new. По умолчанию отправляются немаскированные кадры.

recv_frame

синтаксис: data, typ, err = wb:recv_frame()

Получает кадр WebSocket из сети.

В случае ошибки возвращает два значения nil и строку, описывающую ошибку.

Второе возвращаемое значение всегда является типом кадра, который может быть одним из continuation, text, binary, close, ping, pong или nil (для неизвестных типов).

Для кадров close возвращает 3 значения: дополнительное сообщение состояния (которое может быть пустой строкой), строку "close" и число Lua для кода состояния (если есть). Для возможных кодов состояния закрытия смотрите

http://tools.ietf.org/html/rfc6455#section-7.4.1

Для других типов кадров просто возвращает полезную нагрузку и тип.

Для фрагментированных кадров значение err возвращает строку Lua "again".

resty.websocket.client

Чтобы загрузить этот модуль, просто выполните это

    local client = require "resty.websocket.client"

Простой пример, чтобы продемонстрировать использование:

    local client = require "resty.websocket.client"
    local wb, err = client:new()
    local uri = "ws://127.0.0.1:" .. ngx.var.server_port .. "/s"
    local ok, err, res = wb:connect(uri)
    if not ok then
        ngx.say("не удалось подключиться: " .. err)
        return
    end

    local data, typ, err = wb:recv_frame()
    if not data then
        ngx.say("не удалось получить кадр: ", err)
        return
    end

    ngx.say("получено: ", data, " (", typ, "): ", err)

    local bytes, err = wb:send_text("копия: " .. data)
    if not bytes then
        ngx.say("не удалось отправить кадр: ", err)
        return
    end

    local bytes, err = wb:send_close()
    if not bytes then
        ngx.say("не удалось отправить кадр: ", err)
        return
    end

Методы

client:new

синтаксис: wb, err = client:new()

синтаксис: wb, err = client:new(opts)

Создает объект клиента WebSocket.

В случае ошибки возвращает nil и строку, описывающую ошибку.

Можно указать необязательную таблицу параметров. Следующие параметры следующие:

  • max_payload_len

    Указывает максимальную длину полезной нагрузки, разрешенную при отправке и получении кадров WebSocket. По умолчанию 65536. * max_recv_len

    Указывает максимальную длину полезной нагрузки, разрешенную при получении кадров WebSocket. По умолчанию значение max_payload_len. * max_send_len

    Указывает максимальную длину полезной нагрузки, разрешенную при отправке кадров WebSocket. По умолчанию значение max_payload_len. * send_unmasked

    Указывает, следует ли отправлять немаскированные кадры WebSocket. Когда это true, немаскированные кадры всегда отправляются. По умолчанию false. Однако RFC 6455 требует, чтобы клиент ОБЯЗАТЕЛЬНО отправлял маскированные кадры на сервер, поэтому никогда не устанавливайте эту опцию в true, если вы не знаете, что делаете. * timeout

    Указывает порог сетевого тайм-аута по умолчанию в миллисекундах. Вы можете изменить эту настройку позже с помощью вызова метода set_timeout.

client:connect

синтаксис: ok, err, res = wb:connect("ws://<host>:<port>/<path>")

синтаксис: ok, err, res = wb:connect("wss://<host>:<port>/<path>")

синтаксис: ok, err, res = wb:connect("ws://<host>:<port>/<path>", options)

синтаксис: ok, err, res = wb:connect("wss://<host>:<port>/<path>", options)

Подключается к удаленному порту WebSocket и выполняет процесс рукопожатия WebSocket на стороне клиента.

Перед фактическим разрешением имени хоста и подключением к удаленному бэкенду этот метод всегда будет искать в пуле соединений соответствующие неактивные соединения, созданные предыдущими вызовами этого метода.

Третье возвращаемое значение этого метода содержит необработанный, простой текстовый ответ (строка состояния и заголовки) на запрос рукопожатия. Это позволяет вызывающему выполнять дополнительную проверку и/или извлекать заголовки ответа. Когда соединение повторно используется и запрос рукопожатия не отправляется, возвращается строка "connection reused" вместо ответа.

Необязательную таблицу Lua можно указать в качестве последнего аргумента этого метода для указания различных параметров подключения:

  • protocols

    Указывает все подпроtocolы, используемые для текущей сессии WebSocket. Это может быть таблица Lua, содержащая все имена подпроtocolов, или просто одна строка Lua. * origin

    Указывает значение заголовка запроса Origin. * pool

    Указывает пользовательское имя для используемого пула соединений. Если не указано, имя пула соединений будет сгенерировано из строкового шаблона <host>:<port>. * pool_size

указывает размер пула соединений. Если не указано и не была предоставлена опция backlog, пул не будет создан. Если не указано, но была предоставлена опция backlog, пул будет создан с размером по умолчанию, равным значению директивы lua_socket_pool_size. Пул соединений содержит до pool_size активных соединений, готовых к повторному использованию последующими вызовами к connect, но обратите внимание, что нет верхнего предела на общее количество открытых соединений вне пула. Если вам нужно ограничить общее количество открытых соединений, укажите опцию backlog. Когда пул соединений превышает свой лимит по размеру, будет закрыто наименее недавно использованное (поддерживаемое) соединение, уже находящееся в пуле, чтобы освободить место для текущего соединения. Обратите внимание, что пул соединений cosocket является специфичным для каждого рабочего процесса Nginx, а не для каждого экземпляра сервера Nginx, поэтому лимит размера, указанный здесь, также применяется к каждому отдельному рабочему процессу Nginx. Также обратите внимание, что размер пула соединений не может быть изменен после его создания. Эта опция была впервые введена в версии v0.10.14.

  • backlog

если указано, этот модуль ограничит общее количество открытых соединений для этого пула. Не может быть открыто больше соединений, чем pool_size, для этого пула в любое время. Если пул соединений полон, последующие операции подключения будут помещены в очередь в очередь, равную значению этой опции (очередь "backlog"). Если количество ожидающих операций подключения равно backlog, последующие операции подключения будут завершены с ошибкой и вернут nil плюс строку ошибки "слишком много ожидающих операций подключения". Ожидающие операции подключения будут возобновлены, как только количество соединений в пуле станет меньше pool_size. Ожидающая операция подключения будет прервана, как только она будет находиться в очереди более чем connect_timeout, контролируемым settimeouts, и вернет nil плюс строку ошибки "тайм-аут". Эта опция была впервые введена в версии v0.10.14. * ssl_verify

Указывает, следует ли выполнять проверку SSL-сертификата во время рукопожатия SSL, если используется схема `wss://`.
  • headers

    Указывает пользовательские заголовки, которые будут отправлены в запросе рукопожатия. Ожидается, что таблица будет содержать строки в формате {"a-header: a header value", "another-header: another header value"}.

  • client_cert

    Указывает цепочку сертификатов клиента в виде объекта cdata, который будет использоваться во время TLS-рукопожатия с удаленным сервером. Эти объекты могут быть созданы с помощью функции ngx.ssl.parse_pem_cert, предоставленной lua-resty-core. Обратите внимание, что указание опции client_cert требует также предоставления соответствующего client_priv_key. См. ниже.

  • client_priv_key

    Указывает закрытый ключ, соответствующий вышеуказанной опции client_cert. Эти объекты могут быть созданы с помощью функции ngx.ssl.parse_pem_priv_key, предоставленной lua-resty-core.

  • host

    Указывает значение заголовка Host, отправленного в запросе рукопожатия. Если не указано, заголовок Host будет выведен из имени хоста/адреса и порта в URI подключения.

  • server_name

    Указывает имя сервера (SNI), которое будет использоваться при выполнении TLS-рукопожатия с сервером. Если не указано, будет использовано значение host или <host/addr>:<port> из URI подключения.

  • key

    Указывает значение заголовка Sec-WebSocket-Key в запросе рукопожатия. Значение должно быть строкой длиной 16 байт, закодированной в base64, соответствующей требованиям рукопожатия клиента WebSocket RFC. Если не указано, ключ генерируется случайным образом.

Режим SSL-соединения (wss://) требует как минимум ngx_lua 0.9.11 или OpenResty 1.7.4.1.

client:close

синтаксис: ok, err = wb:close()

Закрывает текущее соединение WebSocket. Если кадр close еще не был отправлен, то кадр close будет автоматически отправлен.

client:set_keepalive

синтаксис: ok, err = wb:set_keepalive(max_idle_timeout, pool_size)

Сразу помещает текущее соединение WebSocket в пул соединений ngx_lua.

Вы можете указать максимальный тайм-аут простоя (в мс), когда соединение находится в пуле, и максимальный размер пула для каждого рабочего процесса nginx.

В случае успеха возвращает 1. В случае ошибок возвращает nil с строкой, описывающей ошибку.

Вызывайте этот метод только в том месте, где вы бы вызвали метод close. Вызов этого метода немедленно переведет текущий объект WebSocket в состояние closed. Любые последующие операции, кроме connect(), на текущем объекте вернут ошибку closed.

client:set_timeout

синтаксис: wb:set_timeout(ms)

Идентичен методу set_timeout объектов resty.websocket.server.

client:send_text

синтаксис: bytes, err = wb:send_text(text)

Идентичен методу send_text объектов resty.websocket.server.

client:send_binary

синтаксис: bytes, err = wb:send_binary(data)

Идентичен методу send_binary объектов resty.websocket.server.

client:send_ping

синтаксис: bytes, err = wb:send_ping()

синтаксис: bytes, err = wb:send_ping(msg)

Идентичен методу send_ping объектов resty.websocket.server.

client:send_pong

синтаксис: bytes, err = wb:send_pong()

синтаксис: bytes, err = wb:send_pong(msg)

Идентичен методу send_pong объектов resty.websocket.server.

client:send_close

синтаксис: bytes, err = wb:send_close()

синтаксис: bytes, err = wb:send_close(code, msg)

Идентичен методу send_close объектов resty.websocket.server.

client:send_frame

синтаксис: bytes, err = wb:send_frame(fin, opcode, payload)

Идентичен методу send_frame объектов resty.websocket.server.

Чтобы контролировать, следует ли отправлять немаскированные кадры, вы можете передать true в параметр send_unmasked в методе конструктора new. По умолчанию отправляются маскированные кадры.

client:recv_frame

синтаксис: data, typ, err = wb:recv_frame()

Идентичен методу recv_frame объектов resty.websocket.server.

resty.websocket.protocol

Чтобы загрузить этот модуль, просто выполните это

    local protocol = require "resty.websocket.protocol"

Методы

protocol.recv_frame

синтаксис: data, typ, err = protocol.recv_frame(socket, max_payload_len, force_masking)

Получает кадр WebSocket из сети.

protocol.build_frame

синтаксис: frame = protocol.build_frame(fin, opcode, payload_len, payload, masking)

Создает сырой кадр WebSocket.

protocol.send_frame

синтаксис: bytes, err = protocol.send_frame(socket, fin, opcode, payload, max_payload_len, masking)

Отправляет сырой кадр WebSocket.

Автоматическая регистрация ошибок

По умолчанию модуль ngx_lua выполняет регистрацию ошибок при возникновении ошибок сокета. Если вы уже выполняете правильную обработку ошибок в своем собственном коде Lua, рекомендуется отключить эту автоматическую регистрацию ошибок, отключив директиву lua_socket_log_errors модуля ngx_lua, а именно,

    lua_socket_log_errors off;

Ограничения

  • Эта библиотека не может использоваться в контекстах кода, таких как init_by_lua, set_by_lua, log_by_lua, и header_filter_by_lua, где API cosocket ngx_lua недоступен.
  • Экземпляр объекта resty.websocket не может храниться в переменной Lua на уровне модуля Lua, потому что он будет разделяться всеми конкурентными запросами, обрабатываемыми одним и тем же рабочим процессом nginx (см. http://wiki.nginx.org/HttpLuaModule#Data_Sharing_within_an_Nginx_Worker) и приведет к плохим условиям гонки, когда конкурентные запросы пытаются использовать один и тот же экземпляр resty.websocket. Вы всегда должны инициализировать объекты resty.websocket в локальных переменных функции или в таблице ngx.ctx. Эти места имеют свои собственные копии данных для каждого запроса.

См. также

GitHub

Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-websocket.