Pular para conteúdo

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_len

    Especifica o comprimento máximo do payload permitido ao enviar e receber frames WebSocket. O padrão é 65535. * max_recv_len

    Especifica o comprimento máximo do payload permitido ao receber frames WebSocket. O padrão é o valor de max_payload_len. * max_send_len

    Especifica o comprimento máximo do payload permitido ao enviar frames WebSocket. O padrão é o valor de max_payload_len. * send_masked

    Especifica se deve enviar frames WebSocket mascarados. Quando é true, frames mascarados são sempre enviados. O padrão é false. * timeout

    Especifica 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_len

    Especifica o comprimento máximo do payload permitido ao enviar e receber frames WebSocket. O padrão é 65536. * max_recv_len

    Especifica o comprimento máximo do payload permitido ao receber frames WebSocket. O padrão é o valor de max_payload_len. * max_send_len

    Especifica o comprimento máximo do payload permitido ao enviar frames WebSocket. O padrão é o valor de max_payload_len. * send_unmasked

    Especifica 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 como true a menos que você saiba o que está fazendo. * timeout

    Especifica 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:

  • protocols

    Especifica 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. * origin

    Especifica o valor do cabeçalho de solicitação Origin. * pool

    Especifica 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.

  • headers

    Especifica 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_cert

    Especifica 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_cert requer que a correspondente client_priv_key também seja fornecida. Veja abaixo.

  • client_priv_key

    Especifica uma chave privada correspondente à opção client_cert acima. Esses objetos podem ser criados usando ngx.ssl.parse_pem_priv_key função fornecida pelo lua-resty-core.

  • host

    Especifica o valor do cabeçalho Host enviado na solicitação de handshake. Se não fornecido, o cabeçalho Host será derivado do nome do host/endereço e da porta na URI de conexão.

  • server_name

    Especifica o nome do servidor (SNI) a ser usado ao realizar o handshake TLS com o servidor. Se não fornecido, o valor host ou o <host/addr>:<port> da URI de conexão será usado.

  • key

    Especifica o valor do cabeçalho Sec-WebSocket-Key na 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.websocket nã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ância resty.websocket. Você deve sempre iniciar objetos resty.websocket em variáveis locais de função ou na tabela ngx.ctx. Esses lugares têm suas próprias cópias de dados para cada solicitação.

Veja Também

GitHub

Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-websocket.