Introduction des API courantes dans OpenResty
API7.ai
November 4, 2022
Dans les articles précédents, vous vous êtes familiarisé avec de nombreuses API Lua importantes dans OpenResty. Aujourd'hui, nous allons découvrir d'autres API générales, principalement liées aux expressions régulières, au temps, aux processus, etc.
API liées aux expressions régulières
Commençons par examiner les expressions régulières les plus couramment utilisées et les plus importantes. Dans OpenResty, nous devrions utiliser l'ensemble d'API fourni par ngx.re.*
pour gérer la logique liée aux expressions régulières plutôt que d'utiliser la correspondance de motifs Lua. Ce n'est pas seulement pour des raisons de performance, mais aussi parce que la régularité Lua est autonome et ne suit pas la spécification PCRE
, ce qui serait ennuyeux pour la plupart des développeurs.
Dans les articles précédents, vous avez déjà rencontré certaines des API ngx.re.*
, dont la documentation est très détaillée. Je ne vais donc pas les énumérer davantage. Ici, je vais présenter séparément les deux API suivantes.
ngx.re.split
La première est ngx.re.split
. La découpe de chaînes est une fonction très courante, et OpenResty fournit également une API correspondante, mais de nombreux développeurs ne parviennent pas à trouver une telle fonction et doivent choisir de l'implémenter eux-mêmes.
Pourquoi ? L'API ngx.re.split
ne se trouve pas dans lua-nginx-module
mais dans lua-resty-core
; elle ne se trouve pas dans la documentation de la page d'accueil de lua-resty-core
mais dans la documentation du répertoire de troisième niveau lua-resty-core/lib/ngx/re.md
. Par conséquent, de nombreux développeurs ignorent complètement l'existence de cette API.
De même, les API difficiles à découvrir incluent ngx_resp.add_header
, enable_privileged_agent
, etc., que nous avons mentionnés précédemment. Alors, comment résoudre rapidement ce problème ? En plus de lire la documentation de la page d'accueil de lua-resty-core
, vous devez également parcourir la documentation *.md
dans le répertoire lua-resty-core/lib/ngx/
.
lua_regex_match_limit
Ensuite, je souhaite présenter lua_regex_match_limit
. Nous n'avons pas encore parlé des commandes NGINX fournies par OpenResty car, dans la plupart des cas, les valeurs par défaut suffisent et il n'est pas nécessaire de les modifier à l'exécution. L'exception à cela est la commande lua_regex_match_limit
, qui est liée aux expressions régulières.
Nous savons que si nous utilisons un moteur d'expressions régulières basé sur le backtracking NFA, il existe un risque de Catastrophic Backtracking, où l'expression régulière effectue trop de retours en arrière lors de la correspondance, provoquant une utilisation du CPU à 100 % et un blocage des services.
Une fois qu'un backtracking catastrophique se produit, nous devons utiliser gdb
pour analyser le dump ou utiliser systemtap
pour analyser l'environnement en ligne pour le localiser. Malheureusement, le détecter à l'avance n'est pas facile car seules des requêtes spéciales le déclenchent. Cela permet aux attaquants d'en profiter, et ReDoS
(RegEx Denial of Service) fait référence à ce type d'attaque.
Ici, je vais principalement vous montrer comment utiliser la ligne de code suivante dans OpenResty pour éviter simplement et efficacement les problèmes mentionnés ci-dessus :
lua_regex_match_limit
est utilisé pour limiter le nombre de retours en arrière par le moteur d'expressions régulières PCRE
. Ainsi, même si un backtracking catastrophique se produit, les conséquences seront limitées à une plage qui ne provoquera pas une utilisation complète de votre CPU.
lua_regex_match_limit 100000;
API liées au temps
L'API de temps la plus couramment utilisée est ngx.now
, qui affiche l'horodatage actuel, comme dans la ligne de code suivante :
resty -e 'ngx.say(ngx.now())'
Comme vous pouvez le voir dans les résultats imprimés, ngx.now
inclut la partie fractionnaire, donc elle est plus précise. L'API ngx.time
associée ne renvoie que la partie entière de la valeur. Les autres, ngx.localtime
, ngx.utctime
, ngx.cookie_time
et ngx.http_time
, sont principalement utilisées pour renvoyer et traiter le temps dans différents formats. Si vous souhaitez les utiliser, vous pouvez consulter la documentation, elles ne sont pas difficiles à comprendre, donc je n'ai pas besoin d'en parler.
Cependant, il est important de mentionner que ces API qui renvoient l'heure actuelle, si elles ne sont pas déclenchées par une opération d'IO réseau non bloquante, renverront toujours la valeur mise en cache plutôt que l'heure actuelle en temps réel comme nous le souhaiterions. Regardez l'exemple de code suivant :
$ resty -e 'ngx.say(ngx.now())
os.execute("sleep 1")
ngx.say(ngx.now())'
Entre les deux appels à ngx.now
, nous avons utilisé la fonction de blocage de Lua pour dormir pendant 1
seconde, mais l'horodatage renvoyé est le même dans les deux cas, comme le montrent les résultats imprimés.
Alors, que se passe-t-il si nous le remplaçons par une fonction de sommeil non bloquante ? Par exemple, le nouveau code suivant :
$ resty -e 'ngx.say(ngx.now())
ngx.sleep(1)
ngx.say(ngx.now())'
Il imprimera un horodatage différent. Cela nous amène à ngx.sleep
, une fonction de sommeil non bloquante. En plus de dormir pendant une durée spécifiée, cette fonction a un autre usage particulier.
Par exemple, si vous avez un morceau de code qui effectue des calculs intensifs, ce qui prend beaucoup de temps, les requêtes correspondant à ce morceau de code continueront à occuper les ressources du worker et du CPU pendant ce temps, provoquant une file d'attente d'autres requêtes qui ne seront pas traitées à temps. À ce moment, nous pouvons intercaler ngx.sleep(0)
pour que ce code abandonne le contrôle afin que d'autres requêtes puissent également être traitées.
API Worker et processus
OpenResty fournit les API ngx.worker.*
et ngx.process.*
pour obtenir des informations sur les workers et les processus. Le premier concerne les processus worker Nginx, tandis que le second fait référence à tous les processus Nginx en général, non seulement les processus worker, mais aussi le processus master, le processus privilégié, etc.
Le problème des valeurs true
et null
Enfin, examinons le problème des valeurs true
et null
. Dans OpenResty, la détermination des valeurs true
et null
a toujours été un point très problématique et déroutant.
Regardons la définition d'une valeur true
en Lua : à l'exception de nil
et false
, toutes les autres sont des valeurs true
.
Ainsi, les valeurs true
incluraient également 0
, une chaîne vide, une table vide, etc.
Regardons nil
en Lua, qui signifie indéfini
. Par exemple, si vous déclarez une variable mais ne l'avez pas initialisée, sa valeur est nil
.
$ resty -e 'local a
ngx.say(type(a))'
Et nil
est également un type de données en Lua. Après avoir compris ces deux points, examinons maintenant les autres problèmes dérivés de ces deux définitions.
ngx.null
Le premier problème est ngx.null
. Parce que le nil
de Lua ne peut pas être utilisé comme valeur dans une table
, OpenResty introduit ngx.null
comme valeur null
dans la table.
$ resty -e 'print(ngx.null)'
null
$ resty -e 'print(type(ngx.null))'
userdata
Comme vous pouvez le voir dans les deux morceaux de code ci-dessus, ngx.null
est imprimé comme null
, et son type est userdata
, donc peut-il être traité comme une valeur false
? Bien sûr que non. La valeur booléenne de ngx.null
est true
.
$ resty -e 'if ngx.null then
ngx.say("true")
end'
Ainsi, gardez à l'esprit que seuls nil
et false
sont des valeurs false
. Si vous manquez ce point, il est facile de tomber dans des pièges, par exemple, lorsque vous utilisez lua-resty-redis
et faites le jugement suivant :
local res, err = red:get("dog")
if not res then
res = res + "test"
end
Si la valeur de retour res
est nil
, l'appel de fonction a échoué ; si res
est ngx.null
, la clé dog
n'existe pas dans redis, alors le code plante si la clé dog
n'existe pas.
cdata:NULL
Le deuxième problème est cdata:NULL
. Lorsque vous appelez une fonction C via l'interface LuaJIT FFI, et que la fonction renvoie un pointeur NULL
, alors vous rencontrerez un autre type de valeur null
, cdata:NULL
.
$ resty -e 'local ffi = require "ffi"
local cdata_null = ffi.new("void*", nil)
if cdata_null then
ngx.say("true")
end'
Comme ngx.null
, cdata:NULL
est également true
. Mais ce qui est plus déroutant, c'est que le code suivant, qui imprime true
, signifie que cdata:NULL
est équivalent à nil
.
$ resty -e 'local ffi = require "ffi"
local cdata_null = ffi.new("void*", nil)
ngx.say(cdata_null == nil)'
Alors, comment devrions-nous gérer ngx.null
et cdata:NULL
? Ce n'est pas une bonne solution de laisser la couche application s'occuper de ces problèmes. Il est préférable de faire un wrapper de second niveau et de ne pas laisser l'appelant connaître ces détails.
Il est préférable de faire un wrapper de second niveau et de ne pas laisser l'appelant connaître ces détails.
cjson.null
Enfin, examinons les valeurs null
qui apparaissent dans cjson
. La bibliothèque cjson
prend le NULL
dans json, le décode en lightuserdata
Lua, et utilise cjson.null
pour le représenter.
$ resty -e 'local cjson = require "cjson"
local data = cjson.encode(nil)
local decode_null = cjson.decode(data)
ngx.say(decode_null == cjson.null)'
Le nil
de Lua devient cjson.null
après avoir été encodé et décodé par JSON. Comme vous pouvez l'imaginer, il est introduit pour la même raison que ngx.null
, car nil
ne peut pas être utilisé comme valeur dans une table
.
Jusqu'à présent, avez-vous été confus par tant de types de valeurs null
dans OpenResty ? Ne vous inquiétez pas. Relisez cette partie plusieurs fois et organisez-la vous-même, alors vous ne serez plus confus. Bien sûr, nous devons réfléchir davantage à l'avenir pour savoir si cela fonctionne lorsque nous écrivons quelque chose comme if not foo then
.
Résumé
L'article d'aujourd'hui vous a présenté les API Lua couramment utilisées dans OpenResty.
Enfin, je vous laisse avec une question : Dans l'exemple ngx.now
, pourquoi la valeur de ngx.now
n'est-elle pas modifiée lorsqu'il n'y a pas d'opération yield
? N'hésitez pas à partager votre opinion dans les commentaires, et n'hésitez pas à partager cet article pour que nous puissions communiquer et nous améliorer ensemble.