Aller au contenu

websocket: Support WebSocket pour le module nginx-module-lua

Installation

Si vous n'avez pas encore configuré l'abonnement au dépôt RPM, inscrivez-vous. Ensuite, vous pouvez procéder avec les étapes suivantes.

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

Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.

Ce document décrit lua-resty-websocket v0.13 publié le 11 février 2025.


Cette bibliothèque Lua implémente un serveur WebSocket et des bibliothèques client basées sur le module ngx_lua.

Cette bibliothèque Lua tire parti de l'API cosocket de ngx_lua, qui garantit un comportement 100 % non-bloquant.

Notez que seul le RFC 6455 est pris en charge. Les révisions de protocole antérieures telles que "hybi-10", "hybi-07" et "hybi-00" ne le sont pas et ne seront pas prises en compte.

Synopsis

    local server = require "resty.websocket.server"

    local wb, err = server:new{
        timeout = 5000,  -- en millisecondes
        max_payload_len = 65535,
    }
    if not wb then
        ngx.log(ngx.ERR, "échec de la création du 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, "échec de la réception d'un frame : ", err)
            return ngx.exit(444)
        end
    end

    if typ == "close" then
        -- pour typ "close", err contient le code d'état
        local code = err

        -- envoyer un frame de fermeture en retour :

        local bytes, err = wb:send_close(1000, "assez, assez !")
        if not bytes then
            ngx.log(ngx.ERR, "échec de l'envoi du frame de fermeture : ", err)
            return
        end
        ngx.log(ngx.INFO, "fermeture avec le code d'état ", code, " et message ", data)
        return
    end

    if typ == "ping" then
        -- envoyer un frame pong en retour :

        local bytes, err = wb:send_pong(data)
        if not bytes then
            ngx.log(ngx.ERR, "échec de l'envoi du frame : ", err)
            return
        end
    elseif typ == "pong" then
        -- simplement ignorer le frame pong entrant

    else
        ngx.log(ngx.INFO, "reçu un frame de type ", typ, " et payload ", data)
    end

    wb:set_timeout(1000)  -- changer le délai d'attente réseau à 1 seconde

    bytes, err = wb:send_text("Bonjour le monde")
    if not bytes then
        ngx.log(ngx.ERR, "échec de l'envoi d'un frame texte : ", err)
        return ngx.exit(444)
    end

    bytes, err = wb:send_binary("blah blah blah...")
    if not bytes then
        ngx.log(ngx.ERR, "échec de l'envoi d'un frame binaire : ", err)
        return ngx.exit(444)
    end

    local bytes, err = wb:send_close(1000, "assez, assez !")
    if not bytes then
        ngx.log(ngx.ERR, "échec de l'envoi du frame de fermeture : ", err)
        return
    end

Modules

resty.websocket.server

Pour charger ce module, faites simplement ceci

    local server = require "resty.websocket.server"

Méthodes

new

syntax: wb, err = server:new()

syntax: wb, err = server:new(opts)

Effectue le processus de handshake WebSocket côté serveur et retourne un objet serveur WebSocket.

En cas d'erreur, il retourne nil et une chaîne décrivant l'erreur.

Une table d'options facultative peut être spécifiée. Les options suivantes sont les suivantes :

  • max_payload_len

    Spécifie la longueur maximale de payload autorisée lors de l'envoi et de la réception des frames WebSocket. Par défaut, c'est 65535. * max_recv_len

    Spécifie la longueur maximale de payload autorisée lors de la réception des frames WebSocket. Par défaut, c'est la valeur de max_payload_len. * max_send_len

    Spécifie la longueur maximale de payload autorisée lors de l'envoi des frames WebSocket. Par défaut, c'est la valeur de max_payload_len. * send_masked

    Spécifie s'il faut envoyer des frames WebSocket masquées. Lorsqu'il est true, les frames masquées sont toujours envoyées. Par défaut, c'est false. * timeout

    Spécifie le seuil de délai d'attente réseau en millisecondes. Vous pouvez modifier ce paramètre plus tard via l'appel de méthode set_timeout. Notez que ce paramètre de délai d'attente n'affecte pas le processus d'envoi de l'en-tête de réponse HTTP pour le handshake WebSocket ; vous devez configurer la directive send_timeout en même temps.

set_timeout

syntax: wb:set_timeout(ms)

Définit le délai d'attente (en millisecondes) pour les opérations liées au réseau.

send_text

syntax: bytes, err = wb:send_text(text)

Envoie l'argument text comme un frame de données non fragmenté de type text. Retourne le nombre d'octets qui ont réellement été envoyés au niveau TCP.

En cas d'erreurs, retourne nil et une chaîne décrivant l'erreur.

send_binary

syntax: bytes, err = wb:send_binary(data)

Envoie l'argument data comme un frame de données non fragmenté de type binary. Retourne le nombre d'octets qui ont réellement été envoyés au niveau TCP.

En cas d'erreurs, retourne nil et une chaîne décrivant l'erreur.

send_ping

syntax: bytes, err = wb:send_ping()

syntax: bytes, err = wb:send_ping(msg)

Envoie un frame ping avec un message optionnel spécifié par l'argument msg. Retourne le nombre d'octets qui ont réellement été envoyés au niveau TCP.

En cas d'erreurs, retourne nil et une chaîne décrivant l'erreur.

Notez que cette méthode n'attend pas de frame pong de l'autre côté.

send_pong

syntax: bytes, err = wb:send_pong()

syntax: bytes, err = wb:send_pong(msg)

Envoie un frame pong avec un message optionnel spécifié par l'argument msg. Retourne le nombre d'octets qui ont réellement été envoyés au niveau TCP.

En cas d'erreurs, retourne nil et une chaîne décrivant l'erreur.

send_close

syntax: bytes, err = wb:send_close()

syntax: bytes, err = wb:send_close(code, msg)

Envoie un frame close avec un code d'état optionnel et un message.

En cas d'erreurs, retourne nil et une chaîne décrivant l'erreur.

Pour une liste de codes d'état valides, voir le document suivant :

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

Notez que cette méthode n'attend pas de frame close de l'autre côté.

send_frame

syntax: bytes, err = wb:send_frame(fin, opcode, payload)

Envoie un frame WebSocket brut en spécifiant le champ fin (valeur booléenne), l'opcode et le payload.

Pour une liste d'opcodes valides, voir

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

En cas d'erreurs, retourne nil et une chaîne décrivant l'erreur.

Pour contrôler la longueur maximale de payload autorisée, vous pouvez passer l'option max_payload_len au constructeur new.

Pour contrôler si des frames masquées doivent être envoyées, vous pouvez passer true à l'option send_masked dans la méthode constructeur new. Par défaut, des frames non masquées sont envoyées.

recv_frame

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

Reçoit un frame WebSocket depuis le réseau.

En cas d'erreur, retourne deux valeurs nil et une chaîne décrivant l'erreur.

La deuxième valeur de retour est toujours le type de frame, qui pourrait être l'un de continuation, text, binary, close, ping, pong, ou nil (pour les types inconnus).

Pour les frames close, retourne 3 valeurs : le message d'état supplémentaire (qui pourrait être une chaîne vide), la chaîne "close", et un nombre Lua pour le code d'état (le cas échéant). Pour les codes d'état de fermeture possibles, voir

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

Pour d'autres types de frames, retourne simplement le payload et le type.

Pour les frames fragmentées, la valeur de retour err est la chaîne Lua "again".

resty.websocket.client

Pour charger ce module, faites simplement ceci

    local client = require "resty.websocket.client"

Un exemple simple pour démontrer l'utilisation :

    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("échec de la connexion : " .. err)
        return
    end

    local data, typ, err = wb:recv_frame()
    if not data then
        ngx.say("échec de la réception du frame : ", err)
        return
    end

    ngx.say("reçu : ", data, " (", typ, "): ", err)

    local bytes, err = wb:send_text("copie : " .. data)
    if not bytes then
        ngx.say("échec de l'envoi du frame : ", err)
        return
    end

    local bytes, err = wb:send_close()
    if not bytes then
        ngx.say("échec de l'envoi du frame : ", err)
        return
    end

Méthodes

client:new

syntax: wb, err = client:new()

syntax: wb, err = client:new(opts)

Instancie un objet client WebSocket.

En cas d'erreur, il retourne nil et une chaîne décrivant l'erreur.

Une table d'options facultative peut être spécifiée. Les options suivantes sont les suivantes :

  • max_payload_len

    Spécifie la longueur maximale de payload autorisée lors de l'envoi et de la réception des frames WebSocket. Par défaut, c'est 65536. * max_recv_len

    Spécifie la longueur maximale de payload autorisée lors de la réception des frames WebSocket. Par défaut, c'est la valeur de max_payload_len. * max_send_len

    Spécifie la longueur maximale de payload autorisée lors de l'envoi des frames WebSocket. Par défaut, c'est la valeur de max_payload_len. * send_unmasked

    Spécifie s'il faut envoyer des frames WebSocket non masquées. Lorsqu'il est true, les frames non masquées sont toujours envoyées. Par défaut, c'est false. Le RFC 6455 exige cependant que le client DOIT envoyer des frames masquées au serveur, donc ne définissez jamais cette option sur true à moins de savoir ce que vous faites. * timeout

    Spécifie le seuil de délai d'attente réseau par défaut en millisecondes. Vous pouvez modifier ce paramètre plus tard via l'appel de méthode 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)

Se connecte au port de service WebSocket distant et effectue le processus de handshake WebSocket côté client.

Avant de résoudre réellement le nom d'hôte et de se connecter au backend distant, cette méthode recherchera toujours dans le pool de connexions les connexions inactives correspondantes créées par des appels précédents de cette méthode.

La troisième valeur de retour de cette méthode contient la réponse brute en texte clair (ligne d'état et en-têtes) à la demande de handshake. Cela permet à l'appelant d'effectuer une validation supplémentaire et/ou d'extraire les en-têtes de réponse. Lorsque la connexion est réutilisée et qu'aucune demande de handshake n'est envoyée, la chaîne "connection reused" est retournée à la place de la réponse.

Une table Lua optionnelle peut être spécifiée comme dernier argument à cette méthode pour spécifier diverses options de connexion :

  • protocols

    Spécifie tous les sous-protocoles utilisés pour la session WebSocket actuelle. Cela pourrait être une table Lua contenant tous les noms de sous-protocoles ou simplement une seule chaîne Lua. * origin

    Spécifie la valeur de l'en-tête de requête Origin. * pool

    Spécifie un nom personnalisé pour le pool de connexions utilisé. Si omis, le nom du pool de connexions sera généré à partir du modèle de chaîne <host>:<port>. * pool_size

spécifie la taille du pool de connexions. Si omis et qu'aucune option backlog n'a été fournie, aucun pool ne sera créé. Si omis mais que backlog a été fourni, le pool sera créé avec une taille par défaut égale à la valeur de la directive lua_socket_pool_size. Le pool de connexions contient jusqu'à pool_size connexions vivantes prêtes à être réutilisées par des appels ultérieurs à connect, mais notez qu'il n'y a pas de limite supérieure au nombre total de connexions ouvertes en dehors du pool. Si vous devez restreindre le nombre total de connexions ouvertes, spécifiez l'option backlog. Lorsque le pool de connexions dépasserait sa limite de taille, la connexion la moins récemment utilisée (maintenue en vie) déjà dans le pool sera fermée pour faire de la place pour la connexion actuelle. Notez que le pool de connexions cosocket est par processus de travail Nginx plutôt que par instance de serveur Nginx, donc la limite de taille spécifiée ici s'applique également à chaque processus de travail Nginx. Notez également que la taille du pool de connexions ne peut pas être modifiée une fois qu'il a été créé. Cette option a été introduite pour la première fois dans la version v0.10.14.

  • backlog

si spécifié, ce module limitera le nombre total de connexions ouvertes pour ce pool. Pas plus de connexions que pool_size peuvent être ouvertes pour ce pool à tout moment. Si le pool de connexions est plein, les opérations de connexion ultérieures seront mises en file d'attente dans une file d'attente égale à la valeur de cette option (la file d'attente "backlog"). Si le nombre d'opérations de connexion mises en file d'attente est égal à backlog, les opérations de connexion ultérieures échoueront et retourneront nil plus la chaîne d'erreur "too many waiting connect operations". Les opérations de connexion mises en file d'attente seront reprises une fois que le nombre de connexions dans le pool sera inférieur à pool_size. L'opération de connexion mise en file d'attente sera abandonnée une fois qu'elle aura été mise en file d'attente pendant plus de connect_timeout, contrôlé par settimeouts, et retournera nil plus la chaîne d'erreur "timeout". Cette option a été introduite pour la première fois dans la version v0.10.14. * ssl_verify

Spécifie s'il faut effectuer une vérification du certificat SSL pendant le handshake SSL si le schéma `wss://` est utilisé.
  • headers

    Spécifie des en-têtes personnalisés à envoyer dans la demande de handshake. La table est censée contenir des chaînes au format {"a-header: une valeur d'en-tête", "another-header: une autre valeur d'en-tête"}.

  • client_cert

    Spécifie un objet cdata de chaîne de certificat client qui sera utilisé lors du handshake TLS avec le serveur distant. Ces objets peuvent être créés en utilisant la fonction ngx.ssl.parse_pem_cert fournie par lua-resty-core. Notez que spécifier l'option client_cert nécessite que la clé privée correspondante client_priv_key soit également fournie. Voir ci-dessous.

  • client_priv_key

    Spécifie une clé privée correspondant à l'option client_cert ci-dessus. Ces objets peuvent être créés en utilisant la fonction ngx.ssl.parse_pem_priv_key fournie par lua-resty-core.

  • host

    Spécifie la valeur de l'en-tête Host envoyé dans la demande de handshake. Si non fourni, l'en-tête Host sera dérivé du nom d'hôte/adresse et du port dans l'URI de connexion.

  • server_name

    Spécifie le nom du serveur (SNI) à utiliser lors de l'exécution du handshake TLS avec le serveur. Si non fourni, la valeur host ou <host/addr>:<port> de l'URI de connexion sera utilisée.

  • key

    Spécifie la valeur de l'en-tête Sec-WebSocket-Key dans la demande de handshake. La valeur doit être une chaîne de 16 octets encodée en base64 conforme aux exigences de handshake client du WebSocket RFC. Si non fourni, une clé est générée aléatoirement.

Le mode de connexion SSL (wss://) nécessite au moins ngx_lua 0.9.11 ou OpenResty 1.7.4.1.

client:close

syntax: ok, err = wb:close()

Ferme la connexion WebSocket actuelle. Si aucun frame close n'a encore été envoyé, alors le frame close sera automatiquement envoyé.

client:set_keepalive

syntax: ok, err = wb:set_keepalive(max_idle_timeout, pool_size)

Met immédiatement la connexion WebSocket actuelle dans le pool de connexions cosocket ngx_lua.

Vous pouvez spécifier le délai d'attente maximal inactif (en ms) lorsque la connexion est dans le pool et la taille maximale du pool pour chaque processus de travail nginx.

En cas de succès, retourne 1. En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

N'appelez cette méthode qu'à l'endroit où vous auriez appelé la méthode close à la place. Appeler cette méthode transformera immédiatement l'objet WebSocket actuel en état closed. Toute opération ultérieure autre que connect() sur l'objet actuel retournera l'erreur closed.

client:set_timeout

syntax: wb:set_timeout(ms)

Identique à la méthode set_timeout des objets resty.websocket.server.

client:send_text

syntax: bytes, err = wb:send_text(text)

Identique à la méthode send_text des objets resty.websocket.server.

client:send_binary

syntax: bytes, err = wb:send_binary(data)

Identique à la méthode send_binary des objets resty.websocket.server.

client:send_ping

syntax: bytes, err = wb:send_ping()

syntax: bytes, err = wb:send_ping(msg)

Identique à la méthode send_ping des objets resty.websocket.server.

client:send_pong

syntax: bytes, err = wb:send_pong()

syntax: bytes, err = wb:send_pong(msg)

Identique à la méthode send_pong des objets resty.websocket.server.

client:send_close

syntax: bytes, err = wb:send_close()

syntax: bytes, err = wb:send_close(code, msg)

Identique à la méthode send_close des objets resty.websocket.server.

client:send_frame

syntax: bytes, err = wb:send_frame(fin, opcode, payload)

Identique à la méthode send_frame des objets resty.websocket.server.

Pour contrôler si des frames non masquées doivent être envoyées, vous pouvez passer true à l'option send_unmasked dans la méthode constructeur new. Par défaut, des frames masquées sont envoyées.

client:recv_frame

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

Identique à la méthode recv_frame des objets resty.websocket.server.

resty.websocket.protocol

Pour charger ce module, faites simplement ceci

    local protocol = require "resty.websocket.protocol"

Méthodes

protocol.recv_frame

syntax: data, typ, err = protocol.recv_frame(socket, max_payload_len, force_masking)

Reçoit un frame WebSocket depuis le réseau.

protocol.build_frame

syntax: frame = protocol.build_frame(fin, opcode, payload_len, payload, masking)

Construit un frame WebSocket brut.

protocol.send_frame

syntax: bytes, err = protocol.send_frame(socket, fin, opcode, payload, max_payload_len, masking)

Envoie un frame WebSocket brut.

Journalisation automatique des erreurs

Par défaut, le module sous-jacent ngx_lua effectue une journalisation des erreurs lorsque des erreurs de socket se produisent. Si vous gérez déjà correctement les erreurs dans votre propre code Lua, il est recommandé de désactiver cette journalisation automatique des erreurs en désactivant la directive lua_socket_log_errors de ngx_lua, c'est-à-dire,

    lua_socket_log_errors off;

Limitations

  • Cette bibliothèque ne peut pas être utilisée dans des contextes de code tels que init_by_lua, set_by_lua, log_by_lua, et header_filter_by_lua où l'API cosocket ngx_lua n'est pas disponible.
  • L'instance d'objet resty.websocket ne peut pas être stockée dans une variable Lua au niveau du module Lua, car elle sera alors partagée par toutes les requêtes concurrentes gérées par le même processus de travail nginx (voir http://wiki.nginx.org/HttpLuaModule#Data_Sharing_within_an_Nginx_Worker) et entraînera de mauvaises conditions de concurrence lorsque des requêtes concurrentes essaient d'utiliser la même instance resty.websocket. Vous devez toujours initier des objets resty.websocket dans des variables locales de fonction ou dans la table ngx.ctx. Ces endroits ont tous leurs propres copies de données pour chaque requête.

Voir aussi

GitHub

Vous pouvez trouver des conseils de configuration supplémentaires et de la documentation pour ce module dans le dépôt GitHub pour nginx-module-websocket.