Erste Schritte mit Lua

API7.ai

September 23, 2022

OpenResty (NGINX + Lua)

Nach einem allgemeinen Verständnis der Grundlagen von NGINX werden wir uns weiter mit Lua beschäftigen. Es ist die Programmiersprache, die in OpenResty verwendet wird, und es ist notwendig, ihre grundlegende Syntax zu beherrschen.

Lua ist eine kleine und subtile Skriptsprache, die in einem Universitätslabor in Brasilien entstanden ist und deren Name auf Portugiesisch "schöner Mond" bedeutet. NGINX wurde in Russland geboren, Lua in Brasilien und OpenResty in China, dem Heimatland des Autors. Interessanterweise stammen diese drei ebenso cleveren Open-Source-Technologien aus BRICS-Ländern und nicht aus Europa oder Amerika.

Lua wurde entworfen, um sich als einfache, leichtgewichtige, einbettbare Klebesprache zu positionieren, die nicht den großen und mutigen Weg geht. Obwohl Sie möglicherweise nicht direkt in Ihrem täglichen Arbeitsalltag Lua-Code schreiben, wird Lua dennoch weit verbreitet eingesetzt. Viele Online-Spiele, wie World of Warcraft, verwenden Lua, um Plugins zu schreiben; Redis, die Schlüssel-Wert-Datenbank, hat Lua integriert, um die Logik zu steuern.

Andererseits, obwohl Luas Bibliothek relativ einfach ist, kann sie leicht eine C-Programmiersprachenbibliothek aufrufen, und viele ausgereifte C-Programmiersprachencodes können dafür verwendet werden. Zum Beispiel werden Sie in OpenResty oft NGINX- und OpenSSL-C-Programmiersprachenfunktionen aufrufen müssen, dank Luas und LuaJITs Fähigkeit, leicht auf C-Bibliotheken zuzugreifen.

Hier werde ich Sie schnell mit Lua-Datentypen und -Syntax vertraut machen, damit Sie später OpenResty reibungsloser lernen können.

Umgebung und hello world

Wir müssen keine spezielle Standard-Lua-5.1-Umgebung installieren, da OpenResty standardmäßiges Lua nicht mehr unterstützt, sondern nur LuaJIT. Beachten Sie, dass die hier vorgestellte Lua-Syntax auch mit LuaJIT kompatibel ist und nicht auf dem neuesten Lua 5.3 basiert.

Sie können das LuaJIT-Verzeichnis und die ausführbare Datei im OpenResty-Installationsverzeichnis finden. Ich bin in einer Mac-Umgebung und habe brew verwendet, um OpenResty zu installieren, daher wird Ihr lokaler Pfad wahrscheinlich von dem folgenden abweichen.

$ ll /usr/local/Cellar/openresty/1.13.6.2/luajit/bin/luajit
     lrwxr-xr-x  1 ming  admin    18B  4  2 14:54 /usr/local/Cellar/openresty/1.13.6.2/luajit/bin/luajit -> luajit-2.1.0-beta3

Sie können es auch im Verzeichnis der ausführbaren Dateien des Systems finden.

 $ which luajit
 /usr/local/bin/luajit

Überprüfen Sie die Version von LuaJIT.

$ luajit -v
 LuaJIT 2.1.0-beta2 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/

Nachdem Sie diese Informationen überprüft haben, können Sie eine neue 1.lua-Datei erstellen und LuaJIT verwenden, um den hello world-Code auszuführen.

$ cat 1.lua
print("hello world")
$ luajit 1.lua
 hello world

Natürlich können Sie auch resty verwenden, um es direkt auszuführen, wissend, dass es letztendlich auch mit LuaJIT ausgeführt wird.

$ resty -e 'print("hello world")'
 hello world

Beide Möglichkeiten, hello world auszuführen, sind möglich. Ich bevorzuge den resty-Ansatz, da später viel OpenResty-Code auch von resty ausgeführt wird.

Datentypen

Es gibt nicht viele Datentypen in Lua, und Sie können den Typ eines Werts mit der type-Funktion zurückgeben, wie zum Beispiel das Folgende.

$ resty -e 'print(type("hello world"))
 print(type(print))
 print(type(true))
 print(type(360.0))
 print(type({}))
 print(type(nil))
 '

Die folgenden Informationen werden ausgegeben.

 string
 function
 boolean
 number
 table
 nil

Dies sind die grundlegenden Datentypen in Lua. Lassen Sie uns sie kurz vorstellen.

String

In Lua ist der String ein unveränderlicher Wert. Wenn Sie einen String ändern möchten, müssen Sie einen neuen erstellen. Dieser Ansatz hat Vor- und Nachteile: Der Vorteil ist, dass selbst wenn der exakte String viele Male auftaucht, es nur eine Kopie im Speicher gibt, aber der Nachteil ist auch offensichtlich: Wenn Sie Strings ändern und zusammenfügen möchten, erstellen Sie viele zusätzliche unnötige Strings.

Lassen Sie uns ein Beispiel nehmen, um diesen Nachteil zu veranschaulichen. In Lua verwenden wir zwei Punkte, um die Addition von Strings anzuzeigen. Der folgende Code fügt die Zahlen 1 bis 10 als Strings zusammen.

$ resty -e 'local s  = ""
 for i = 1, 10 do
     s = s .. tostring(i)
 end
 print(s)'

Hier schleifen wir zehn Mal, und nur das letzte Ergebnis ist das, was wir brauchen; die neun neuen Strings dazwischen sind nutzlos. Sie nehmen nicht nur zusätzlichen Speicherplatz ein, sondern verbrauchen auch unnötige CPU-Operationen.

Natürlich werden wir später in der Leistungsoptimierung eine Lösung dafür haben.

Außerdem haben Sie in Lua drei Möglichkeiten, einen String auszudrücken: einfache Anführungszeichen, doppelte Anführungszeichen und lange Klammern ([[]]). Die ersten beiden sind relativ leicht zu verstehen und werden in anderen Sprachen allgemein verwendet, also wozu dienen die langen Klammern?

Lassen Sie uns ein konkretes Beispiel betrachten.

$ resty -e 'print([[string has \n and \r]])'
 string has \n and \r

Sie können sehen, dass die Strings in den langen Klammern in keiner Weise escaped werden.

Sie mögen eine weitere Frage haben: Was, wenn der obige String die langen Klammern enthält? Die Antwort ist einfach: Fügen Sie ein oder mehrere =-Zeichen in die Mitte der langen Klammern ein.

$ resty -e 'print([=[ string has a [[]]. ]=])'
  string has a [[]].

Boolean

Dies ist ein einfacher Typ, true und false. In Lua sind nur nil und false falsch; alles andere ist wahr, einschließlich 0 und der leere String. Wir können dies mit dem folgenden Code überprüfen.

$ resty -e 'local a = 0
 if a then
   print("true")
 end
 a = ""
 if a then
   print("true")
 end'

Diese Art der Beurteilung ist mit vielen gängigen Entwicklungssprachen nicht konsistent, daher können Sie, um Fehler in solchen Angelegenheiten zu vermeiden, das Vergleichsobjekt explizit schreiben, wie zum Beispiel das Folgende.

$ resty -e 'local a = 0
 if a == false then
   print("true")
 end
 '

Number

Der Zahlentyp von Lua wird als doppelt genaue Gleitkommazahl implementiert. Es ist erwähnenswert, dass LuaJIT den Dual-Number-Modus unterstützt, was bedeutet, dass LuaJIT Ganzzahlen als Ganzzahlen und Gleitkommazahlen als doppelt genaue Gleitkommazahlen speichert, abhängig vom Kontext.

Darüber hinaus unterstützt LuaJIT long-long integers für große Ganzzahlen, wie das folgende Beispiel zeigt.

$ resty -e 'print(9223372036854775807LL - 1)'
9223372036854775806LL

Funktion

Funktionen sind erstklassige Bürger in Lua, und Sie können eine Funktion in einer Variablen speichern oder sie als Ein- und Ausgangsreferenz für eine andere Funktion verwenden.

Zum Beispiel sind die folgenden beiden Funktionsdeklarationen genau gleichwertig.

function foo()
 end

und

foo = function ()
 end

Table

Die Table ist die einzige Datenstruktur in Lua und daher natürlich sehr wichtig, daher werde ich später einen speziellen Abschnitt darauf verwenden. Wir können zunächst einen einfachen Beispielcode betrachten.

$ resty -e 'local color = {first = "red"}
print(color["first"])'
 red

Nullwert

In Lua ist ein Nullwert nil. Wenn Sie eine Variable definieren, aber keinen Wert zuweisen, ist ihr Standardwert nil.

$ resty -e 'local a
 print(type(a))'
 nil

Wenn Sie in das OpenResty-System eintreten, werden Sie viele Nullwerte finden, wie ngx.null und so weiter. Wir werden später mehr darüber sprechen.

Lua-Datentypen werde ich hauptsächlich so viel einführen, um Ihnen zunächst eine Grundlage zu geben. Wir werden später im Artikel weiter lernen, was Sie beherrschen müssen. Lernen durch Praxis und Anwendung ist immer der bequemste Weg, neues Wissen aufzunehmen.

Gemeinsame Standardbibliotheken

Oft besteht das Lernen einer Sprache wirklich darin, ihre Standardbibliotheken zu lernen.

Lua ist relativ klein und hat nicht viele Standardbibliotheken integriert. Außerdem ist in der OpenResty-Umgebung die Lua-Standardbibliothek von sehr niedriger Priorität. Für die gleiche Funktion empfehle ich, zuerst die OpenResty-API zu verwenden, dann die LuaJIT-Bibliotheksfunktionen und schließlich die normalen Lua-Funktionen.

OpenResty's API > LuaJIT's Bibliotheksfunktionen > Standard-Lua-Funktionen ist eine Priorität, die in Bezug auf Benutzerfreundlichkeit und Leistung wiederholt erwähnt wird.

Trotzdem werden wir in unseren tatsächlichen Projekten unweigerlich einige Lua-Bibliotheken verwenden. Hier habe ich einige der häufiger verwendeten Standardbibliotheken ausgewählt, um sie vorzustellen, und wenn Sie mehr wissen möchten, können Sie die offizielle Lua-Dokumentation überprüfen.

String-Bibliothek

String-Manipulation ist das, was wir oft verwenden und wo die Fallstricke am größten sind.

Eine einfache Regel ist, dass, wenn reguläre Ausdrücke beteiligt sind, Sie unbedingt ngx.re.* verwenden sollten, das von OpenResty bereitgestellt wird, um sie zu lösen, und nicht Luas string.*-Verarbeitung. Dies liegt daran, dass Luas reguläre Ausdrücke einzigartig sind und nicht der PCRE-Spezifikation entsprechen, und ich glaube, die meisten Ingenieure werden damit nicht umgehen können.

Eine der am häufigsten verwendeten String-Bibliotheksfunktionen ist string.byte(s [, i [, j ]]), die den ASCII-Code zurückgibt, der den Zeichen s[i], s[i + 1], s[i + 2], ------, s[j] entspricht. Der Standardwert von i ist 1, das erste Byte, und der Standardwert von j ist i.

Lassen Sie uns einen Beispielcode betrachten.

$ resty -e 'print(string.byte("abc", 1, 3))
 print(string.byte("abc", 3)) -- Fehlender dritter Parameter, der dritte Parameter ist standardmäßig der gleiche wie der zweite, also 3
 print(string.byte("abc"))    -- Der zweite und dritte Parameter fehlen, beide standardmäßig 1
 '

Die Ausgabe ist:

 979899
 99
 97

Table-Bibliothek

Im Kontext von OpenResty empfehle ich nicht, die meisten der mitgelieferten Table-Bibliotheken von Lua zu verwenden, außer einigen Funktionen wie table.concat und table.sort. Was ihre Details betrifft, werden wir sie dem LuaJIT-Kapitel überlassen.

Hier werde ich kurz table.concat erwähnen. table.concat wird allgemein in String-Verkettungsszenarien verwendet, wie das folgende Beispiel zeigt. Es kann vermeiden, viele nutzlose Strings zu erzeugen.

$ resty -e 'local a = {"A", "b", "C"}
 print(table.concat(a))'

Math-Bibliothek

Die Lua-Math-Bibliothek besteht aus einem Standardsatz mathematischer Funktionen. Die Einführung der Math-Bibliothek bereichert sowohl die Lua-Programmiersprache als auch erleichtert das Schreiben von Programmen.

In OpenResty-Projekten verwenden wir selten Lua, um mathematische Operationen durchzuführen. Dennoch werden zwei Funktionen im Zusammenhang mit Zufallszahlen, math.random() und math.randomseed(), häufig verwendet, wie der folgende Code, der zwei Zufallszahlen in einem bestimmten Bereich erzeugen kann.

$ resty -e 'math.randomseed (os.time())
print(math.random())
print(math.random(100))'

Dummy-Variablen

Nachdem wir diese gemeinsamen Standardbibliotheken verstanden haben, lassen Sie uns ein neues Konzept lernen - Dummy-Variablen.

Stellen Sie sich ein Szenario vor, in dem eine Funktion mehrere Werte zurückgibt, von denen einige wir nicht benötigen, wie sollten wir dann diese Werte empfangen?

Ich weiß nicht, wie Sie sich dabei fühlen, aber für mich ist es zumindest quälend, diesen unbenutzten Variablen sinnvolle Namen zu geben.

Glücklicherweise hat Lua eine perfekte Lösung dafür, indem es das Konzept einer Dummy-Variablen bereitstellt, die konventionell mit einem Unterstrich benannt wird, um unerwünschte Werte zu verwerfen und als Platzhalter zu dienen.

Lassen Sie uns die Standardbibliotheksfunktion string.find als Beispiel nehmen, um die Verwendung von Dummy-Variablen zu sehen. Diese normale Bibliotheksfunktion gibt zwei Werte zurück, die den Start- und Endindex darstellen.

Wenn wir nur den Startindex erhalten möchten, ist es einfach, eine Variable zu deklarieren, um den Rückgabewert von string.find zu empfangen, wie folgt.

$ resty -e 'local start = string.find("hello", "he")
 print(start)'
 1

Aber wenn Sie nur den Endindex erhalten möchten, dann müssen Sie die Dummy-Variable verwenden

$ resty -e 'local  _, end_pos = string.find("hello", "he")
 print(end_pos)'
 2

Neben der Verwendung im Rückgabewert werden Dummy-Variablen oft in Schleifen verwendet, wie das folgende Beispiel zeigt.

$ resty -e 'for _, v in ipairs({4,5,6}) do
     print(v)
 end'
 4
 5
 6

Und wenn es mehrere Rückgabewerte zu ignorieren gibt, können Sie dieselbe Dummy-Variable wiederverwenden. Ich werde hier kein Beispiel geben. Können Sie versuchen, einen Beispielcode wie diesen selbst zu schreiben? Sie sind willkommen, den Code im Kommentarbereich zu posten, um ihn mit mir zu teilen und auszutauschen.

Zusammenfassung

Heute haben wir einen schnellen Blick auf die Datenstrukturen und die Syntax von Standard-Lua geworfen, und ich bin sicher, Sie haben einen ersten Einblick in diese einfache und kompakte Sprache erhalten. In der nächsten Lektion werde ich Sie durch die Beziehung zwischen Lua und LuaJIT führen, wobei LuaJIT der Hauptfokus von OpenResty ist und es wert ist, tiefer zu graben.

Abschließend möchte ich Ihnen noch eine weitere nachdenkliche Frage stellen.

Erinnern Sie sich an den Code, den Sie in diesem Beitrag gelernt haben, als wir über die Math-Bibliothek gesprochen haben? Er erzeugt zwei Zufallszahlen in einem bestimmten Bereich.

$ resty -e 'math.randomseed (os.time())
print(math.random())
 print(math.random(100))'

Allerdings haben Sie möglicherweise bemerkt, dass der Code mit dem aktuellen Zeitstempel gesät wird. Gibt es ein Problem mit diesem Ansatz? Und wie sollten wir gute Seeds erzeugen? Oft sind die Zufallszahlen, die wir entwickeln, nicht zufällig und haben ausgezeichnete Sicherheitsrisiken.

Willkommen, Ihre Meinungen mit uns zu teilen, und auch willkommen, diesen Beitrag an Ihre Kollegen und Freunde weiterzuleiten. Lassen Sie uns kommunizieren und gemeinsam verbessern.