websocket: WebSocket-Unterstützung für das nginx-module-lua Modul
Installation
Wenn Sie noch kein RPM-Repository-Abonnement eingerichtet haben, melden Sie sich an. Danach können Sie mit den folgenden Schritten fortfahren.
CentOS/RHEL 7 oder 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
Um diese Lua-Bibliothek mit NGINX zu verwenden, stellen Sie sicher, dass nginx-module-lua installiert ist.
Dieses Dokument beschreibt lua-resty-websocket v0.13, veröffentlicht am 11. Februar 2025.
Diese Lua-Bibliothek implementiert WebSocket-Server- und Client-Bibliotheken, die auf dem ngx_lua-Modul basieren.
Diese Lua-Bibliothek nutzt die Cosocket-API von ngx_lua, die ein 100% nicht blockierendes Verhalten gewährleistet.
Beachten Sie, dass nur RFC 6455 unterstützt wird. Frühere Protokollrevisionen wie "hybi-10", "hybi-07" und "hybi-00" werden nicht unterstützt und werden nicht berücksichtigt.
Synopsis
local server = require "resty.websocket.server"
local wb, err = server:new{
timeout = 5000, -- in Millisekunden
max_payload_len = 65535,
}
if not wb then
ngx.log(ngx.ERR, "Fehler beim Erstellen des Websockets: ", 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, "Fehler beim Empfangen eines Frames: ", err)
return ngx.exit(444)
end
end
if typ == "close" then
-- für typ "close", enthält err den Statuscode
local code = err
-- sendet einen Close-Frame zurück:
local bytes, err = wb:send_close(1000, "genug, genug!")
if not bytes then
ngx.log(ngx.ERR, "Fehler beim Senden des Close-Frames: ", err)
return
end
ngx.log(ngx.INFO, "Schließe mit Statuscode ", code, " und Nachricht ", data)
return
end
if typ == "ping" then
-- sendet einen Pong-Frame zurück:
local bytes, err = wb:send_pong(data)
if not bytes then
ngx.log(ngx.ERR, "Fehler beim Senden des Frames: ", err)
return
end
elseif typ == "pong" then
-- verwirft einfach den eingehenden Pong-Frame
else
ngx.log(ngx.INFO, "Empfangen eines Frames vom Typ ", typ, " und Payload ", data)
end
wb:set_timeout(1000) -- ändert den Netzwerk-Timeout auf 1 Sekunde
bytes, err = wb:send_text("Hallo Welt")
if not bytes then
ngx.log(ngx.ERR, "Fehler beim Senden eines Text-Frames: ", err)
return ngx.exit(444)
end
bytes, err = wb:send_binary("blah blah blah...")
if not bytes then
ngx.log(ngx.ERR, "Fehler beim Senden eines Binär-Frames: ", err)
return ngx.exit(444)
end
local bytes, err = wb:send_close(1000, "genug, genug!")
if not bytes then
ngx.log(ngx.ERR, "Fehler beim Senden des Close-Frames: ", err)
return
end
Module
resty.websocket.server
Um dieses Modul zu laden, tun Sie einfach Folgendes:
local server = require "resty.websocket.server"
Methoden
new
syntax: wb, err = server:new()
syntax: wb, err = server:new(opts)
Führt den WebSocket-Handshake-Prozess auf der Serverseite durch und gibt ein WebSocket-Serverobjekt zurück.
Im Falle eines Fehlers gibt es nil und eine Zeichenfolge zurück, die den Fehler beschreibt.
Eine optionale Optionen-Tabelle kann angegeben werden. Die folgenden Optionen sind wie folgt:
-
max_payload_lenGibt die maximale Länge der Payload an, die beim Senden und Empfangen von WebSocket-Frames erlaubt ist. Standardmäßig
65535. *max_recv_lenGibt die maximale Länge der Payload an, die beim Empfangen von WebSocket-Frames erlaubt ist. Standardmäßig der Wert von
max_payload_len. *max_send_lenGibt die maximale Länge der Payload an, die beim Senden von WebSocket-Frames erlaubt ist. Standardmäßig der Wert von
max_payload_len. *send_maskedGibt an, ob maskierte WebSocket-Frames gesendet werden sollen. Wenn es
trueist, werden maskierte Frames immer gesendet. Standardmäßigfalse. *timeoutGibt die Netzwerk-Timeout-Schwelle in Millisekunden an. Sie können diese Einstellung später über den
set_timeout-Methodenaufruf ändern. Beachten Sie, dass diese Timeout-Einstellung den HTTP-Antwortheader-Sendeprozess für den WebSocket-Handshake nicht beeinflusst; Sie müssen gleichzeitig die send_timeout Direktive konfigurieren.
set_timeout
syntax: wb:set_timeout(ms)
Setzt die Timeout-Verzögerung (in Millisekunden) für netzwerkbezogene Operationen.
send_text
syntax: bytes, err = wb:send_text(text)
Sendet das Argument text als unfragmentierten Daten-Frame vom Typ text. Gibt die Anzahl der Bytes zurück, die auf TCP-Ebene tatsächlich gesendet wurden.
Im Falle von Fehlern gibt es nil und eine Zeichenfolge zurück, die den Fehler beschreibt.
send_binary
syntax: bytes, err = wb:send_binary(data)
Sendet das Argument data als unfragmentierten Daten-Frame vom Typ binary. Gibt die Anzahl der Bytes zurück, die auf TCP-Ebene tatsächlich gesendet wurden.
Im Falle von Fehlern gibt es nil und eine Zeichenfolge zurück, die den Fehler beschreibt.
send_ping
syntax: bytes, err = wb:send_ping()
syntax: bytes, err = wb:send_ping(msg)
Sendet einen ping-Frame mit einer optionalen Nachricht, die durch das Argument msg angegeben wird. Gibt die Anzahl der Bytes zurück, die auf TCP-Ebene tatsächlich gesendet wurden.
Im Falle von Fehlern gibt es nil und eine Zeichenfolge zurück, die den Fehler beschreibt.
Beachten Sie, dass diese Methode nicht auf einen Pong-Frame vom Remote-Ende wartet.
send_pong
syntax: bytes, err = wb:send_pong()
syntax: bytes, err = wb:send_pong(msg)
Sendet einen pong-Frame mit einer optionalen Nachricht, die durch das Argument msg angegeben wird. Gibt die Anzahl der Bytes zurück, die auf TCP-Ebene tatsächlich gesendet wurden.
Im Falle von Fehlern gibt es nil und eine Zeichenfolge zurück, die den Fehler beschreibt.
send_close
syntax: bytes, err = wb:send_close()
syntax: bytes, err = wb:send_close(code, msg)
Sendet einen close-Frame mit einem optionalen Statuscode und einer Nachricht.
Im Falle von Fehlern gibt es nil und eine Zeichenfolge zurück, die den Fehler beschreibt.
Für eine Liste gültiger Statuscodes siehe das folgende Dokument:
http://tools.ietf.org/html/rfc6455#section-7.4.1
Beachten Sie, dass diese Methode nicht auf einen close-Frame vom Remote-Ende wartet.
send_frame
syntax: bytes, err = wb:send_frame(fin, opcode, payload)
Sendet einen rohen WebSocket-Frame, indem das fin-Feld (boolescher Wert), der Opcode und die Payload angegeben werden.
Für eine Liste gültiger Opcodes siehe
http://tools.ietf.org/html/rfc6455#section-5.2
Im Falle von Fehlern gibt es nil und eine Zeichenfolge zurück, die den Fehler beschreibt.
Um die maximale Payload-Länge zu steuern, die erlaubt ist, können Sie die Option max_payload_len an den new-Konstruktor übergeben.
Um zu steuern, ob maskierte Frames gesendet werden, können Sie true an die Option send_masked im new-Konstruktor übergeben. Standardmäßig werden unmaskierte Frames gesendet.
recv_frame
syntax: data, typ, err = wb:recv_frame()
Empfängt einen WebSocket-Frame vom Draht.
Im Falle eines Fehlers gibt es zwei nil-Werte und eine Zeichenfolge zurück, die den Fehler beschreibt.
Der zweite Rückgabewert ist immer der Frame-Typ, der einer von continuation, text, binary, close, ping, pong oder nil (für unbekannte Typen) sein könnte.
Für close-Frames gibt es 3 Werte zurück: die zusätzliche Statusnachricht (die eine leere Zeichenfolge sein könnte), die Zeichenfolge "close" und eine Lua-Zahl für den Statuscode (falls vorhanden). Für mögliche Schließstatuscodes siehe
http://tools.ietf.org/html/rfc6455#section-7.4.1
Für andere Arten von Frames gibt es einfach die Payload und den Typ zurück.
Für fragmentierte Frames ist der Rückgabewert err die Lua-Zeichenfolge "again".
resty.websocket.client
Um dieses Modul zu laden, tun Sie einfach Folgendes:
local client = require "resty.websocket.client"
Ein einfaches Beispiel zur Demonstration der Verwendung:
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("Fehler beim Verbinden: " .. err)
return
end
local data, typ, err = wb:recv_frame()
if not data then
ngx.say("Fehler beim Empfangen des Frames: ", err)
return
end
ngx.say("Empfangen: ", data, " (", typ, "): ", err)
local bytes, err = wb:send_text("Kopie: " .. data)
if not bytes then
ngx.say("Fehler beim Senden des Frames: ", err)
return
end
local bytes, err = wb:send_close()
if not bytes then
ngx.say("Fehler beim Senden des Frames: ", err)
return
end
Methoden
client:new
syntax: wb, err = client:new()
syntax: wb, err = client:new(opts)
Instanziiert ein WebSocket-Clientobjekt.
Im Falle eines Fehlers gibt es nil und eine Zeichenfolge zurück, die den Fehler beschreibt.
Eine optionale Optionen-Tabelle kann angegeben werden. Die folgenden Optionen sind wie folgt:
-
max_payload_lenGibt die maximale Länge der Payload an, die beim Senden und Empfangen von WebSocket-Frames erlaubt ist. Standardmäßig
65536. *max_recv_lenGibt die maximale Länge der Payload an, die beim Empfangen von WebSocket-Frames erlaubt ist. Standardmäßig der Wert von
max_payload_len. *max_send_lenGibt die maximale Länge der Payload an, die beim Senden von WebSocket-Frames erlaubt ist. Standardmäßig der Wert von
max_payload_len. *send_unmaskedGibt an, ob unmaskierte WebSocket-Frames gesendet werden sollen. Wenn es
trueist, werden unmaskierte Frames immer gesendet. Standardmäßigfalse. RFC 6455 verlangt jedoch, dass der Client maskierte Frames an den Server senden muss, daher sollten Sie diese Option niemals auftruesetzen, es sei denn, Sie wissen, was Sie tun. *timeoutGibt die standardmäßige Netzwerk-Timeout-Schwelle in Millisekunden an. Sie können diese Einstellung später über den
set_timeout-Methodenaufruf ändern.
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)
Stellt eine Verbindung zum Remote-WebSocket-Dienstport her und führt den WebSocket-Handshake-Prozess auf der Clientseite durch.
Bevor tatsächlich der Hostname aufgelöst und eine Verbindung zum Remote-Backend hergestellt wird, wird diese Methode immer den Verbindungs-Pool nach passenden inaktiven Verbindungen durchsuchen, die durch vorherige Aufrufe dieser Methode erstellt wurden.
Der dritte Rückgabewert dieser Methode enthält die rohe, im Klartext gehaltene Antwort (Statuszeile und Header) auf die Handshake-Anfrage. Dies ermöglicht dem Aufrufer, zusätzliche Validierungen durchzuführen und/oder die Antwort-Header zu extrahieren. Wenn die Verbindung wiederverwendet wird und keine Handshake-Anfrage gesendet wird, wird die Zeichenfolge "connection reused" anstelle der Antwort zurückgegeben.
Eine optionale Lua-Tabelle kann als letztes Argument an diese Methode übergeben werden, um verschiedene Verbindungsoptionen anzugeben:
-
protocolsGibt alle Subprotokolle an, die für die aktuelle WebSocket-Sitzung verwendet werden. Es könnte eine Lua-Tabelle sein, die alle Subprotokollnamen enthält, oder einfach eine einzelne Lua-Zeichenfolge. *
originGibt den Wert des
Origin-Anforderungsheaders an. *poolGibt einen benutzerdefinierten Namen für den verwendeten Verbindungs-Pool an. Wenn weggelassen, wird der Verbindungs-Pool-Name aus der Zeichenfolgenvorlage
<host>:<port>generiert. *pool_size
Gibt die Größe des Verbindungs-Pools an. Wenn weggelassen und keine
backlog-Option angegeben wurde, wird kein Pool erstellt. Wenn weggelassen
aber backlog angegeben wurde, wird der Pool mit einer Standardgröße erstellt,
die dem Wert der lua_socket_pool_size
Direktive entspricht.
Der Verbindungs-Pool hält bis zu pool_size aktive Verbindungen,
die bereit sind, von nachfolgenden Aufrufen von connect wiederverwendet zu werden, aber
beachten Sie, dass es keine obere Grenze für die Gesamtzahl der geöffneten Verbindungen
außerhalb des Pools gibt. Wenn Sie die Gesamtzahl der geöffneten
Verbindungen einschränken müssen, geben Sie die backlog-Option an.
Wenn der Verbindungs-Pool seine Größenbeschränkung überschreiten würde, wird die am wenigsten kürzlich verwendete
(aktiv gehaltene) Verbindung, die sich bereits im Pool befindet, geschlossen, um Platz für
die aktuelle Verbindung zu schaffen.
Beachten Sie, dass der Cosocket-Verbindungs-Pool pro Nginx-Arbeitsprozess
und nicht pro Nginx-Serverinstanz ist, sodass die hier angegebene Größenbeschränkung auch für
jeden einzelnen Nginx-Arbeitsprozess gilt. Beachten Sie auch, dass die Größe des Verbindungs-Pools
nicht geändert werden kann, sobald er erstellt wurde.
Diese Option wurde erstmals in der Version v0.10.14 eingeführt.
backlog
Wenn angegeben, wird dieses Modul die Gesamtzahl der geöffneten Verbindungen
für diesen Pool begrenzen. Es können zu jedem Zeitpunkt nicht mehr Verbindungen als pool_size
für diesen Pool geöffnet werden. Wenn der Verbindungs-Pool voll ist, werden nachfolgende
Verbindungsoperationen in eine Warteschlange eingereiht, die dem Wert dieser Option
(der "backlog"-Warteschlange) entspricht.
Wenn die Anzahl der eingereihten Verbindungsoperationen gleich backlog ist,
schlagen nachfolgende Verbindungsoperationen fehl und geben nil plus die
Fehlermeldung "too many waiting connect operations" zurück.
Die eingereihten Verbindungsoperationen werden fortgesetzt, sobald die Anzahl der Verbindungen
im Pool weniger als pool_size beträgt.
Die eingereihte Verbindungsoperation wird abgebrochen, sobald sie mehr als
connect_timeout eingereiht wurde, gesteuert durch
settimeouts, und gibt nil plus
die Fehlermeldung "timeout" zurück.
Diese Option wurde erstmals in der Version v0.10.14 eingeführt.
* ssl_verify
Gibt an, ob während des SSL-Handshakes eine SSL-Zertifikatsüberprüfung durchgeführt werden soll, wenn das `wss://`-Schema verwendet wird.
-
headersGibt benutzerdefinierte Header an, die in der Handshake-Anfrage gesendet werden sollen. Die Tabelle sollte Zeichenfolgen im Format
{"a-header: ein Headerwert", "another-header: ein anderer Headerwert"}enthalten. -
client_certGibt ein Client-Zertifikat-Ketten-cdata-Objekt an, das während des TLS-Handshakes mit dem Remote-Server verwendet wird. Diese Objekte können mit der ngx.ssl.parse_pem_cert Funktion erstellt werden, die von lua-resty-core bereitgestellt wird. Beachten Sie, dass die Angabe der
client_cert-Option auch die Bereitstellung des entsprechendenclient_priv_keyerfordert. Siehe unten. -
client_priv_keyGibt einen privaten Schlüssel an, der dem oben genannten
client_cert-Option entspricht. Diese Objekte können mit der ngx.ssl.parse_pem_priv_key Funktion erstellt werden, die von lua-resty-core bereitgestellt wird. -
hostGibt den Wert des
Host-Headers an, der in der Handshake-Anfrage gesendet wird. Wenn nicht angegeben, wird derHost-Header aus dem Hostnamen/Adresse und Port in der Verbindungs-URI abgeleitet. -
server_nameGibt den Servernamen (SNI) an, der beim Durchführen des TLS-Handshakes mit dem Server verwendet werden soll. Wenn nicht angegeben, wird der
host-Wert oder<host/addr>:<port>aus der Verbindungs-URI verwendet. -
keyGibt den Wert des
Sec-WebSocket-Key-Headers in der Handshake-Anfrage an. Der Wert sollte eine base64-kodierte, 16-Byte-Zeichenfolge sein, die den Anforderungen des Client-Handshakes des WebSocket RFC entspricht. Wenn nicht angegeben, wird ein Schlüssel zufällig generiert.
Der SSL-Verbindungsmodus (wss://) erfordert mindestens ngx_lua 0.9.11 oder OpenResty 1.7.4.1.
client:close
syntax: ok, err = wb:close()
Schließt die aktuelle WebSocket-Verbindung. Wenn noch kein close-Frame gesendet wurde, wird der close-Frame automatisch gesendet.
client:set_keepalive
syntax: ok, err = wb:set_keepalive(max_idle_timeout, pool_size)
Setzt die aktuelle WebSocket-Verbindung sofort in den ngx_lua Cosocket-Verbindungs-Pool.
Sie können das maximale Leerlauf-Timeout (in ms) angeben, wenn die Verbindung im Pool ist, und die maximale Größe des Pools für jeden Nginx-Arbeitsprozess.
Im Erfolgsfall gibt es 1 zurück. Im Falle von Fehlern gibt es nil mit einer Zeichenfolge zurück, die den Fehler beschreibt.
Rufen Sie diese Methode nur an der Stelle auf, an der Sie stattdessen die close-Methode aufgerufen hätten. Das Aufrufen dieser Methode versetzt das aktuelle WebSocket-Objekt sofort in den closed-Zustand. Alle nachfolgenden Operationen, die nicht connect() auf dem aktuellen Objekt sind, geben den closed-Fehler zurück.
client:set_timeout
syntax: wb:set_timeout(ms)
Identisch mit der set_timeout-Methode der resty.websocket.server-Objekte.
client:send_text
syntax: bytes, err = wb:send_text(text)
Identisch mit der send_text-Methode der resty.websocket.server-Objekte.
client:send_binary
syntax: bytes, err = wb:send_binary(data)
Identisch mit der send_binary-Methode der resty.websocket.server-Objekte.
client:send_ping
syntax: bytes, err = wb:send_ping()
syntax: bytes, err = wb:send_ping(msg)
Identisch mit der send_ping-Methode der resty.websocket.server-Objekte.
client:send_pong
syntax: bytes, err = wb:send_pong()
syntax: bytes, err = wb:send_pong(msg)
Identisch mit der send_pong-Methode der resty.websocket.server-Objekte.
client:send_close
syntax: bytes, err = wb:send_close()
syntax: bytes, err = wb:send_close(code, msg)
Identisch mit der send_close-Methode der resty.websocket.server-Objekte.
client:send_frame
syntax: bytes, err = wb:send_frame(fin, opcode, payload)
Identisch mit der send_frame-Methode der resty.websocket.server-Objekte.
Um zu steuern, ob unmaskierte Frames gesendet werden, können Sie true an die send_unmasked-Option im new-Konstruktor übergeben. Standardmäßig werden maskierte Frames gesendet.
client:recv_frame
syntax: data, typ, err = wb:recv_frame()
Identisch mit der recv_frame-Methode der resty.websocket.server-Objekte.
resty.websocket.protocol
Um dieses Modul zu laden, tun Sie einfach Folgendes:
local protocol = require "resty.websocket.protocol"
Methoden
protocol.recv_frame
syntax: data, typ, err = protocol.recv_frame(socket, max_payload_len, force_masking)
Empfängt einen WebSocket-Frame vom Draht.
protocol.build_frame
syntax: frame = protocol.build_frame(fin, opcode, payload_len, payload, masking)
Baut einen rohen WebSocket-Frame.
protocol.send_frame
syntax: bytes, err = protocol.send_frame(socket, fin, opcode, payload, max_payload_len, masking)
Sendet einen rohen WebSocket-Frame.
Automatische Fehlerprotokollierung
Standardmäßig protokolliert das zugrunde liegende ngx_lua-Modul Fehler, wenn Socket-Fehler auftreten. Wenn Sie bereits eine ordnungsgemäße Fehlerbehandlung in Ihrem eigenen Lua-Code durchführen, wird empfohlen, diese automatische Fehlerprotokollierung zu deaktivieren, indem Sie die lua_socket_log_errors Direktive von ngx_lua deaktivieren, das heißt,
lua_socket_log_errors off;
Einschränkungen
- Diese Bibliothek kann nicht in Code-Kontexten wie init_by_lua, set_by_lua, log_by_lua und header_filter_by_lua verwendet werden, wo die ngx_lua Cosocket-API nicht verfügbar ist.
- Die
resty.websocket-Objektinstanz kann nicht auf Modulebene in einer Lua-Variablen gespeichert werden, da sie dann von allen gleichzeitigen Anfragen, die vom selben Nginx-Arbeitsprozess bearbeitet werden, geteilt wird (siehe http://wiki.nginx.org/HttpLuaModule#Data_Sharing_within_an_Nginx_Worker) und zu schlechten Race-Conditions führen kann, wenn gleichzeitige Anfragen versuchen, dieselberesty.websocket-Instanz zu verwenden. Sie sollten immerresty.websocket-Objekte in lokalen Funktionsvariablen oder in derngx.ctx-Tabelle initiieren. Diese Orte haben alle ihre eigenen Datenkopien für jede Anfrage.
Siehe auch
- Blogbeitrag WebSockets mit OpenResty von Aapo Talvensaari.
- das ngx_lua-Modul: http://wiki.nginx.org/HttpLuaModule
- das WebSocket-Protokoll: http://tools.ietf.org/html/rfc6455
- die lua-resty-upload Bibliothek
- die lua-resty-redis Bibliothek
- die lua-resty-memcached Bibliothek
- die lua-resty-mysql Bibliothek
GitHub
Sie finden möglicherweise zusätzliche Konfigurationstipps und Dokumentationen für dieses Modul im GitHub-Repository für nginx-module-websocket.