Einführung in gängige APIs in OpenResty
API7.ai
November 4, 2022
In den vorherigen Artikeln haben Sie bereits viele wichtige Lua-APIs in OpenResty kennengelernt. Heute werden wir uns mit einigen anderen allgemeinen APIs befassen, die hauptsächlich mit regulären Ausdrücken, Zeit, Prozessen usw. zusammenhängen.
APIs im Zusammenhang mit regulären Ausdrücken
Beginnen wir mit den am häufigsten verwendeten und wichtigsten regulären Ausdrücken. In OpenResty sollten wir die von ngx.re.*
bereitgestellten APIs verwenden, um die Logik im Zusammenhang mit regulären Ausdrücken zu handhaben, anstatt Lua-Mustervergleiche zu verwenden. Dies liegt nicht nur an Leistungsgründen, sondern auch daran, dass die Lua-Regularität eigenständig ist und nicht der PCRE
-Spezifikation entspricht, was für die meisten Entwickler ärgerlich wäre.
In den vorherigen Artikeln sind Sie bereits auf einige der ngx.re.*
-APIs gestoßen, deren Dokumentation sehr detailliert ist. Daher werde ich sie hier nicht weiter auflisten. Stattdessen werde ich die folgenden beiden APIs gesondert vorstellen.
ngx.re.split
Die erste ist ngx.re.split
. Das Teilen von Zeichenketten ist eine sehr häufige Funktion, und OpenResty bietet auch eine entsprechende API an, aber viele Entwickler können eine solche Funktion nicht finden und entscheiden sich dafür, sie selbst zu implementieren.
Warum? Die ngx.re.split
-API befindet sich nicht in lua-nginx-module
, sondern in lua-resty-core
; sie ist nicht in der Dokumentation der lua-resty-core
-Startseite zu finden, sondern in der Dokumentation des lua-resty-core/lib/ngx/re.md
-Drittanbieterverzeichnisses. Daher sind viele Entwickler völlig ahnungslos über die Existenz dieser API.
Ebenso schwer zu entdeckende APIs sind ngx_resp.add_header
, enable_privileged_agent
usw., die wir bereits erwähnt haben. Wie können wir dieses Problem also schnell lösen? Neben dem Lesen der Dokumentation auf der lua-resty-core
-Startseite müssen Sie auch die *.md
-Dokumentation im Verzeichnis lua-resty-core/lib/ngx/
durchlesen.
lua_regex_match_limit
Zweitens möchte ich lua_regex_match_limit
vorstellen. Wir haben bisher noch nicht über die von OpenResty bereitgestellten NGINX-Befehle gesprochen, da in den meisten Fällen die Standardwerte ausreichend sind und es nicht notwendig ist, sie zur Laufzeit zu ändern. Eine Ausnahme davon ist der Befehl lua_regex_match_limit
, der mit regulären Ausdrücken zusammenhängt.
Wir wissen, dass wenn wir eine reguläre Engine verwenden, die auf Backtracking-NFA basiert, dann besteht das Risiko eines katastrophalen Backtrackings, bei dem der reguläre Ausdruck beim Abgleich zu viel zurückverfolgt, was dazu führt, dass die CPU zu 100 % ausgelastet ist und Dienste blockiert werden.
Sobald ein katastrophales Backtracking auftritt, müssen wir gdb
verwenden, um den Dump zu analysieren, oder systemtap
, um die Online-Umgebung zu analysieren, um es zu lokalisieren. Leider ist es nicht einfach, es im Voraus zu erkennen, da nur spezielle Anfragen es auslösen. Dies ermöglicht es Angreifern, dies auszunutzen, und ReDoS
(RegEx Denial of Service) bezieht sich auf diese Art von Angriff.
Hier zeige ich Ihnen hauptsächlich, wie Sie die folgende Zeile Code in OpenResty verwenden können, um die oben genannten Probleme einfach und effektiv zu vermeiden:
lua_regex_match_limit
wird verwendet, um die Anzahl der Rückverfolgungen durch die PCRE
-reguläre Engine zu begrenzen. Auf diese Weise, selbst wenn katastrophales Backtracking auftritt, werden die Folgen auf einen Bereich beschränkt, der nicht dazu führt, dass Ihre CPU vollständig ausgelastet ist.
lua_regex_match_limit 100000;
Zeitbezogene APIs
Die am häufigsten verwendete Zeit-API ist ngx.now
, die den aktuellen Zeitstempel ausgibt, wie die folgende Codezeile:
resty -e 'ngx.say(ngx.now())'
Wie Sie an den gedruckten Ergebnissen sehen können, enthält ngx.now
den Bruchteil, ist also genauer. Die verwandte ngx.time
-API gibt nur den ganzzahligen Teil des Wertes zurück. Die anderen, ngx.localtime
, ngx.utctime
, ngx.cookie_time
und ngx.http_time
, werden hauptsächlich verwendet, um Zeit in verschiedenen Formaten zurückzugeben und zu verarbeiten. Wenn Sie sie verwenden möchten, können Sie die Dokumentation überprüfen, sie sind nicht schwer zu verstehen, daher muss ich nicht weiter darauf eingehen.
Es ist jedoch erwähnenswert, dass diese APIs, die die aktuelle Zeit zurückgeben, wenn sie nicht durch eine nicht-blockierende Netzwerk-IO-Operation ausgelöst werden, immer den zwischengespeicherten Wert zurückgeben und nicht die aktuelle Echtzeitzeit, wie wir es uns wünschen würden. Schauen Sie sich den folgenden Beispielcode an:
$ resty -e 'ngx.say(ngx.now())
os.execute("sleep 1")
ngx.say(ngx.now())'
Zwischen den beiden Aufrufen von ngx.now
haben wir die blockierende Funktion von Lua verwendet, um für 1
Sekunde zu schlafen, aber der zurückgegebene Zeitstempel ist in beiden Fällen derselbe, wie die gedruckten Ergebnisse zeigen.
Was passiert also, wenn wir sie durch eine nicht-blockierende Schlaffunktion ersetzen? Zum Beispiel der folgende neue Code:
$ resty -e 'ngx.say(ngx.now())
ngx.sleep(1)
ngx.say(ngx.now())'
Es wird einen anderen Zeitstempel drucken. Dies führt uns zu ngx.sleep
, einer nicht-blockierenden Schlaffunktion. Neben dem Schlafen für eine bestimmte Zeit hat diese Funktion noch einen weiteren besonderen Zweck.
Wenn Sie beispielsweise ein Stück Code haben, das intensive Berechnungen durchführt, was viel Zeit in Anspruch nimmt, werden die Anfragen, die diesem Code entsprechen, während dieser Zeit die Worker- und CPU-Ressourcen in Anspruch nehmen, was dazu führt, dass andere Anfragen in der Warteschlange stehen und nicht rechtzeitig beantwortet werden. An diesem Punkt können wir ngx.sleep(0)
einstreuen, um diesen Code dazu zu bringen, die Kontrolle abzugeben, damit auch andere Anfragen verarbeitet werden können.
Worker- und Prozess-APIs
OpenResty bietet die ngx.worker.*
und ngx.process.*
APIs, um Informationen über Worker und Prozesse zu erhalten. Ersteres bezieht sich auf Nginx-Worker-Prozesse, während letzteres sich auf alle Nginx-Prozesse im Allgemeinen bezieht, nicht nur auf Worker-Prozesse, sondern auch auf den Master-Prozess, privilegierte Prozesse usw.
Das Problem von true
- und null
-Werten
Schließlich werfen wir einen Blick auf das Problem von true
- und null
-Werten. In OpenResty war die Bestimmung von true
- und null
-Werten schon immer ein sehr ärgerlicher und verwirrender Punkt.
Schauen wir uns die Definition eines true
-Werts in Lua an: außer nil
und false
sind alle true
-Werte.
Daher würden true
-Werte auch 0
, leere string
, leere table
usw. einschließen.
Schauen wir uns nil
in Lua an, was undefiniert
bedeutet. Wenn Sie beispielsweise eine Variable deklarieren, aber noch nicht initialisiert haben, ist ihr Wert nil
.
$ resty -e 'local a
ngx.say(type(a))'
Und nil
ist auch ein Datentyp in Lua. Nachdem wir diese beiden Punkte verstanden haben, schauen wir uns nun die anderen Probleme an, die sich aus diesen beiden Definitionen ergeben.
ngx.null
Das erste Problem ist ngx.null
. Da Ldas nil
nicht als Wert einer table
verwendet werden kann, führt OpenResty ngx.null
als null
-Wert in der Tabelle ein.
$ resty -e 'print(ngx.null)'
null
$ resty -e 'print(type(ngx.null))'
userdata
Wie Sie an den beiden obigen Codezeilen sehen können, wird ngx.null
als null
gedruckt, und sein Typ ist userdata
. Kann es also als false
-Wert behandelt werden? Natürlich nicht. Der boolesche Wert von ngx.null
ist true
.
$ resty -e 'if ngx.null then
ngx.say("true")
end'
Denken Sie also daran, dass nur nil
und false
false
-Werte sind. Wenn Sie diesen Punkt übersehen, ist es leicht, in Fallen zu treten, zum Beispiel, wenn Sie lua-resty-redis
verwenden und die folgende Überprüfung durchführen:
local res, err = red:get("dog")
if not res then
res = res + "test"
end
Wenn der Rückgabewert res
nil
ist, ist der Funktionsaufruf fehlgeschlagen; wenn res
ngx.null
ist, existiert der Schlüssel dog
nicht in Redis, dann stürzt der Code ab, wenn der Schlüssel dog
nicht existiert.
cdata:NULL
Das zweite Problem ist cdata:NULL
. Wenn Sie eine C-Funktion über die LuaJIT FFI-Schnittstelle aufrufen und die Funktion einen NULL
-Zeiger zurückgibt, dann werden Sie auf eine andere Art von null
-Wert stoßen, cdata:NULL
.
$ resty -e 'local ffi = require "ffi"
local cdata_null = ffi.new("void*", nil)
if cdata_null then
ngx.say("true")
end'
Wie ngx.null
ist auch cdata:NULL
true
. Aber was noch verwirrender ist, ist, dass der folgende Code, der true
druckt, bedeutet, dass cdata:NULL
gleichbedeutend mit nil
ist.
$ resty -e 'local ffi = require "ffi"
local cdata_null = ffi.new("void*", nil)
ngx.say(cdata_null == nil)'
Wie sollten wir also mit ngx.null
und cdata:NULL
umgehen? Es ist keine gute Lösung, die Anwendungsschicht mit diesen Problemen zu belasten. Es ist besser, eine zweite Ebene der Verpackung durchzuführen und den Aufrufer nicht über diese Details informieren zu lassen.
Es ist besser, eine zweite Ebene der Verpackung durchzuführen und den Aufrufer nicht über diese Details informieren zu lassen.
cjson.null
Schließlich werfen wir einen Blick auf die null
-Werte, die in cjson
auftreten. Die cjson
-Bibliothek nimmt das NULL
in JSON, dekodiert es in Lua lightuserdata
und verwendet cjson.null
, um es darzustellen.
$ resty -e 'local cjson = require "cjson"
local data = cjson.encode(nil)
local decode_null = cjson.decode(data)
ngx.say(decode_null == cjson.null)'
Ldas nil
wird nach der Kodierung und Dekodierung durch JSON zu cjson.null
. Wie Sie sich vorstellen können, wird es aus dem gleichen Grund wie ngx.null
eingeführt, weil nil
nicht als Wert in einer table
verwendet werden kann.
Sind Sie bisher von so vielen Arten von null
-Werten in OpenResty verwirrt? Machen Sie sich keine Sorgen. Lesen Sie diesen Teil ein paar Mal und sortieren Sie ihn selbst, dann werden Sie nicht verwirrt sein. Natürlich müssen wir in Zukunft mehr darüber nachdenken, ob es funktioniert, wenn wir so etwas wie if not foo then
schreiben.
Zusammenfassung
Der heutige Artikel stellt Ihnen die in OpenResty häufig verwendeten Lua-APIs vor.
Abschließend stelle ich Ihnen eine Frage: Warum wird im ngx.now
-Beispiel der Wert von ngx.now
nicht geändert, wenn es keine yield
-Operation gibt? Teilen Sie Ihre Meinung in den Kommentaren mit, und teilen Sie diesen Artikel auch gerne, damit wir uns austauschen und gemeinsam verbessern können.