websocket: Suporte a WebSocket para o módulo nginx-module-lua
Instalação
Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Depois, você pode prosseguir com os seguintes passos.
CentOS/RHEL 7 ou 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
Para usar esta biblioteca Lua com o NGINX, certifique-se de que o nginx-module-lua está instalado.
Este documento descreve lua-resty-websocket v0.13 lançado em 11 de fevereiro de 2025.
Esta biblioteca Lua implementa um servidor WebSocket e bibliotecas de cliente baseadas no módulo ngx_lua.
Esta biblioteca Lua aproveita a API de cosocket do ngx_lua, que garante um comportamento 100% não bloqueante.
Observe que apenas RFC 6455 é suportado. Revisões anteriores do protocolo, como "hybi-10", "hybi-07" e "hybi-00", não são e não serão consideradas.
Sinopse
local server = require "resty.websocket.server"
local wb, err = server:new{
timeout = 5000, -- em milissegundos
max_payload_len = 65535,
}
if not wb then
ngx.log(ngx.ERR, "falha ao criar 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, "falha ao receber um frame: ", err)
return ngx.exit(444)
end
end
if typ == "close" then
-- para o tipo "close", err contém o código de status
local code = err
-- envia um frame de fechamento de volta:
local bytes, err = wb:send_close(1000, "suficiente, suficiente!")
if not bytes then
ngx.log(ngx.ERR, "falha ao enviar o frame de fechamento: ", err)
return
end
ngx.log(ngx.INFO, "fechando com código de status ", code, " e mensagem ", data)
return
end
if typ == "ping" then
-- envia um frame de pong de volta:
local bytes, err = wb:send_pong(data)
if not bytes then
ngx.log(ngx.ERR, "falha ao enviar frame: ", err)
return
end
elseif typ == "pong" then
-- apenas descarta o frame de pong recebido
else
ngx.log(ngx.INFO, "recebido um frame do tipo ", typ, " e payload ", data)
end
wb:set_timeout(1000) -- altera o timeout de rede para 1 segundo
bytes, err = wb:send_text("Olá mundo")
if not bytes then
ngx.log(ngx.ERR, "falha ao enviar um frame de texto: ", err)
return ngx.exit(444)
end
bytes, err = wb:send_binary("blah blah blah...")
if not bytes then
ngx.log(ngx.ERR, "falha ao enviar um frame binário: ", err)
return ngx.exit(444)
end
local bytes, err = wb:send_close(1000, "suficiente, suficiente!")
if not bytes then
ngx.log(ngx.ERR, "falha ao enviar o frame de fechamento: ", err)
return
end
Módulos
resty.websocket.server
Para carregar este módulo, basta fazer isso
local server = require "resty.websocket.server"
Métodos
new
syntax: wb, err = server:new()
syntax: wb, err = server:new(opts)
Realiza o processo de handshake do websocket no lado do servidor e retorna um objeto servidor WebSocket.
Em caso de erro, retorna nil e uma string descrevendo o erro.
Uma tabela de opções opcional pode ser especificada. As seguintes opções são as seguintes:
-
max_payload_lenEspecifica o comprimento máximo do payload permitido ao enviar e receber frames WebSocket. O padrão é
65535. *max_recv_lenEspecifica o comprimento máximo do payload permitido ao receber frames WebSocket. O padrão é o valor de
max_payload_len. *max_send_lenEspecifica o comprimento máximo do payload permitido ao enviar frames WebSocket. O padrão é o valor de
max_payload_len. *send_maskedEspecifica se deve enviar frames WebSocket mascarados. Quando é
true, frames mascarados são sempre enviados. O padrão éfalse. *timeoutEspecifica o limite de timeout de rede em milissegundos. Você pode alterar essa configuração mais tarde através da chamada do método
set_timeout. Observe que essa configuração de timeout não afeta o processo de envio do cabeçalho de resposta HTTP para o handshake do websocket; você precisa configurar a diretiva send_timeout ao mesmo tempo.
set_timeout
syntax: wb:set_timeout(ms)
Define o atraso de timeout (em milissegundos) para as operações relacionadas à rede.
send_text
syntax: bytes, err = wb:send_text(text)
Envia o argumento text como um frame de dados não fragmentado do tipo text. Retorna o número de bytes que foram realmente enviados no nível TCP.
Em caso de erros, retorna nil e uma string descrevendo o erro.
send_binary
syntax: bytes, err = wb:send_binary(data)
Envia o argumento data como um frame de dados não fragmentado do tipo binary. Retorna o número de bytes que foram realmente enviados no nível TCP.
Em caso de erros, retorna nil e uma string descrevendo o erro.
send_ping
syntax: bytes, err = wb:send_ping()
syntax: bytes, err = wb:send_ping(msg)
Envia um frame de ping com uma mensagem opcional especificada pelo argumento msg. Retorna o número de bytes que foram realmente enviados no nível TCP.
Em caso de erros, retorna nil e uma string descrevendo o erro.
Observe que este método não espera por um frame de pong do lado remoto.
send_pong
syntax: bytes, err = wb:send_pong()
syntax: bytes, err = wb:send_pong(msg)
Envia um frame de pong com uma mensagem opcional especificada pelo argumento msg. Retorna o número de bytes que foram realmente enviados no nível TCP.
Em caso de erros, retorna nil e uma string descrevendo o erro.
send_close
syntax: bytes, err = wb:send_close()
syntax: bytes, err = wb:send_close(code, msg)
Envia um frame de close com um código de status opcional e uma mensagem.
Em caso de erros, retorna nil e uma string descrevendo o erro.
Para uma lista de códigos de status válidos, consulte o seguinte documento:
http://tools.ietf.org/html/rfc6455#section-7.4.1
Observe que este método não espera por um frame de close do lado remoto.
send_frame
syntax: bytes, err = wb:send_frame(fin, opcode, payload)
Envia um frame WebSocket bruto especificando o campo fin (valor booleano), o opcode e o payload.
Para uma lista de opcodes válidos, consulte
http://tools.ietf.org/html/rfc6455#section-5.2
Em caso de erros, retorna nil e uma string descrevendo o erro.
Para controlar o comprimento máximo do payload permitido, você pode passar a opção max_payload_len para o construtor new.
Para controlar se deve enviar frames mascarados, você pode passar true para a opção send_masked no método construtor new. Por padrão, frames não mascarados são enviados.
recv_frame
syntax: data, typ, err = wb:recv_frame()
Recebe um frame WebSocket da rede.
Em caso de erro, retorna dois valores nil e uma string descrevendo o erro.
O segundo valor de retorno é sempre o tipo do frame, que pode ser um dos continuation, text, binary, close, ping, pong, ou nil (para tipos desconhecidos).
Para frames close, retorna 3 valores: a mensagem de status extra (que pode ser uma string vazia), a string "close", e um número Lua para o código de status (se houver). Para possíveis códigos de status de fechamento, consulte
http://tools.ietf.org/html/rfc6455#section-7.4.1
Para outros tipos de frames, apenas retorna o payload e o tipo.
Para frames fragmentados, o valor de retorno err é a string Lua "again".
resty.websocket.client
Para carregar este módulo, basta fazer isso
local client = require "resty.websocket.client"
Um exemplo simples para demonstrar o uso:
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("falha ao conectar: " .. err)
return
end
local data, typ, err = wb:recv_frame()
if not data then
ngx.say("falha ao receber o frame: ", err)
return
end
ngx.say("recebido: ", data, " (", typ, "): ", err)
local bytes, err = wb:send_text("cópia: " .. data)
if not bytes then
ngx.say("falha ao enviar frame: ", err)
return
end
local bytes, err = wb:send_close()
if not bytes then
ngx.say("falha ao enviar frame: ", err)
return
end
Métodos
client:new
syntax: wb, err = client:new()
syntax: wb, err = client:new(opts)
Instancia um objeto cliente WebSocket.
Em caso de erro, retorna nil e uma string descrevendo o erro.
Uma tabela de opções opcional pode ser especificada. As seguintes opções são as seguintes:
-
max_payload_lenEspecifica o comprimento máximo do payload permitido ao enviar e receber frames WebSocket. O padrão é
65536. *max_recv_lenEspecifica o comprimento máximo do payload permitido ao receber frames WebSocket. O padrão é o valor de
max_payload_len. *max_send_lenEspecifica o comprimento máximo do payload permitido ao enviar frames WebSocket. O padrão é o valor de
max_payload_len. *send_unmaskedEspecifica se deve enviar frames WebSocket não mascarados. Quando é
true, frames não mascarados são sempre enviados. O padrão éfalse. A RFC 6455 exige, no entanto, que o cliente DEVE enviar frames mascarados para o servidor, portanto, nunca defina esta opção comotruea menos que você saiba o que está fazendo. *timeoutEspecifica o limite de timeout de rede padrão em milissegundos. Você pode alterar essa configuração mais tarde através da chamada do método
set_timeout.
client:connect
syntax: ok, err, res = wb:connect("ws://<host>:<port>/<path>")
syntax: ok, err, res = wb:connect("wss://<host>:<port>/<path>")
syntax: ok, err, res = wb:connect("ws://<host>:<port>/<path>", options)
syntax: ok, err, res = wb:connect("wss://<host>:<port>/<path>", options)
Conecta-se à porta do serviço WebSocket remoto e realiza o processo de handshake do websocket no lado do cliente.
Antes de realmente resolver o nome do host e conectar-se ao backend remoto, este método sempre procurará no pool de conexões por conexões ociosas correspondentes criadas por chamadas anteriores deste método.
O terceiro valor de retorno deste método contém a resposta bruta em texto simples (linha de status e cabeçalhos) para a solicitação de handshake. Isso permite que o chamador realize validações adicionais e/ou extraia os cabeçalhos de resposta. Quando a conexão é reutilizada e nenhuma solicitação de handshake é enviada, a string "connection reused" é retornada em vez da resposta.
Uma tabela Lua opcional pode ser especificada como o último argumento para este método para especificar várias opções de conexão:
-
protocolsEspecifica todos os subprotocolos usados para a sessão WebSocket atual. Pode ser uma tabela Lua contendo todos os nomes de subprotocolos ou apenas uma única string Lua. *
originEspecifica o valor do cabeçalho de solicitação
Origin. *poolEspecifica um nome personalizado para o pool de conexões sendo usado. Se omitido, o nome do pool de conexões será gerado a partir do template de string
<host>:<port>. *pool_size
especifica o tamanho do pool de conexões. Se omitido e nenhuma
opção backlog foi fornecida, nenhum pool será criado. Se omitido
mas backlog foi fornecido, o pool será criado com um tamanho padrão
igual ao valor da diretiva lua_socket_pool_size.
O pool de conexões mantém até pool_size conexões ativas
prontas para serem reutilizadas por chamadas subsequentes a connect, mas
observe que não há limite superior para o número total de conexões abertas
fora do pool. Se você precisar restringir o número total de conexões abertas,
especifique a opção backlog.
Quando o pool de conexões exceder seu limite de tamanho, a conexão menos recentemente usada
(mantida ativa) já no pool será fechada para abrir espaço para
a conexão atual.
Observe que o pool de conexões de cosocket é por processo de trabalho do Nginx, em vez de por instância do servidor Nginx, portanto, o limite de tamanho especificado aqui também se aplica
a cada processo de trabalho do Nginx. Também observe que o tamanho do pool de conexões não pode ser alterado uma vez que foi criado.
Esta opção foi introduzida pela primeira vez na versão v0.10.14.
backlog
se especificado, este módulo limitará o número total de conexões abertas
para este pool. Não mais conexões do que pool_size podem ser abertas
para este pool a qualquer momento. Se o pool de conexões estiver cheio, operações de
conexão subsequentes serão enfileiradas em uma fila igual ao valor desta opção
(a fila "backlog").
Se o número de operações de conexão enfileiradas for igual a backlog,
operações de conexão subsequentes falharão e retornarão nil mais a
string de erro "too many waiting connect operations".
As operações de conexão enfileiradas serão retomadas uma vez que o número de conexões
no pool seja menor que pool_size.
A operação de conexão enfileirada abortará uma vez que tenha sido enfileirada por mais
de connect_timeout, controlado por
settimeouts, e retornará nil mais
a string de erro "timeout".
Esta opção foi introduzida pela primeira vez na versão v0.10.14.
* ssl_verify
Especifica se deve realizar a verificação do certificado SSL durante o
handshake SSL se o esquema wss:// for usado.
-
headersEspecifica cabeçalhos personalizados a serem enviados na solicitação de handshake. A tabela deve conter strings no formato
{"a-header: a header value", "another-header: another header value"}. -
client_certEspecifica um objeto cdata de cadeia de certificado do cliente que será usado durante o handshake TLS com o servidor remoto. Esses objetos podem ser criados usando ngx.ssl.parse_pem_cert função fornecida pelo lua-resty-core. Observe que especificar a opção
client_certrequer que a correspondenteclient_priv_keytambém seja fornecida. Veja abaixo. -
client_priv_keyEspecifica uma chave privada correspondente à opção
client_certacima. Esses objetos podem ser criados usando ngx.ssl.parse_pem_priv_key função fornecida pelo lua-resty-core. -
hostEspecifica o valor do cabeçalho
Hostenviado na solicitação de handshake. Se não fornecido, o cabeçalhoHostserá derivado do nome do host/endereço e da porta na URI de conexão. -
server_nameEspecifica o nome do servidor (SNI) a ser usado ao realizar o handshake TLS com o servidor. Se não fornecido, o valor
hostou o<host/addr>:<port>da URI de conexão será usado. -
keyEspecifica o valor do cabeçalho
Sec-WebSocket-Keyna solicitação de handshake. O valor deve ser uma string codificada em base64, de 16 bytes, conforme os requisitos de handshake do cliente da WebSocket RFC. Se não fornecido, uma chave é gerada aleatoriamente.
O modo de conexão SSL (wss://) requer pelo menos ngx_lua 0.9.11 ou OpenResty 1.7.4.1.
client:close
syntax: ok, err = wb:close()
Fecha a conexão WebSocket atual. Se nenhum frame close foi enviado ainda, o frame close será enviado automaticamente.
client:set_keepalive
syntax: ok, err = wb:set_keepalive(max_idle_timeout, pool_size)
Coloca a conexão WebSocket atual imediatamente no pool de conexões cosocket do ngx_lua.
Você pode especificar o timeout máximo ocioso (em ms) quando a conexão está no pool e o tamanho máximo do pool para cada processo de trabalho do nginx.
Em caso de sucesso, retorna 1. Em caso de erros, retorna nil com uma string descrevendo o erro.
Chame este método apenas no lugar onde você teria chamado o método close. Chamar este método imediatamente transformará o objeto WebSocket atual no estado closed. Quaisquer operações subsequentes, além de connect(), no objeto atual retornarão o erro closed.
client:set_timeout
syntax: wb:set_timeout(ms)
Idêntico ao método set_timeout dos objetos resty.websocket.server.
client:send_text
syntax: bytes, err = wb:send_text(text)
Idêntico ao método send_text dos objetos resty.websocket.server.
client:send_binary
syntax: bytes, err = wb:send_binary(data)
Idêntico ao método send_binary dos objetos resty.websocket.server.
client:send_ping
syntax: bytes, err = wb:send_ping()
syntax: bytes, err = wb:send_ping(msg)
Idêntico ao método send_ping dos objetos resty.websocket.server.
client:send_pong
syntax: bytes, err = wb:send_pong()
syntax: bytes, err = wb:send_pong(msg)
Idêntico ao método send_pong dos objetos resty.websocket.server.
client:send_close
syntax: bytes, err = wb:send_close()
syntax: bytes, err = wb:send_close(code, msg)
Idêntico ao método send_close dos objetos resty.websocket.server.
client:send_frame
syntax: bytes, err = wb:send_frame(fin, opcode, payload)
Idêntico ao método send_frame dos objetos resty.websocket.server.
Para controlar se deve enviar frames não mascarados, você pode passar true para a opção send_unmasked no método construtor new. Por padrão, frames mascarados são enviados.
client:recv_frame
syntax: data, typ, err = wb:recv_frame()
Idêntico ao método recv_frame dos objetos resty.websocket.server.
resty.websocket.protocol
Para carregar este módulo, basta fazer isso
local protocol = require "resty.websocket.protocol"
Métodos
protocol.recv_frame
syntax: data, typ, err = protocol.recv_frame(socket, max_payload_len, force_masking)
Recebe um frame WebSocket da rede.
protocol.build_frame
syntax: frame = protocol.build_frame(fin, opcode, payload_len, payload, masking)
Constrói um frame WebSocket bruto.
protocol.send_frame
syntax: bytes, err = protocol.send_frame(socket, fin, opcode, payload, max_payload_len, masking)
Envia um frame WebSocket bruto.
Registro Automático de Erros
Por padrão, o módulo subjacente ngx_lua faz registro de erros quando ocorrem erros de socket. Se você já estiver fazendo o tratamento adequado de erros em seu próprio código Lua, é recomendável desativar esse registro automático de erros desativando a diretiva lua_socket_log_errors do ngx_lua, ou seja,
lua_socket_log_errors off;
Limitações
- Esta biblioteca não pode ser usada em contextos de código como init_by_lua, set_by_lua, log_by_lua, e header_filter_by_lua onde a API de cosocket do ngx_lua não está disponível.
- A instância do objeto
resty.websocketnão pode ser armazenada em uma variável Lua no nível do módulo Lua, porque então será compartilhada por todas as solicitações concorrentes tratadas pelo mesmo processo de trabalho do nginx (veja http://wiki.nginx.org/HttpLuaModule#Data_Sharing_within_an_Nginx_Worker) e resultará em condições de corrida ruins quando solicitações concorrentes tentarem usar a mesma instânciaresty.websocket. Você deve sempre iniciar objetosresty.websocketem variáveis locais de função ou na tabelangx.ctx. Esses lugares têm suas próprias cópias de dados para cada solicitação.
Veja Também
- Postagem no blog WebSockets com OpenResty por Aapo Talvensaari.
- o módulo ngx_lua: http://wiki.nginx.org/HttpLuaModule
- o protocolo websocket: http://tools.ietf.org/html/rfc6455
- a biblioteca lua-resty-upload
- a biblioteca lua-resty-redis
- a biblioteca lua-resty-memcached
- a biblioteca lua-resty-mysql
GitHub
Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-websocket.