Débuter avec Lua

API7.ai

September 23, 2022

OpenResty (NGINX + Lua)

Après avoir acquis une compréhension générale des bases de NGINX, nous allons approfondir notre connaissance de Lua. C'est le langage de programmation utilisé dans OpenResty, et il est essentiel de maîtriser sa syntaxe de base.

Lua est un langage de script petit et subtil, né dans un laboratoire universitaire au Brésil, dont le nom signifie "belle lune" en portugais. NGINX est né en Russie, Lua au Brésil, et OpenResty en Chine, le pays d'origine de l'auteur. Il est intéressant de noter que ces trois technologies open source tout aussi intelligentes proviennent de pays des BRICS, et non d'Europe ou d'Amérique.

Lua a été conçu pour se positionner comme un langage de colle simple, léger et embarquable, qui ne suit pas la voie des solutions complexes et ambitieuses. Bien que vous n'écriviez peut-être pas directement du code Lua dans votre travail quotidien, Lua est largement utilisé. De nombreux jeux en ligne, comme World of Warcraft, utilisent Lua pour écrire des plugins ; Redis, la base de données clé-valeur, intègre Lua pour contrôler la logique.

D'autre part, bien que la bibliothèque de Lua soit relativement simple, elle peut facilement appeler des bibliothèques du langage de programmation C, et de nombreux codes matures en C peuvent être utilisés pour Lua. Par exemple, dans OpenResty, vous aurez souvent besoin d'appeler des fonctions du langage C de NGINX et OpenSSL, grâce à la capacité de Lua et LuaJIT à accéder facilement aux bibliothèques C.

Ici, je vais vous familiariser rapidement avec les types de données et la syntaxe de Lua afin que vous puissiez apprendre OpenResty plus facilement par la suite.

Environnement et hello world

Nous n'avons pas besoin d'installer spécifiquement un environnement Lua 5.1 standard car OpenResty ne supporte plus le Lua standard, seulement LuaJIT. Notez que la syntaxe Lua que je présente ici est également compatible avec LuaJIT et ne se base pas sur la dernière version de Lua 5.3.

Vous pouvez trouver le répertoire et l'exécutable de LuaJIT dans le répertoire d'installation d'OpenResty. Je suis sur un environnement Mac et j'ai utilisé brew pour installer OpenResty, donc votre chemin local sera probablement différent de celui ci-dessous.

$ 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

Vous pouvez également le trouver dans le répertoire des fichiers exécutables du système.

 $ which luajit
 /usr/local/bin/luajit

Vérifiez la version de LuaJIT.

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

Après avoir vérifié ces informations, vous pouvez créer un nouveau fichier 1.lua et utiliser LuaJIT pour exécuter le code hello world.

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

Bien sûr, vous pouvez également utiliser resty pour l'exécuter directement, sachant qu'il est finalement exécuté avec LuaJIT.

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

Ces deux façons d'exécuter hello world sont possibles. Je préfère l'approche resty car beaucoup de code OpenResty est également exécuté par resty par la suite.

Types de données

Il n'y a pas beaucoup de types de données en Lua, et vous pouvez retourner le type d'une valeur avec la fonction type, comme suit.

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

Les informations suivantes seront imprimées.

 string
 function
 boolean
 number
 table
 nil

Ce sont les types de données de base en Lua. Passons-les brièvement en revue.

Chaîne de caractères

En Lua, la chaîne de caractères est une valeur immuable. Si vous souhaitez modifier une chaîne, vous devez en créer une nouvelle. Cette approche a des avantages et des inconvénients : l'avantage est que même si la même chaîne apparaît plusieurs fois, il n'y en a qu'une seule copie en mémoire, mais l'inconvénient est également évident : si vous souhaitez modifier et concaténer des chaînes, vous créez beaucoup de chaînes supplémentaires inutiles.

Prenons un exemple pour illustrer cet inconvénient. En Lua, nous utilisons deux points pour indiquer l'addition de chaînes. Le code suivant concatène les nombres de 1 à 10 sous forme de chaînes.

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

Ici, nous bouclons dix fois, et seul le dernier résultat est ce dont nous avons besoin ; les neuf nouvelles chaînes intermédiaires sont inutiles. Elles prennent non seulement de l'espace supplémentaire, mais consomment également des opérations CPU inutiles.

Bien sûr, nous aurons une solution pour cela plus tard dans la section sur l'optimisation des performances.

De plus, en Lua, vous avez trois façons d'exprimer une chaîne : les guillemets simples, les guillemets doubles et les crochets longs ([[]]). Les deux premiers sont relativement faciles à comprendre et sont généralement utilisés dans d'autres langages, alors à quoi servent les crochets longs ?

Regardons un exemple concret.

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

Vous pouvez voir que les chaînes entre crochets longs ne sont pas échappées.

Vous pourriez vous demander : Et si la chaîne ci-dessus inclut les crochets longs ? La réponse est simple : ajoutez un ou plusieurs signes = au milieu des crochets longs.

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

Booléen

C'est simple, true et false. En Lua, seuls nil et false sont faux ; tout le reste est vrai, y compris 0 et la chaîne vide. Nous pouvons vérifier cela avec le code suivant.

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

Ce type de jugement est incompatible avec de nombreux langages de développement courants, donc pour éviter des erreurs dans de tels cas, vous pouvez écrire explicitement l'objet de comparaison, comme suit.

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

Nombre

Le type nombre de Lua est implémenté comme un nombre à virgule flottante double précision. Il est intéressant de noter que LuaJIT supporte le mode dual-number, ce qui signifie que LuaJIT stocke les entiers comme des entiers et les nombres à virgule flottante comme des nombres à virgule flottante double précision, selon le contexte.

De plus, LuaJIT supporte les long-long integers pour les grands entiers, comme dans l'exemple suivant.

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

Fonction

Les fonctions sont des citoyens de première classe en Lua, et vous pouvez stocker une fonction dans une variable ou l'utiliser comme référence entrante et sortante d'une autre fonction.

Par exemple, les deux déclarations de fonction suivantes sont exactement équivalentes.

function foo()
 end

et

foo = function ()
 end

Table

La Table est la seule structure de données en Lua et est donc naturellement très importante, donc je lui consacrerai une section spéciale plus tard. Nous pouvons commencer par regarder un exemple de code simple.

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

Valeur nulle

En Lua, une valeur nulle est nil. Si vous définissez une variable mais ne lui attribuez pas de valeur, sa valeur par défaut est nil.

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

Lorsque vous entrez dans le système OpenResty, vous trouverez de nombreuses valeurs nulles, comme ngx.null, etc. Nous en reparlerons plus tard.

Les types de données de Lua, je vais principalement en introduire autant, pour vous donner une base. Nous continuerons à apprendre ce que vous devez maîtriser plus tard dans l'article. Apprendre par la pratique et l'utilisation est toujours le moyen le plus pratique d'absorber de nouvelles connaissances.

Bibliothèque standard commune

Souvent, apprendre un langage revient vraiment à apprendre ses bibliothèques standard.

Lua est relativement petit et n'a pas beaucoup de bibliothèques standard intégrées. De plus, dans l'environnement OpenResty, la bibliothèque standard Lua est une priorité très faible. Pour la même fonctionnalité, je recommande d'utiliser d'abord l'API OpenResty, puis les fonctions de bibliothèque de LuaJIT, et enfin les fonctions normales de Lua.

L'API d'OpenResty > les fonctions de bibliothèque de LuaJIT > les fonctions standard de Lua est une priorité qui sera mentionnée à plusieurs reprises en termes de facilité d'utilisation et de performance.

Cependant, malgré cela, nous utiliserons inévitablement certaines bibliothèques Lua dans nos projets réels. Ici, j'ai sélectionné quelques-unes des bibliothèques standard les plus couramment utilisées pour les présenter, et si vous souhaitez en savoir plus, vous pouvez consulter la documentation officielle de Lua.

Bibliothèque de chaînes

La manipulation des chaînes est ce que nous utilisons souvent et où les pièges sont les plus grands.

Une règle simple est que si des expressions régulières sont impliquées, veuillez utiliser ngx.re.* fourni par OpenResty pour les résoudre, et non le traitement string.* de Lua. En effet, les expressions régulières de Lua sont uniques et ne sont pas conformes à la spécification PCRE, et je crois que la plupart des ingénieurs ne pourront pas les maîtriser.

L'une des fonctions de bibliothèque de chaînes les plus couramment utilisées est string.byte(s [, i [, j ]]), qui retourne le code ASCII correspondant aux caractères s[i], s[i + 1], s[i + 2], ------, s[j]. La valeur par défaut de i est 1, le premier octet, et la valeur par défaut de j est i.

Regardons un exemple de code.

$ resty -e 'print(string.byte("abc", 1, 3))
 print(string.byte("abc", 3)) -- Le troisième paramètre est manquant, le troisième paramètre est le même que le deuxième par défaut, qui est 3
 print(string.byte("abc"))    -- Les deuxième et troisième paramètres sont manquants, les deux valent 1 par défaut
 '

Sa sortie est :

 979899
 99
 97

Bibliothèque de tables

Dans le contexte d'OpenResty, je ne recommande pas d'utiliser la plupart des bibliothèques de tables fournies avec Lua, à l'exception de quelques fonctions comme table.concat et table.sort. Quant à leurs détails, nous les laisserons au chapitre sur LuaJIT.

Ici, je mentionnerai brièvement table.concat. table.concat est généralement utilisé dans des scénarios de concaténation de chaînes, comme dans l'exemple ci-dessous. Il peut éviter de générer beaucoup de chaînes inutiles.

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

Bibliothèque mathématique

La bibliothèque mathématique de Lua est composée d'un ensemble standard de fonctions mathématiques. L'introduction de la bibliothèque mathématique enrichit à la fois le langage de programmation Lua et facilite l'écriture de programmes.

Dans les projets OpenResty, nous utilisons rarement Lua pour faire des opérations mathématiques. Cependant, deux fonctions liées aux nombres aléatoires, math.random() et math.randomseed(), sont couramment utilisées, comme dans le code suivant, qui peut générer deux nombres aléatoires dans une plage spécifiée.

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

Variables factices

Après avoir compris ces bibliothèques standard partagées, apprenons un nouveau concept - les variables factices.

Imaginez un scénario où une fonction retourne plusieurs valeurs, dont certaines dont nous n'avons pas besoin, alors comment devrions-nous recevoir ces valeurs ?

Je ne sais pas ce que vous en pensez, mais pour moi, au moins, c'est tortueux d'essayer de donner des noms significatifs à ces variables inutilisées.

Heureusement, Lua a une solution parfaite pour cela, fournissant le concept de variable factice, conventionnellement nommée avec un trait de soulignement, pour ignorer les valeurs inutiles et servir de placeholder.

Prenons la fonction de bibliothèque standard string.find comme exemple pour voir l'utilisation des variables factices. Cette fonction de bibliothèque normale retourne deux valeurs représentant respectivement les indices de début et de fin.

Si nous avons seulement besoin d'obtenir l'indice de début, il est simple de déclarer une variable pour recevoir la valeur de retour de string.find comme suit.

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

Mais si vous voulez seulement obtenir l'indice de fin, alors vous devez utiliser la variable factice

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

En plus d'être utilisées dans les valeurs de retour, les variables factices sont souvent utilisées dans les boucles, comme dans l'exemple suivant.

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

Et lorsqu'il y a plusieurs valeurs de retour à ignorer, vous pouvez réutiliser la même variable factice. Je ne donnerai pas d'exemple ici. Pouvez-vous essayer d'écrire un exemple de code comme celui-ci vous-même ? Vous êtes invités à poster le code dans la section des commentaires pour partager et échanger avec moi.

Résumé

Aujourd'hui, nous avons rapidement parcouru les structures de données et la syntaxe de Lua standard, et je suis sûr que vous avez eu un premier aperçu de ce langage simple et compact. Dans la prochaine leçon, je vous emmènerai à travers la relation entre Lua et LuaJIT, LuaJIT étant le point central d'OpenResty et méritant d'être approfondi.

Enfin, je veux vous laisser avec une question de réflexion supplémentaire.

Vous souvenez-vous du code que vous avez appris dans ce post lorsque nous avons parlé de la bibliothèque mathématique ? Il génère deux nombres aléatoires dans une plage spécifiée.

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

Cependant, vous avez peut-être remarqué que le code est ensemencé avec l'horodatage actuel. Y a-t-il un problème avec cette approche ? Et comment devrions-nous générer de bons seeds ? Souvent, les nombres aléatoires que nous développons ne sont pas aléatoires et présentent d'excellents risques de sécurité.

N'hésitez pas à partager vos opinions avec nous, et à partager ce post avec vos collègues et amis. Communiquons et améliorons-nous ensemble.