OpenResty est la version améliorée de NGINX avec des requêtes et réponses dynamiques
API7.ai
October 23, 2022
Après l'introduction précédente, vous devez comprendre le concept d'OpenResty et comment l'apprendre. Cet article nous guidera sur la manière dont OpenResty gère les requêtes et les réponses des clients.
Bien qu'OpenResty soit un serveur web basé sur NGINX, il est fondamentalement différent de NGINX : NGINX est piloté par des fichiers de configuration statiques, tandis qu'OpenResty est piloté par l'API Lua, offrant ainsi plus de flexibilité et de programmabilité.
Permettez-moi de vous expliquer les avantages de l'API Lua.
Catégories d'API
Tout d'abord, nous devons savoir que l'API OpenResty est divisée en grandes catégories suivantes.
- Traitement des requêtes et des réponses.
- SSL.
- Dictionnaire partagé.
- Cosocket.
- Gestion du trafic de couche 4.
- Processus et worker.
- Accès aux variables et configurations de NGINX.
- Fonctions générales telles que les chaînes de caractères, le temps, le codage, etc.
Ici, je vous suggère également d'ouvrir la documentation de l'API Lua d'OpenResty et de la consulter par rapport à la liste des API pour voir si vous pouvez relier cette catégorie.
Les API OpenResty existent non seulement dans le projet lua-nginx-module, mais aussi dans le projet lua-resty-core, comme ngx.ssl
, ngx.base64
, ngx.errlog
, ngx.process
, ngx.re.split
, ngx.resp.add_header
, ngx.balancer
, ngx.semaphore
, ngx.ocsp
, et d'autres API.
Pour les API qui ne sont pas dans le projet lua-nginx-module, vous devez les inclure séparément pour les utiliser. Par exemple, si vous souhaitez utiliser la fonction split, vous devez l'appeler comme suit.
$ resty -e 'local ngx_re = require "ngx.re"
local res, err = ngx_re.split("a,b,c,d", ",", nil, {pos = 5})
print(res)
'
Bien sûr, cela peut vous embrouiller : dans le projet lua-nginx-module
, il y a plusieurs API commençant par ngx.re.sub
, ngx.re.find
, etc. Pourquoi est-ce que l'API ngx.re.split
est la seule qui nécessite d'être incluse avant d'être utilisée ?
Comme nous l'avons mentionné dans le chapitre précédent sur lua-resty-core
, les nouvelles API OpenResty sont implémentées dans le dépôt lua-rety-core
via FFI, ce qui entraîne inévitablement un sentiment de fragmentation. J'espère que ce problème sera résolu à l'avenir en fusionnant les projets lua-nginx-module
et lua-resty-core
.
Requête
Ensuite, voyons comment OpenResty gère les requêtes et les réponses des clients. Tout d'abord, regardons l'API pour gérer les requêtes, mais il y a plus de 20 API commençant par ngx.req
, alors par où commencer ?
Nous savons que les messages de requête HTTP sont composés de trois parties : la ligne de requête, l'en-tête de requête et le corps de la requête, donc je vais présenter l'API en fonction de ces trois parties.
Ligne de requête
La première est la ligne de requête, qui contient la méthode de requête, l'URI et la version du protocole HTTP. Dans NGINX, vous pouvez obtenir cette valeur en utilisant une variable intégrée, tandis que dans OpenResty, cela correspond à l'API ngx.var.*
. Regardons deux exemples.
- La variable intégrée
$scheme
, qui représente le nom du protocole dans NGINX, est soithttp
soithttps
; dans OpenResty, vous pouvez utiliserngx.var.scheme
pour obtenir la même valeur. $request_method
représente la méthode de requête commeGET
,POST
, etc. ; dans OpenResty, vous pouvez obtenir la même valeur viangx.var.request_method
.
Vous pouvez consulter la documentation officielle de NGINX pour obtenir une liste complète des variables intégrées de NGINX : http://nginx.org/en/docs/http/ngx_http_core_module.html#variables.
Alors la question se pose : pourquoi OpenResty fournit-il une API séparée pour la ligne de requête alors que vous pouvez obtenir les données de la ligne de requête en retournant la valeur d'une variable comme ngx.var.*
?
Le résultat contient de nombreux facteurs :
- Tout d'abord, il n'est pas recommandé de lire
ngx.var
de manière répétée en raison de ses performances inefficaces. - Deuxièmement, par souci de convivialité pour le programme,
ngx.var
retourne une chaîne de caractères, pas un objet Lua. Il est difficile de gérer lorsque vous obtenezargs
, qui peut retourner plusieurs valeurs. - Troisièmement, du point de vue de la flexibilité, la plupart des
ngx.var
sont en lecture seule, et seules quelques variables sont modifiables, comme$args
etlimit_rate
. Cependant, nous avons souvent besoin de modifier la méthode, l'URI et les args.
Par conséquent, OpenResty fournit plusieurs API dédiées à la manipulation de la ligne de requête, qui peuvent réécrire la ligne de requête pour des opérations ultérieures telles que la redirection.
Voyons comment obtenir le numéro de version du protocole HTTP via l'API. L'API OpenResty ngx.req.http_version
fait la même chose que la variable NGINX $server_protocol
: retourner le numéro de version du protocole HTTP. Cependant, la valeur de retour de cette API n'est pas une chaîne de caractères mais un format numérique, les valeurs possibles étant 2.0
, 1.0
, 1.1
et 0.9
. Nil
est retourné si le résultat est en dehors de cette plage de valeurs.
Voyons la méthode pour obtenir la requête dans la ligne de requête. Comme mentionné, le rôle et la valeur de retour de ngx.req.get_method
et des variables $request_method
de NGINX sont les mêmes : au format chaîne de caractères.
Cependant, le format du paramètre de la méthode de requête HTTP actuelle ngx.req.set_method
n'est pas une chaîne de caractères mais des constantes numériques intégrées. Par exemple, le code suivant réécrit la méthode de requête en POST.
ngx.req.set_method(ngx.HTTP_POST)
Pour vérifier que la constante intégrée ngx.HTTP_POST
est bien un nombre et non une chaîne de caractères, vous pouvez imprimer sa valeur et voir si la sortie est 8.
resty -e 'print(ngx.HTTP_POST)'
De cette manière, la valeur de retour de la méthode get
est une chaîne de caractères, tandis que la valeur d'entrée de la méthode set
est un nombre. Cela fonctionne lorsque la méthode set passe une valeur confuse car l'API peut planter et signaler une erreur 500
. Cependant, dans la logique de jugement suivante :
if (ngx.req.get_method() == ngx.HTTP_POST) then
-- faire quelque chose
end
Ce type de code fonctionne bien, ne signale aucune erreur et est difficile à détecter même lors des revues de code. J'ai déjà fait une erreur similaire et je m'en souviens encore : j'avais déjà passé deux tours de revues de code et des cas de test incomplets pour essayer de la couvrir. Finalement, une anomalie dans l'environnement de production m'a conduit au problème.
Il n'y a pas de moyen pratique de résoudre un tel problème, sauf à être plus prudent ou à ajouter une autre couche d'encapsulation. Lorsque vous concevez votre API métier, vous pouvez également envisager de garder le format de paramètre cohérent des méthodes get
et set
, même si cela nécessite de sacrifier un peu de performance.
De plus, parmi les méthodes pour réécrire la ligne de requête, il y a deux API, ngx.req.set_uri
et ngx.req.set_uri_args
, qui peuvent être utilisées pour réécrire l'URI et les args. Regardons cette configuration NGINX.
rewrite ^ /foo?a=3? break;
Alors, comment pouvons-nous résoudre cela avec l'API Lua équivalente ? La réponse est les deux lignes de code suivantes.
ngx.req.set_uri_args("a=3")
ngx.req.set_uri("/foo")
Si vous avez lu la documentation officielle, vous constaterez que ngx.req.set_uri
a un deuxième paramètre : jump
, qui est "false" par défaut. Si vous le définissez comme "true", cela équivaut à définir le drapeau de la commande rewrite
à last
au lieu de break
dans l'exemple ci-dessus.
Cependant, je n'aime pas trop la configuration des drapeaux de la commande rewrite
car elle est illisible et difficile à reconnaître, et bien moins intuitive et maintenable que le code.
En-tête de requête
Comme nous le savons, les en-têtes de requête HTTP sont au format key : value
, par exemple :
Accept: text/css,*/*;q=0.1
Accept-Encoding: gzip, deflate, br
Dans OpenResty, vous pouvez utiliser ngx.req.get_headers
pour analyser et obtenir les en-têtes de requête, et le type de valeur de retour est une table.
local h, err = ngx.req.get_headers()
if err == "truncated" then
-- on peut choisir d'ignorer ou de rejeter la requête actuelle ici
end
for k, v in pairs(h) do
...
end
Par défaut, il retourne les 100 premiers en-têtes. Si le nombre dépasse 100, il signalera une erreur truncated
, laissant au développeur le soin de décider comment la gérer. Vous vous demandez peut-être pourquoi prendre cette voie, ce que je mentionnerai plus tard dans la section sur les vulnérabilités de sécurité.
Cependant, nous devons noter qu'OpenResty ne fournit pas d'API spécifique pour obtenir un en-tête de requête spécifié, ce qui signifie qu'il n'y a pas de forme comme ngx.req.header['host']
. Si vous avez un tel besoin, vous devez vous appuyer sur la variable NGINX $http_xxx
pour l'obtenir. Ensuite, dans OpenResty, vous pouvez l'obtenir via ngx.var.http_xxx
.
Voyons maintenant comment nous devrions réécrire et supprimer l'en-tête de requête. Les API pour ces deux opérations sont assez intuitives :
ngx.req.set_header("Content-Type", "text/css")
ngx.req.clear_header("Content-Type")
Bien sûr, la documentation officielle mentionne également d'autres méthodes pour supprimer l'en-tête de requête, comme définir la valeur du titre à nil
, etc. Cependant, je recommande toujours d'utiliser clear_header
pour le faire uniformément pour la clarté du code.
Corps de la requête
Enfin, regardons le corps de la requête. Pour des raisons de performance, OpenResty ne lit pas activement le corps de la requête à moins que vous n'activiez la directive lua_need_request_body
dans nginx.conf
. De plus, pour les corps de requête plus volumineux, OpenResty enregistre le contenu dans un fichier temporaire sur le disque, donc le processus de lecture du corps de la requête ressemble à ceci.
ngx.req.read_body()
local data = ngx.req.get_body_data()
if not data then
local tmp_file = ngx.req.get_body_file()
-- io.open(tmp_file)
-- ...
end
Ce code a une opération de blocage IO pour lire le fichier disque. Vous devriez ajuster la configuration de client_body_buffer_size
(16 Ko par défaut sur les systèmes 64 bits) pour minimiser les opérations de blocage ; vous pouvez également configurer client_body_buffer_size
et client_max_body_size
pour qu'ils soient identiques et les gérer entièrement en mémoire, en fonction de la taille de votre mémoire et du nombre de requêtes simultanées que vous prenez.
De plus, le corps de la requête peut être réécrit. Les deux API ngx.req.set_body_data
et ngx.req.set_body_file
acceptent une chaîne de caractères et un fichier disque local comme paramètres d'entrée pour accomplir la réécriture du corps de la requête. Cependant, ce type d'opération est rare, et vous pouvez consulter la documentation pour plus de détails.
Réponse
Après le traitement de la requête, nous devons envoyer une réponse au client. Comme le message de requête, le message de réponse est également composé de plusieurs parties : la ligne d'état, l'en-tête de réponse et le corps de la réponse. Je vais présenter les API correspondantes en fonction de ces trois parties.
Ligne d'état
La principale chose qui nous intéresse dans la ligne d'état est le code d'état. Par défaut, le code d'état HTTP retourné est 200, qui est la constante ngx.HTTP_OK
intégrée dans OpenResty. Mais dans le monde du code, c'est toujours le code qui gère les cas les plus exceptionnels.
Si vous détectez le message de requête et constatez qu'il s'agit d'une requête malveillante, alors vous devez terminer la requête :
ngx.exit(ngx.HTTP_BAD_REQUEST)
Cependant, il y a une constante particulière dans les codes d'état HTTP d'OpenResty : ngx.OK
. Dans le cas de ngx.exit(ngx.OK)
, la requête quitte la phase de traitement actuelle et passe à la phase suivante plutôt que de retourner directement au client.
Bien sûr, vous pouvez également choisir de ne pas quitter et simplement réécrire le code d'état en utilisant ngx.status
, comme écrit dans la manière suivante.
ngx.status = ngx.HTTP_FORBIDDEN
Vous pouvez les consulter dans la documentation si vous souhaitez en savoir plus sur les constantes de code d'état.
En-tête de réponse
En ce qui concerne l'en-tête de réponse, il y a deux façons de le configurer. La première est la plus simple.
ngx.header.content_type = 'text/plain'
ngx.header["X-My-Header"] = 'blah blah'
ngx.header["X-My-Header"] = nil -- supprimer
Ici, ngx.header
contient les informations de l'en-tête de réponse, qui peuvent être lues, modifiées et supprimées.
La deuxième façon de configurer l'en-tête de réponse est ngx_resp.add_header
, du dépôt lua-resty-core
, qui ajoute un message d'en-tête, appelé avec :
local ngx_resp = require "ngx.resp"
ngx_resp.add_header("Foo", "bar")
La différence avec la première méthode est que add_header
ne remplace pas un champ existant du même nom.
Corps de la réponse
Enfin, regardons le corps de la réponse. Dans OpenResty, vous pouvez utiliser ngx.say
et ngx.print
pour afficher le corps de la réponse.
ngx.say('hello, world')
La fonctionnalité des deux API est identique, la seule différence étant que ngx.say
a un saut de ligne à la fin.
Pour éviter l'inefficacité de la concaténation de chaînes de caractères, ngx.say / ngx.print
supporte les formats de chaîne de caractères et de tableau comme paramètres.
$ resty -e 'ngx.say({"hello", ", ", "world"})'
hello, world
Cette méthode saute la concaténation de chaînes de caractères au niveau Lua et laisse les fonctions C s'en occuper.
Résumé
Passons en revue le contenu d'aujourd'hui. Nous avons introduit les API OpenResty associées aux messages de requête et de réponse. Comme vous pouvez le voir, l'API OpenResty est plus flexible et puissante que la directive NGINX.
Par conséquent, l'API Lua fournie par OpenResty est-elle suffisante pour répondre à vos besoins lorsque vous traitez des requêtes HTTP ? Veuillez laisser vos commentaires et partager cet article avec vos collègues et amis afin que nous puissions communiquer et nous améliorer ensemble.