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. Эти места имеют свои собственные копии данных для каждого запроса.
См. также
- Пост в блоге WebSockets с OpenResty от Аапо Талвенсаари.
- модуль ngx_lua: http://wiki.nginx.org/HttpLuaModule
- протокол websocket: http://tools.ietf.org/html/rfc6455
- библиотека lua-resty-upload
- библиотека lua-resty-redis
- библиотека lua-resty-memcached
- библиотека lua-resty-mysql
GitHub
Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-websocket.