Aller au contenu

http: pilote cosocket HTTP Lua pour nginx-module-lua

Installation

Si vous n'avez pas configuré l'abonnement au dépôt RPM, inscrivez-vous. Ensuite, vous pouvez procéder avec les étapes suivantes.

CentOS/RHEL 7 ou Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-http

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-http

Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.

Ce document décrit lua-resty-http v0.17.2 publié le 29 février 2024.


Pilote cosocket HTTP Lua pour OpenResty / ngx_lua.

Fonctionnalités

  • HTTP 1.0 et 1.1
  • SSL
  • Interface de streaming pour le corps de la réponse, pour une utilisation mémoire prévisible
  • Interface alternative simple pour des requêtes à usage unique sans étape de connexion manuelle
  • Encodages de transfert en morceaux et non en morceaux
  • Connexions keepalives
  • Pipelining des requêtes
  • Trailers
  • Connexions proxy HTTP
  • mTLS (nécessite ngx_lua_http_module >= v0.10.23)

API

Obsolète

Ces méthodes peuvent être supprimées dans de futures versions.

Utilisation

Il existe deux modes de fonctionnement de base :

  1. Requêtes simples à usage unique qui ne nécessitent aucune gestion manuelle de la connexion mais qui mettent en mémoire tampon l'ensemble de la réponse et laissent la connexion soit fermée, soit de retour dans le pool de connexions.

  2. Requêtes en streaming où la connexion est établie séparément, puis la requête est envoyée, le flux du corps est lu par morceaux, et enfin la connexion est fermée manuellement ou maintenue active. Cette technique nécessite un peu plus de code mais permet de rejeter des corps de réponse potentiellement volumineux du côté Lua, ainsi que de pipeliner plusieurs requêtes sur une seule connexion.

Requête à usage unique

local httpc = require("resty.http").new()

-- Les requêtes à usage unique utilisent l'interface `request_uri`.
local res, err = httpc:request_uri("http://example.com/helloworld", {
    method = "POST",
    body = "a=1&b=2",
    headers = {
        ["Content-Type"] = "application/x-www-form-urlencoded",
    },
})
if not res then
    ngx.log(ngx.ERR, "échec de la requête : ", err)
    return
end

-- À ce stade, l'ensemble de la requête / réponse est complet et la connexion
-- sera fermée ou de retour dans le pool de connexions.

-- La table `res` contient les champs `status`, `headers` et `body` attendus.
local status = res.status
local length = res.headers["Content-Length"]
local body   = res.body

Requête en streaming

local httpc = require("resty.http").new()

-- D'abord établir une connexion
local ok, err, ssl_session = httpc:connect({
    scheme = "https",
    host = "127.0.0.1",
    port = 8080,
})
if not ok then
    ngx.log(ngx.ERR, "échec de la connexion : ", err)
    return
end

-- Ensuite, envoyer en utilisant `request`, en fournissant un chemin et un
-- en-tête `Host` au lieu d'une URI complète.
local res, err = httpc:request({
    path = "/helloworld",
    headers = {
        ["Host"] = "example.com",
    },
})
if not res then
    ngx.log(ngx.ERR, "échec de la requête : ", err)
    return
end

-- À ce stade, le statut et les en-têtes seront disponibles pour être utilisés dans la table `res`,
-- mais le corps et les trailers seront encore sur le fil.

-- Nous pouvons utiliser l'itérateur `body_reader`, pour streamer le corps selon notre
-- taille de tampon souhaitée.
local reader = res.body_reader
local buffer_size = 8192

repeat
    local buffer, err = reader(buffer_size)
    if err then
        ngx.log(ngx.ERR, err)
        break
    end

    if buffer then
        -- traiter
    end
until not buffer

local ok, err = httpc:set_keepalive()
if not ok then
    ngx.say("échec de la mise en keepalive : ", err)
    return
end

-- À ce stade, la connexion sera soit de retour en toute sécurité dans le pool, soit fermée.

Connexion

new

syntax: httpc, err = http.new()

Crée l'objet de connexion HTTP. En cas d'échec, retourne nil et une chaîne décrivant l'erreur.

connect

syntax: ok, err, ssl_session = httpc:connect(options)

Tente de se connecter au serveur web tout en incorporant les activités suivantes :

  • Connexion TCP
  • Négociation SSL
  • Configuration du proxy HTTP

Ce faisant, il créera un nom de pool de connexions distinct qui est sûr à utiliser avec des connexions basées sur SSL et / ou proxy, et par conséquent cette syntaxe est fortement recommandée par rapport à l'originale (maintenant obsolète) syntaxe de connexion uniquement TCP.

La table d'options a les champs suivants :

  • scheme: schéma à utiliser, ou nil pour un socket de domaine unix
  • host: hôte cible, ou chemin vers un socket de domaine unix
  • port: port sur l'hôte cible, par défaut 80 ou 443 selon le schéma
  • pool: nom de pool de connexion personnalisé. Option selon la documentation OpenResty, sauf que le défaut deviendra un nom de pool construit à l'aide des propriétés SSL / proxy, ce qui est important pour une réutilisation sûre des connexions. En cas de doute, laissez-le vide !
  • pool_size: option selon la documentation OpenResty
  • backlog: option selon la documentation OpenResty
  • proxy_opts: sous-table, par défaut aux options proxy globales définies, voir set_proxy_options.
  • ssl_reused_session: option selon la documentation OpenResty
  • ssl_verify: option selon la documentation OpenResty, sauf qu'elle est par défaut true.
  • ssl_server_name: option selon la documentation OpenResty
  • ssl_send_status_req: option selon la documentation OpenResty
  • ssl_client_cert: sera passé à tcpsock:setclientcert. Nécessite ngx_lua_http_module >= v0.10.23.
  • ssl_client_priv_key: comme ci-dessus.

set_timeout

syntax: httpc:set_timeout(time)

Définit le délai d'attente du socket (en ms) pour les opérations suivantes. Voir set_timeouts ci-dessous pour une approche plus déclarative.

set_timeouts

syntax: httpc:set_timeouts(connect_timeout, send_timeout, read_timeout)

Définit le seuil de délai d'attente de connexion, le seuil de délai d'attente d'envoi et le seuil de délai d'attente de lecture, respectivement, en millisecondes, pour les opérations de socket suivantes (connecter, envoyer, recevoir, et itérateurs retournés par receiveuntil).

set_keepalive

syntax: ok, err = httpc:set_keepalive(max_idle_timeout, pool_size)

Place soit la connexion actuelle dans le pool pour une réutilisation future, soit ferme la connexion. Appeler cela au lieu de close est "sûr" en ce sens qu'il fermera conditionnellement en fonction du type de requête. En particulier, une requête 1.0 sans Connection: Keep-Alive sera fermée, tout comme une requête 1.1 avec Connection: Close.

En cas de succès, retourne 1. En cas d'erreurs, retourne nil, err. Dans le cas où la connexion est conditionnellement fermée comme décrit ci-dessus, retourne 2 et la chaîne d'erreur la connexion doit être fermée, afin de distinguer des erreurs inattendues.

Voir la documentation OpenResty pour la documentation des paramètres.

set_proxy_options

syntax: httpc:set_proxy_options(opts)

Configure un proxy HTTP à utiliser avec cette instance de client. La table opts attend les champs suivants :

  • http_proxy: une URI vers un serveur proxy à utiliser avec les requêtes HTTP
  • http_proxy_authorization: une valeur d'en-tête par défaut Proxy-Authorization à utiliser avec http_proxy, par exemple Basic ZGVtbzp0ZXN0, qui sera remplacée si l'en-tête de requête Proxy-Authorization est présent.
  • https_proxy: une URI vers un serveur proxy à utiliser avec les requêtes HTTPS
  • https_proxy_authorization: comme http_proxy_authorization mais à utiliser avec https_proxy (puisque avec HTTPS l'autorisation est effectuée lors de la connexion, celle-ci ne peut pas être remplacée en passant l'en-tête de requête Proxy-Authorization).
  • no_proxy: une liste séparée par des virgules d'hôtes qui ne doivent pas être proxyfiés.

Notez que cette méthode n'a aucun effet lors de l'utilisation de la syntaxe de connexion obsolète TCP uniquement.

get_reused_times

syntax: times, err = httpc:get_reused_times()

Voir la documentation OpenResty.

close

syntax: ok, err = httpc:close()

Voir la documentation OpenResty.

Demande

request

syntax: res, err = httpc:request(params)

Envoie une requête HTTP sur une connexion déjà établie. Retourne une table res ou nil et un message d'erreur.

La table params attend les champs suivants :

  • version: Le numéro de version HTTP. Par défaut 1.1.
  • method: La chaîne de méthode HTTP. Par défaut GET.
  • path: La chaîne de chemin. Par défaut /.
  • query: La chaîne de requête, présentée soit comme une chaîne littérale soit comme une table Lua.
  • headers: Une table d'en-têtes de requête.
  • body: Le corps de la requête sous forme de chaîne, de table de chaînes, ou d'une fonction itératrice produisant des chaînes jusqu'à nil lorsqu'elle est épuisée. Notez que vous devez spécifier un Content-Length pour le corps de la requête, ou spécifier Transfer-Encoding: chunked et faire en sorte que votre fonction implémente l'encodage. Voir aussi : get_client_body_reader.

Lorsque la requête est réussie, res contiendra les champs suivants :

  • status: Le code de statut.
  • reason: La phrase de raison du statut.
  • headers: Une table d'en-têtes. Plusieurs en-têtes avec le même nom de champ seront présentés sous forme de table de valeurs.
  • has_body: Un indicateur booléen indiquant s'il y a un corps à lire.
  • body_reader: Une fonction itératrice pour lire le corps de manière continue.
  • read_body: Une méthode pour lire l'ensemble du corps dans une chaîne.
  • read_trailers: Une méthode pour fusionner tous les trailers sous la table res.headers, après avoir lu le corps.

Si la réponse a un corps, alors avant que la même connexion puisse être utilisée pour une autre requête, vous devez lire le corps en utilisant read_body ou body_reader.

request_uri

syntax: res, err = httpc:request_uri(uri, params)

L'interface à usage unique (voir utilisation). Puisque cette méthode effectue une requête complète de bout en bout, les options spécifiées dans params peuvent inclure tout ce qui se trouve dans connect et request documentées ci-dessus. Notez également que les champs path, et query, dans params remplaceront les composants pertinents de l'uri si spécifiés (scheme, host, et port seront toujours pris de l'uri).

Il y a 3 paramètres supplémentaires pour contrôler les keepalives :

  • keepalive: Défini sur false pour désactiver les keepalives et fermer immédiatement la connexion. Par défaut true.
  • keepalive_timeout: Le délai d'attente maximal d'inactivité (ms). Par défaut lua_socket_keepalive_timeout.
  • keepalive_pool: Le nombre maximal de connexions dans le pool. Par défaut lua_socket_pool_size.

Si la requête est réussie, res contiendra les champs suivants :

  • status: Le code de statut.
  • headers: Une table d'en-têtes.
  • body: L'ensemble du corps de la réponse sous forme de chaîne.

request_pipeline

syntax: responses, err = httpc:request_pipeline(params)

Cette méthode fonctionne comme la méthode request ci-dessus, mais params est plutôt une table imbriquée de tables de paramètres. Chaque requête est envoyée dans l'ordre, et responses est retourné sous forme de table de gestionnaires de réponse. Par exemple :

local responses = httpc:request_pipeline({
    { path = "/b" },
    { path = "/c" },
    { path = "/d" },
})

for _, r in ipairs(responses) do
    if not r.status then
        ngx.log(ngx.ERR, "erreur de lecture du socket")
        break
    end

    ngx.say(r.status)
    ngx.say(r:read_body())
end

En raison de la nature du pipelining, aucune réponse n'est réellement lue jusqu'à ce que vous tentiez d'utiliser les champs de réponse (statut / en-têtes, etc.). Et puisque les réponses sont lues dans l'ordre, vous devez lire l'ensemble du corps (et tous les trailers si vous en avez) avant de tenter de lire la prochaine réponse.

Notez que cela n'exclut pas l'utilisation de l'itérateur de corps de réponse en streaming. Les réponses peuvent toujours être streamées, tant que l'ensemble du corps est streamé avant de tenter d'accéder à la prochaine réponse.

Assurez-vous de tester au moins un champ (tel que le statut) avant d'essayer d'utiliser les autres, au cas où une erreur de lecture de socket se serait produite.

Réponse

res.body_reader

L'itérateur body_reader peut être utilisé pour streamer le corps de la réponse en tailles de morceaux de votre choix, comme suit :

local reader = res.body_reader
local buffer_size = 8192

repeat
    local buffer, err = reader(buffer_size)
    if err then
        ngx.log(ngx.ERR, err)
        break
    end

    if buffer then
        -- traiter
    end
until not buffer

Si le lecteur est appelé sans arguments, le comportement dépend du type de connexion. Si la réponse est encodée comme chunked, alors l'itérateur renverra les morceaux au fur et à mesure de leur arrivée. Sinon, il renverra simplement l'ensemble du corps.

Notez que la taille fournie est en fait une taille maximale. Donc dans le cas du transfert en morceaux, vous pouvez obtenir des tampons plus petits que la taille demandée, en raison du reste des morceaux réellement encodés.

res:read_body

syntax: body, err = res:read_body()

Lit l'ensemble du corps dans une chaîne locale.

res:read_trailers

syntax: res:read_trailers()

Cela fusionne tous les trailers sous la table res.headers elle-même. Doit être appelé après avoir lu le corps.

Utilitaire

parse_uri

syntax: local scheme, host, port, path, query? = unpack(httpc:parse_uri(uri, query_in_path?))

C'est une fonction de commodité permettant d'utiliser plus facilement l'interface générique, lorsque les données d'entrée sont une URI.

À partir de la version 0.10, le paramètre optionnel query_in_path a été ajouté, qui spécifie si la chaîne de requête doit être incluse dans la valeur de retour path, ou séparément comme sa propre valeur de retour. Cela est par défaut true afin de maintenir la compatibilité ascendante. Lorsqu'il est défini sur false, path n'inclura que le chemin, et query contiendra les arguments de l'URI, sans inclure le délimiteur ?.

get_client_body_reader

syntax: reader, err = httpc:get_client_body_reader(chunksize?, sock?)

Retourne une fonction itératrice qui peut être utilisée pour lire le corps de la requête client en aval de manière continue. Vous pouvez également spécifier une taille de morceau par défaut optionnelle (par défaut 65536), ou un socket déjà établi à la place de la requête client.

Exemple :

local req_reader = httpc:get_client_body_reader()
local buffer_size = 8192

repeat
    local buffer, err = req_reader(buffer_size)
    if err then
        ngx.log(ngx.ERR, err)
        break
    end

    if buffer then
        -- traiter
    end
until not buffer

Cet itérateur peut également être utilisé comme valeur pour le champ body dans les paramètres de requête, permettant de streamer le corps de la requête dans une requête proxy en amont.

local client_body_reader, err = httpc:get_client_body_reader()

local res, err = httpc:request({
    path = "/helloworld",
    body = client_body_reader,
})

Obsolète

Ces fonctionnalités restent pour la compatibilité ascendante, mais peuvent être supprimées dans de futures versions.

Connexion uniquement TCP

Les versions suivantes de la signature de méthode connect sont obsolètes en faveur de l'argument unique table documenté ci-dessus.

syntax: ok, err = httpc:connect(host, port, options_table?)

syntax: ok, err = httpc:connect("unix:/path/to/unix.sock", options_table?)

REMARQUE : le nom de pool par défaut n'incorporera que des informations IP et port, il est donc dangereux de l'utiliser en cas de connexions SSL et/ou Proxy. Spécifiez votre propre pool ou, mieux encore, n'utilisez pas ces signatures.

connect_proxy

syntax: ok, err = httpc:connect_proxy(proxy_uri, scheme, host, port, proxy_authorization)

Appeler cette méthode manuellement n'est plus nécessaire puisqu'elle est incorporée dans connect. Elle est conservée pour l'instant pour la compatibilité avec les utilisateurs de l'ancienne syntaxe connexion uniquement TCP.

Tente de se connecter au serveur web via le serveur proxy donné. La méthode accepte les arguments suivants :

  • proxy_uri - URI complète du serveur proxy à utiliser (par exemple http://proxy.example.com:3128/). Remarque : seul le protocole http est pris en charge.
  • scheme - Le protocole à utiliser entre le serveur proxy et l'hôte distant (http ou https). Si https est spécifié comme schéma, connect_proxy() effectue une requête CONNECT pour établir un tunnel TCP vers l'hôte distant via le serveur proxy.
  • host - Le nom d'hôte de l'hôte distant auquel se connecter.
  • port - Le port de l'hôte distant auquel se connecter.
  • proxy_authorization - La valeur de l'en-tête Proxy-Authorization envoyée au serveur proxy via CONNECT lorsque le scheme est https.

S'il y a une erreur lors de la tentative de connexion, cette méthode retourne nil avec une chaîne décrivant l'erreur. Si la connexion a été établie avec succès, la méthode retourne 1.

Il y a quelques points clés à garder à l'esprit lors de l'utilisation de cette API :

  • Si le schéma est https, vous devez effectuer la négociation TLS avec le serveur distant manuellement en utilisant la méthode ssl_handshake() avant d'envoyer des requêtes via le tunnel proxy.
  • Si le schéma est http, vous devez vous assurer que les requêtes que vous envoyez via les connexions respectent RFC 7230 et en particulier Section 5.3.2. qui stipule que la cible de la requête doit être sous forme absolue. En pratique, cela signifie que lorsque vous utilisez send_request(), le path doit être une URI absolue vers la ressource (par exemple http://example.com/index.html au lieu de simplement /index.html).

ssl_handshake

syntax: session, err = httpc:ssl_handshake(session, host, verify)

Appeler cette méthode manuellement n'est plus nécessaire puisqu'elle est incorporée dans connect. Elle est conservée pour l'instant pour la compatibilité avec les utilisateurs de l'ancienne syntaxe connexion uniquement TCP.

Voir la documentation OpenResty.

proxy_request / proxy_response

Ces deux méthodes de commodité étaient simplement destinées à démontrer un cas d'utilisation courant de la mise en œuvre du proxy inverse, et l'auteur regrette leur inclusion dans le module. Les utilisateurs sont encouragés à créer leurs propres solutions plutôt que de dépendre de ces fonctions, qui peuvent être supprimées dans une version ultérieure.

proxy_request

syntax: local res, err = httpc:proxy_request(request_body_chunk_size?)

Effectue une requête en utilisant les arguments de la requête client actuelle, effectuant effectivement un proxy vers l'amont connecté. Le corps de la requête sera lu de manière continue, selon request_body_chunk_size (voir documentation sur le lecteur de corps client ci-dessous).

proxy_response

syntax: httpc:proxy_response(res, chunksize?)

Définit la réponse actuelle en fonction du res donné. S'assure que les en-têtes hop-by-hop ne sont pas envoyés en aval, et lira la réponse selon chunksize (voir documentation sur le lecteur de corps ci-dessus).

GitHub

Vous pouvez trouver des conseils de configuration supplémentaires et de la documentation pour ce module dans le dépôt GitHub pour nginx-module-http.