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_lenSpé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_lenSpé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_lenSpé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_maskedSpé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'estfalse. *timeoutSpé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_lenSpé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_lenSpé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_lenSpé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_unmaskedSpé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'estfalse. Le RFC 6455 exige cependant que le client DOIT envoyer des frames masquées au serveur, donc ne définissez jamais cette option surtrueà moins de savoir ce que vous faites. *timeoutSpé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 :
-
protocolsSpé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. *
originSpécifie la valeur de l'en-tête de requête
Origin. *poolSpé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é.
-
headersSpé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_certSpé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_certnécessite que la clé privée correspondanteclient_priv_keysoit également fournie. Voir ci-dessous. -
client_priv_keySpécifie une clé privée correspondant à l'option
client_certci-dessus. Ces objets peuvent être créés en utilisant la fonction ngx.ssl.parse_pem_priv_key fournie par lua-resty-core. -
hostSpécifie la valeur de l'en-tête
Hostenvoyé dans la demande de handshake. Si non fourni, l'en-têteHostsera dérivé du nom d'hôte/adresse et du port dans l'URI de connexion. -
server_nameSpécifie le nom du serveur (SNI) à utiliser lors de l'exécution du handshake TLS avec le serveur. Si non fourni, la valeur
hostou<host/addr>:<port>de l'URI de connexion sera utilisée. -
keySpécifie la valeur de l'en-tête
Sec-WebSocket-Keydans 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.websocketne 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 instanceresty.websocket. Vous devez toujours initier des objetsresty.websocketdans des variables locales de fonction ou dans la tablengx.ctx. Ces endroits ont tous leurs propres copies de données pour chaque requête.
Voir aussi
- Article de blog WebSockets avec OpenResty par Aapo Talvensaari.
- le module ngx_lua : http://wiki.nginx.org/HttpLuaModule
- le protocole websocket : http://tools.ietf.org/html/rfc6455
- la bibliothèque lua-resty-upload
- la bibliothèque lua-resty-redis
- la bibliothèque lua-resty-memcached
- la bibliothèque lua-resty-mysql
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.