Aller au contenu

httpipe: pilote cosocket HTTP Lua pour nginx-module-lua, les interfaces sont plus flexibles

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-httpipe

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

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

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

Ce document décrit lua-resty-httpipe v0.5 publié le 25 novembre 2015.


Pilote cosocket HTTP Lua pour OpenResty / ngx_lua.

Caractéristiques

  • HTTP 1.0/1.1 et HTTPS
  • Conception d'interface flexible
  • Lecteur de flux et téléchargements
  • Corps de requête / réponse encodé en morceaux
  • Définit le délai d'attente pour les opérations de lecture et d'envoi
  • Limite la taille maximale du corps de réponse
  • Keepalive

Synopsis

server {

  listen 9090;

  location /echo {
    content_by_lua '
      local raw_header = ngx.req.raw_header()

      if ngx.req.get_method() == "GET" then
          ngx.header["Content-Length"] = #raw_header
      end

      ngx.req.read_body()
      local body, err = ngx.req.get_body_data()

      ngx.print(raw_header)
      ngx.print(body)
    ';
  }

  location /simple {
    content_by_lua '
      local httpipe = require "resty.httpipe"

      local hp, err = httpipe:new()
      if not hp then
          ngx.log(ngx.ERR, "échec de la création de httpipe: ", err)
          return ngx.exit(503)
      end

      hp:set_timeout(5 * 1000) -- 5 sec

      local res, err = hp:request("127.0.0.1", 9090, {
                                     method = "GET", path = "/echo" })
      if not res then
          ngx.log(ngx.ERR, "échec de la requête: ", err)
          return ngx.exit(503)
      end

      ngx.status = res.status

      for k, v in pairs(res.headers) do
          ngx.header[k] = v
      end

      ngx.say(res.body)
    ';
  }

  location /generic {
    content_by_lua '
      local cjson = require "cjson"
      local httpipe = require "resty.httpipe"

      local hp, err = httpipe:new(10) -- chunk_size = 10
      if not hp then
          ngx.log(ngx.ERR, "échec de la création de httpipe: ", err)
          return ngx.exit(503)
      end

      hp:set_timeout(5 * 1000) -- 5 sec

      local ok, err = hp:connect("127.0.0.1", 9090)
      if not ok then
          ngx.log(ngx.ERR, "échec de la connexion: ", err)
          return ngx.exit(503)
      end

      local ok, err = hp:send_request{ method = "GET", path = "/echo" }
      if not ok then
          ngx.log(ngx.ERR, "échec de l'envoi de la requête: ", err)
          return ngx.exit(503)
      end

      -- analyseur de flux complet

      while true do
          local typ, res, err = hp:read()
          if not typ then
              ngx.say("échec de la lecture: ", err)
              return
          end

          ngx.say("lu: ", cjson.encode({typ, res}))

          if typ == 'eof' then
              break
          end
      end
    ';
  }

  location /advanced {
    content_by_lua '
      local httpipe = require "resty.httpipe"

      local hp, err = httpipe:new()

      hp:set_timeout(5 * 1000) -- 5 sec

      local r0, err = hp:request("127.0.0.1", 9090, {
                                     method = "GET", path = "/echo",
                                     stream = true })

      -- d'un flux http à un autre, comme un pipe unix

      local pipe = r0.pipe

      pipe:set_timeout(5 * 1000) -- 5 sec

      --[[
          local headers = {["Content-Length"] = r0.headers["Content-Length"]}
          local r1, err = pipe:request("127.0.0.1", 9090, {
                                           method = "POST", path = "/echo",
                                           headers = headers,
                                           body = r0.body_reader })
      --]]
      local r1, err = pipe:request("127.0.0.1", 9090, {
                                       method = "POST", path = "/echo" })

      ngx.status = r1.status

      for k, v in pairs(r1.headers) do
          ngx.header[k] = v
      end

      ngx.say(r1.body)
    ';
  }

}

Une sortie typique de l'emplacement /simple défini ci-dessus est :

GET /echo HTTP/1.1
Host: 127.0.0.1
User-Agent: Resty/HTTPipe-1.00
Accept: */*

Une sortie typique de l'emplacement /generic défini ci-dessus est :

read: ["statusline","200"]
read: ["header",["Server","openresty\/1.5.12.1","Server: openresty\/1.5.12.1"]]
read: ["header",["Date","Tue, 10 Jun 2014 07:29:57 GMT","Date: Tue, 10 Jun 2014 07:29:57 GMT"]]
read: ["header",["Content-Type","text\/plain","Content-Type: text\/plain"]]
read: ["header",["Connection","keep-alive","Connection: keep-alive"]]
read: ["header",["Content-Length","84","Content-Length: 84"]]
read: ["header_end"]
read: ["body","GET \/echo "]
read: ["body","HTTP\/1.1\r\n"]
read: ["body","Host: 127."]
read: ["body","0.0.1\r\nUse"]
read: ["body","r-Agent: R"]
read: ["body","esty\/HTTPi"]
read: ["body","pe-1.00\r\nA"]
read: ["body","ccept: *\/*"]
read: ["body","\r\n\r\n"]
read: ["body_end"]
read: ["eof"]

Une sortie typique de l'emplacement /advanced défini ci-dessus est :

POST /echo HTTP/1.1
Content-Length: 84
User-Agent: Resty/HTTPipe-1.00
Accept: */*
Host: 127.0.0.1

GET /echo HTTP/1.1
Host: 127.0.0.1
User-Agent: Resty/HTTPipe-1.00
Accept: */*

Connexion

new

syntax: hp, err = httpipe:new(chunk_size?, sock?)

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

L'argument chunk_size spécifie la taille du tampon utilisée par les opérations de lecture cosocket. Par défaut, c'est 8192.

connect

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

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

Tente de se connecter au serveur web.

Avant de résoudre réellement le nom d'hôte et de se connecter au backend distant, cette méthode cherchera toujours dans le pool de connexions les connexions inactives correspondantes créées par des appels précédents de cette méthode.

Une table Lua optionnelle peut être spécifiée comme dernier argument à cette méthode pour spécifier diverses options de connexion :

  • pool : Spécifie un nom personnalisé pour le pool de connexions utilisé. Si omis, le nom du pool de connexions sera généré à partir du modèle de chaîne <host>:<port> ou <unix-socket-path>.

set_timeout

syntax: hp:set_timeout(time)

Définit le délai d'attente (en ms) pour les opérations suivantes, y compris la méthode connect.

ssl_handshake

syntax: hp:ssl_handshake(reused_session?, server_name?, ssl_verify?)

Effectue la poignée de main SSL/TLS sur la connexion actuellement établie.

Voir plus : http://wiki.nginx.org/HttpLuaModule#tcpsock:sslhandshake

set_keepalive

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

Tente de placer la connexion actuelle dans le pool de connexions cosocket ngx_lua.

Remarque Normalement, cela sera appelé automatiquement après le traitement de la requête. En d'autres termes, nous ne pouvons pas libérer la connexion vers le pool à moins que vous ne consommiez toutes les données.

Vous pouvez spécifier le délai d'attente maximal inactif (en ms) lorsque la connexion est dans le pool et la taille maximale du pool pour chaque processus de travail nginx.

En cas de succès, retourne 1. En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

get_reused_times

syntax: times, err = hp:get_reused_times()

Cette méthode retourne le nombre de fois (avec succès) que la connexion actuelle a été réutilisée. En cas d'erreur, elle retourne nil et une chaîne décrivant l'erreur.

Si la connexion actuelle ne provient pas du pool de connexions intégré, alors cette méthode retourne toujours 0, c'est-à-dire que la connexion n'a jamais été réutilisée (jusqu'à présent). Si la connexion provient du pool de connexions, alors la valeur de retour est toujours non nulle. Ainsi, cette méthode peut également être utilisée pour déterminer si la connexion actuelle provient du pool.

close

syntax: ok, err = hp:close()

Ferme la connexion actuelle et retourne le statut.

En cas de succès, retourne 1. En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

Demande

request

syntax: res, err = hp:request(opts?)

syntax: res, err = hp:request(host, port, opts?)

syntax: res, err = hp:request("unix:/path/to/unix-domain.socket", opts?)

La table opts accepte les champs suivants :

  • version: Définit la version HTTP. Utilisez 10 pour HTTP/1.0 et 11 pour HTTP/1.1. Par défaut, c'est 11.
  • method: La chaîne de méthode HTTP. Par défaut, c'est GET.
  • path: La chaîne de chemin. Par défaut, c'est /.
  • query: Spécifie les paramètres de requête. Accepte soit une chaîne soit une table Lua.
  • headers: Une table d'en-têtes de requête. Accepte une table Lua.
  • body: Le corps de la requête sous forme de chaîne, ou une fonction d'itérateur.
  • read_timeout: Définit le délai d'attente en millisecondes pour les opérations de lecture réseau spécifiquement.
  • send_timeout: Définit le délai d'attente en millisecondes pour les opérations d'envoi réseau spécifiquement.
  • stream: S'il est défini sur true, retourne un objet res.body_reader itérable au lieu de res.body.
  • maxsize: Définit la taille maximale en octets à récupérer. Un corps de réponse plus grand que cela provoquera le retour d'une erreur exceeds maxsize. Par défaut, c'est nil, ce qui signifie pas de limite.
  • ssl_verify: Une valeur booléenne Lua pour contrôler si une vérification SSL doit être effectuée.

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

  • res.status (nombre) : Le statut de la réponse, par exemple 200
  • res.headers (table) : Une table Lua avec les en-têtes de réponse.
  • res.body (chaîne) : Le corps de réponse brut.
  • res.body_reader (fonction) : Une fonction d'itérateur pour lire le corps de manière continue.
  • res.pipe (httpipe) : Un nouveau pipe http qui utilise le body_reader actuel comme corps d'entrée par défaut.

Remarque Tous les en-têtes (requête et réponse) sont normalisés pour la capitalisation - par exemple, Accept-Encoding, ETag, Foo-Bar, Baz - dans le "standard" HTTP normal.

En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

request_uri

syntax: res, err = hp:request_uri(uri, opts?)

L'interface simple. Les options fournies dans la table opts sont les mêmes que dans l'interface générique et remplaceront les composants trouvés dans l'uri elle-même.

Retourne un objet res identique à la méthode hp:request.

En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

res.body_reader

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

local reader = res.body_reader

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

  if chunk then
    -- traiter
  end
until not chunk

send_request

syntax: ok, err = hp:send_request(opts?)

En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

read_response

syntax: local res, err = hp:read_response(callback?)

La table callback accepte les champs suivants :

  • header_filter: Une fonction de rappel pour filtrer les en-têtes de réponse
local res, err = hp:read_response{
    header_filter = function (status, headers)
        if status == 200 then
            return 1
        end
end }
  • body_filter: Une fonction de rappel pour filtrer le corps de réponse
local res, err = hp:read_response{
    body_filter = function (chunk)
        ngx.print(chunk)
    end
}

De plus, il n'y a pas de possibilité de diffuser le corps de réponse dans cette méthode. Si la réponse est réussie, res contiendra les champs suivants : res.status, res.headers, res.body.

Remarque Lorsque vous retournez true dans la fonction de rappel, le processus de filtrage sera interrompu.

En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

read

syntax: local typ, res, err = hp:read()

Analyseur de flux pour la réponse complète.

L'utilisateur doit simplement appeler la méthode read de manière répétée jusqu'à ce qu'un type de jeton nil soit retourné. Pour chaque jeton retourné par la méthode read, vérifiez simplement la première valeur de retour pour le type de jeton actuel. Le type de jeton peut être statusline, header, header_end, body, body_end et eof. Pour le format de la valeur res, veuillez vous référer à l'exemple ci-dessus. Par exemple, plusieurs jetons de corps contenant chacun un morceau de données de corps, donc la valeur res est égale au morceau de données du corps.

En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

eof

syntax: local eof = hp:eof()

Si retourne true indiquant que toutes les données ont déjà été consommées ; sinon, la requête n'est toujours pas terminée, vous devez appeler hp:close pour fermer la connexion de force.

Utilitaire

parse_uri

syntax: local scheme, host, port, path, args = unpack(hp:parse_uri(uri))

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.

get_client_body_reader

syntax: reader, err = hp:get_client_body_reader(chunk_size?)

Retourne une fonction d'itérateur qui peut être utilisée pour lire le corps de la requête du client en continu. Par exemple :

local req_reader = hp:get_client_body_reader()

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

  if chunk then
    -- traiter
  end
until not chunk

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

local client_body_reader, err = hp:get_client_body_reader()

local res, err = hp:request{
   path = "/helloworld",
   body = client_body_reader,
}

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-httpipe.