Documentation et cas de test : des outils puissants pour résoudre les problèmes de développement OpenResty
API7.ai
October 23, 2022
Après avoir appris les principes et quelques concepts essentiels d'OpenResty, nous allons enfin commencer à apprendre l'API.
D'après mon expérience personnelle, apprendre l'API d'OpenResty est relativement facile, donc cela ne nécessite pas beaucoup d'articles pour l'introduire. Vous pourriez vous demander : l'API n'est-elle pas la partie la plus courante et essentielle ? Pourquoi ne pas y consacrer beaucoup de temps ? Il y a deux considérations principales.
Premièrement, OpenResty fournit une documentation très détaillée. Comparé à de nombreux autres langages de programmation ou plateformes, OpenResty fournit non seulement les définitions des paramètres de l'API et des valeurs de retour, mais aussi des exemples de code complets et exécutables, vous montrant clairement comment l'API gère diverses conditions limites.
Suivre la définition de l'API avec des exemples de code et des mises en garde est un style cohérent de la documentation d'OpenResty. Par conséquent, après avoir lu la description de l'API, vous pouvez immédiatement exécuter l'exemple de code dans votre environnement et modifier les paramètres et la documentation pour les vérifier et approfondir votre compréhension.
Deuxièmement, OpenResty fournit des cas de test complets. Comme je l'ai mentionné, la documentation d'OpenResty montre des exemples de code d'API. Cependant, en raison de contraintes d'espace, le document ne présente pas les rapports d'erreur et le traitement dans diverses situations anormales et la méthode d'utilisation de plusieurs API.
Mais ne vous inquiétez pas. Vous pouvez trouver la plupart de ces contenus dans l'ensemble des cas de test.
Pour les développeurs OpenResty, les meilleurs matériaux d'apprentissage de l'API sont la documentation officielle et les cas de test, qui sont professionnels et conviviaux pour les lecteurs.
Donnez un poisson à un homme, et vous le nourrissez pour un jour ; apprenez-lui à pêcher et vous le nourrissez pour toute une vie. Utilisons un exemple réel pour expérimenter comment exploiter la puissance de la documentation et de l'ensemble des cas de test dans le développement OpenResty.
Prenons l'API get
de shdict comme exemple
Basé sur la zone de mémoire partagée de NGINX, le dictionnaire partagé (shared dict) est un objet dictionnaire Lua, qui peut accéder aux données à travers plusieurs workers et stocker des données telles que la limitation de débit, le cache, etc. Il y a plus de 20 API liées au dictionnaire partagé - l'API la plus couramment utilisée et cruciale dans OpenResty.
Prenons l'opération get
la plus simple comme exemple ; vous pouvez cliquer sur le lien de documentation pour comparer. L'exemple de code minimal suivant est adapté de la documentation officielle.
http {
lua_shared_dict dogs 10m;
server {
location /demo {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
local v = dogs:get("Jim")
ngx.say(v)
}
}
}
}
Comme note rapide, avant de pouvoir utiliser le dictionnaire partagé dans le code Lua, nous devons ajouter un bloc de mémoire dans nginx.conf
avec la directive lua_shared_dict
, qui est nommé "dogs" et a une taille de 10M. Après avoir modifié nginx.conf
, vous devez redémarrer le processus et y accéder avec un navigateur ou la commande curl
pour voir les résultats.
Cela ne semble-t-il pas un peu fastidieux ? Modifions-le de manière plus simple. Comme vous pouvez le voir, utiliser l'interface CLI resty de cette manière a le même effet que d'intégrer le code dans nginx.conf
.
$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
local v = dogs:get("Jim")
ngx.say(v)
'
Vous savez maintenant comment nginx.conf
et le code Lua fonctionnent ensemble, et vous avez réussi à exécuter les méthodes set et get du dictionnaire partagé. Généralement, la plupart des développeurs s'arrêtent là. Il y a quelques points à noter ici.
- Quelles étapes ne peuvent pas utiliser les API liées à la mémoire partagée ?
- Nous voyons dans l'exemple de code que la fonction get n'a qu'une seule valeur de retour. Alors, quand y aura-t-il plus d'une valeur de retour ?
- Quel est le type d'entrée de la fonction get ? Y a-t-il une limite de longueur ?
Ne sous-estimez pas ces questions ; elles peuvent nous aider à mieux comprendre OpenResty, et je vais vous les expliquer une par une.
Question 1 : Quelles étapes ne peuvent pas utiliser les API liées à la mémoire partagée ?
Regardons la première question. La réponse est simple ; la documentation a une section context
(c'est-à-dire section de contexte) qui liste les environnements dans lesquels l'API peut être utilisée.
context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
Comme vous pouvez le voir, les phases init
et init_worker
ne sont pas incluses, ce qui signifie que l'API get
de la mémoire partagée ne peut pas être utilisée dans ces deux phases. Veuillez noter que chaque API de mémoire partagée peut être utilisée dans différentes phases. Par exemple, l'API set
peut être utilisée dans la phase init
.
Toujours, lisez la documentation lors de son utilisation. Bien sûr, la documentation d'OpenResty contient parfois des erreurs et des omissions, vous devez donc les vérifier avec des tests réels.
Ensuite, modifions l'ensemble de test pour nous assurer que la phase init
peut exécuter l'API get
du dictionnaire partagé.
Comment pouvons-nous trouver l'ensemble de cas de test lié à la mémoire partagée ? Les cas de test d'OpenResty sont tous placés dans le répertoire /t
et nommés régulièrement, c'est-à-dire numéro-auto-incrémenté-nom-de-fonction.t
. Recherchez shdict
, et vous trouverez 043-shdict.t
, l'ensemble de cas de test de la mémoire partagée, qui contient près de 100 cas de test, y compris des tests pour diverses circonstances normales et anormales.
Essayons de modifier le premier cas de test.
Vous pouvez remplacer la phase content
par une phase init
et supprimer le code superflu pour voir si l'interface get
fonctionne. Vous n'avez pas besoin de comprendre comment le cas de test est écrit, organisé et exécuté à ce stade. Vous devez seulement savoir qu'il teste l'interface get
.
=== TEST 1: string key, int value
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
init_by_lua '
local dogs = ngx.shared.dogs
local val = dogs:get("foo")
ngx.say(val)
';
}
--- request
GET /test
--- response_body
32
--- no_error_log
[error]
--- ONLY
Vous avez dû remarquer qu'à la fin du cas de test, j'ai ajouté le drapeau --ONLY
, ce qui signifie ignorer tous les autres cas de test et n'exécuter que celui-ci, améliorant ainsi la vitesse d'exécution. Plus tard dans la section des tests, j'expliquerai spécifiquement les différents tags.
Après la modification, nous pouvons exécuter le cas de test avec la commande prove
.
prove t/043-shdict.t
Ensuite, vous obtiendrez une erreur qui corrobore les limites de phase décrites dans la documentation.
nginx: [emerg] "init_by_lua" directive is not allowed here
Question 2 : Quand la fonction get
a-t-elle plusieurs valeurs de retour ?
Regardons la deuxième question, qui peut être résumée à partir de la documentation officielle. La documentation commence par la description syntax
de cette interface.
value, flags = ngx.shared.DICT:get(key)
Dans des circonstances normales.
- Le premier paramètre
value
renvoie la valeur correspondant à lakey
dans le dictionnaire ; cependant, lorsque lakey
n'existe pas ou expire, la valeurvalue
estnil
. - Le deuxième paramètre,
flags
, est un peu plus compliqué ; si l'interface set définit des flags, elle les renvoie. Sinon, elle ne le fait pas.
Si l'appel de l'API échoue, value
renvoie nil
, et flags
renvoie un message d'erreur spécifique.
D'après les informations résumées dans la documentation, nous pouvons voir que local v = dogs:get("Jim")
est écrit avec un seul paramètre de réception. Une telle écriture est incomplète car elle ne couvre que le scénario d'utilisation typique sans recevoir un deuxième paramètre ou effectuer une gestion des exceptions. Nous pourrions le modifier comme suit.
local data, err = dogs:get("Jim")
if data == nil and err then
ngx.say("get not ok: ", err)
return
end
Comme pour la première question, nous pouvons rechercher l'ensemble de cas de test pour confirmer notre compréhension de la documentation.
=== TEST 65: get nil key
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
content_by_lua '
local dogs = ngx.shared.dogs
local ok, err = dogs:get(nil)
if not ok then
ngx.say("not ok: ", err)
return
end
ngx.say("ok")
';
}
--- request
GET /test
--- response_body
not ok: nil key
--- no_error_log
[error]
Dans ce cas de test, l'interface get
a une entrée nil
, et le message d'erreur renvoyé est nil key
. Cela vérifie que notre analyse de la documentation est correcte et fournit une réponse partielle à la troisième question. Au moins, l'entrée de get ne peut pas être nil.
Question 3 : Quel est le type d'entrée de la fonction get
?
Quant à la troisième question, quel type de paramètres d'entrée get
peut-il accepter ? Vérifions d'abord la documentation, mais malheureusement, vous constaterez que la documentation ne spécifie pas quels sont les types de clés légaux. Que devons-nous faire ?
Ne vous inquiétez pas. Au moins, nous savons que la key
peut être de type chaîne de caractères et ne peut pas être nil. Vous souvenez-vous des types de données en Lua ? En plus des chaînes de caractères et de nil, il y a les nombres, les tableaux, les types booléens et les fonctions. Les deux derniers sont inutiles comme clés, donc nous n'avons besoin de vérifier que les deux premiers : les nombres et les tableaux. Nous pouvons commencer par rechercher dans le fichier de test les cas où les nombres sont utilisés comme key
.
=== TEST 4: number keys, string values
Avec ce cas de test, vous pouvez voir que les nombres peuvent également être utilisés comme clés, et qu'ils seront convertis en chaînes de caractères en interne. Qu'en est-il des tableaux ? Malheureusement, le cas de test ne le couvre pas, nous devons donc l'essayer nous-mêmes.
$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
dogs:get({})
'
Sans surprise, l'erreur suivante a été signalée.
ERROR: (command line -e):2: bad argument #1 to 'get' (string expected, got table)
En résumé, nous pouvons conclure que les types de key
acceptés par l'API get
sont les chaînes de caractères et les nombres.
Y a-t-il une limite de longueur pour la clé entrante ? Il y a un cas de test correspondant ici.
=== TEST 67: get a too-long key
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
content_by_lua '
local dogs = ngx.shared.dogs
local ok, err = dogs:get(string.rep("a", 65536))
if not ok then
ngx.say("not ok: ", err)
return
end
ngx.say("ok")
';
}
--- request
GET /test
--- response_body
not ok: key too long
--- no_error_log
[error]
Lorsque la longueur de la chaîne est de 65536, vous serez informé que la clé est trop longue. Vous pouvez essayer de changer la longueur à 65535, bien que seulement 1 octet de moins, mais il n'y aura plus d'erreurs. Cela signifie que la longueur maximale de la clé est exactement de 65535.
Résumé
Enfin, je tiens à vous rappeler que dans l'API OpenResty, toute valeur de retour avec un message d'erreur doit avoir une variable pour la recevoir et effectuer une gestion des erreurs, sinon cela fera une erreur. Par exemple, si la mauvaise connexion est mise dans le pool de connexions, ou si l'appel de l'API échoue pour continuer la logique derrière, cela fait que les gens se plaignent sans cesse.
Donc, si vous rencontrez un problème lorsque vous écrivez du code OpenResty, quelle est votre manière habituelle de le résoudre ? Est-ce la documentation, les listes de diffusion, ou d'autres canaux ?
N'hésitez pas à partager cet article avec vos collègues et amis afin que nous puissions communiquer et nous améliorer.