Saltar a contenido

httpipe: controlador cosocket HTTP de Lua para nginx-module-lua, las interfaces son más flexibles

Instalación

Si no has configurado la suscripción al repositorio RPM, regístrate. Luego puedes proceder con los siguientes pasos.

CentOS/RHEL 7 o 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

Para usar esta biblioteca Lua con NGINX, asegúrate de que nginx-module-lua esté instalado.

Este documento describe lua-resty-httpipe v0.5 lanzado el 25 de noviembre de 2015.


Controlador cosocket HTTP de Lua para OpenResty / ngx_lua.

Características

  • HTTP 1.0/1.1 y HTTPS
  • Diseño de interfaz flexible
  • Lector de streaming y cargas
  • Cuerpo de solicitud/respuesta con codificación por fragmentos
  • Establece el tiempo de espera para operaciones de lectura y envío
  • Limita el tamaño máximo del cuerpo de respuesta
  • Keepalive

Sinopsis

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, "failed to new 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, "failed to request: ", 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, "failed to new 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, "failed to connect: ", err)
          return ngx.exit(503)
      end

      local ok, err = hp:send_request{ method = "GET", path = "/echo" }
      if not ok then
          ngx.log(ngx.ERR, "failed to send request: ", err)
          return ngx.exit(503)
      end

      -- parser de streaming completo

      while true do
          local typ, res, err = hp:read()
          if not typ then
              ngx.say("failed to read: ", err)
              return
          end

          ngx.say("read: ", 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 })

      -- de un stream http a otro, como un pipe de 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)
    ';
  }

}

Una salida típica de la ubicación /simple definida arriba es:

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

Una salida típica de la ubicación /generic definida arriba es:

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"]

Una salida típica de la ubicación /advanced definida arriba es:

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: */*

Conexión

new

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

Crea el objeto httpipe. En caso de fallos, devuelve nil y una cadena que describe el error.

El argumento chunk_size especifica el tamaño del búfer utilizado por las operaciones de lectura de cosocket. Por defecto es 8192.

connect

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

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

Intenta conectarse al servidor web.

Antes de resolver realmente el nombre del host y conectarse al backend remoto, este método siempre buscará en el grupo de conexiones las conexiones inactivas coincidentes creadas por llamadas anteriores a este método.

Se puede especificar una tabla Lua opcional como último argumento a este método para especificar varias opciones de conexión:

  • pool : Especifica un nombre personalizado para el grupo de conexiones que se está utilizando. Si se omite, el nombre del grupo de conexiones se generará a partir de la plantilla de cadena <host>:<port> o <unix-socket-path>.

set_timeout

syntax: hp:set_timeout(time)

Establece el tiempo de espera (en ms) de protección para las operaciones posteriores, incluyendo el método connect.

ssl_handshake

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

Realiza el apretón de manos SSL/TLS en la conexión actualmente establecida.

Ver más: http://wiki.nginx.org/HttpLuaModule#tcpsock:sslhandshake

set_keepalive

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

Intenta poner la conexión actual en el grupo de conexiones de cosocket ngx_lua.

Nota Normalmente, se llamará automáticamente después de procesar la solicitud. En otras palabras, no podemos liberar la conexión de nuevo al grupo a menos que consumamos todos los datos.

Puedes especificar el tiempo máximo de inactividad (en ms) cuando la conexión está en el grupo y el tamaño máximo del grupo para cada proceso de trabajo de nginx.

En caso de éxito, devuelve 1. En caso de errores, devuelve nil con una cadena que describe el error.

get_reused_times

syntax: times, err = hp:get_reused_times()

Este método devuelve las veces que se ha reutilizado (con éxito) la conexión actual. En caso de error, devuelve nil y una cadena que describe el error.

Si la conexión actual no proviene del grupo de conexiones incorporado, entonces este método siempre devuelve 0, es decir, la conexión nunca ha sido reutilizada (aún). Si la conexión proviene del grupo de conexiones, entonces el valor de retorno siempre es distinto de cero. Por lo tanto, este método también se puede usar para determinar si la conexión actual proviene del grupo.

close

syntax: ok, err = hp:close()

Cierra la conexión actual y devuelve el estado.

En caso de éxito, devuelve 1. En caso de errores, devuelve nil con una cadena que describe el error.

Solicitudes

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 tabla opts acepta los siguientes campos:

  • version: Establece la versión HTTP. Usa 10 para HTTP/1.0 y 11 para HTTP/1.1. Por defecto es 11.
  • method: La cadena del método HTTP. Por defecto es GET.
  • path: La cadena de la ruta. Por defecto es /.
  • query: Especifica los parámetros de consulta. Acepta una cadena o una tabla Lua.
  • headers: Una tabla de encabezados de solicitud. Acepta una tabla Lua.
  • body: El cuerpo de la solicitud como una cadena, o una función iteradora.
  • read_timeout: Establece el tiempo de espera en milisegundos para operaciones de lectura de red especialmente.
  • send_timeout: Establece el tiempo de espera en milisegundos para operaciones de envío de red especialmente.
  • stream: Si se establece en true, devuelve un objeto iterable res.body_reader en lugar de res.body.
  • maxsize: Establece el tamaño máximo en bytes a recuperar. Un cuerpo de respuesta más grande que esto hará que la función devuelva un error exceeds maxsize. Por defecto es nil, lo que significa que no hay límite.
  • ssl_verify: Un valor booleano de Lua para controlar si se debe realizar la verificación SSL.

Cuando la solicitud es exitosa, res contendrá los siguientes campos:

  • res.status (número): El estado de la respuesta, por ejemplo, 200
  • res.headers (tabla): Una tabla Lua con los encabezados de respuesta.
  • res.body (cadena): El cuerpo de respuesta en texto plano.
  • res.body_reader (función): Una función iteradora para leer el cuerpo de manera continua.
  • res.pipe (httpipe): Un nuevo pipe http que usa el body_reader actual como cuerpo de entrada por defecto.

Nota Todos los encabezados (de solicitud y respuesta) están normalizados para la capitalización - por ejemplo, Accept-Encoding, ETag, Foo-Bar, Baz - en el "estándar" HTTP normal.

En caso de errores, devuelve nil con una cadena que describe el error.

request_uri

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

La interfaz simple. Las opciones suministradas en la tabla opts son las mismas que en la interfaz genérica y sobrescribirán los componentes encontrados en la URI misma.

Devuelve un objeto res igual al método hp:request.

En caso de errores, devuelve nil con una cadena que describe el error.

res.body_reader

El iterador body_reader se puede usar para transmitir el cuerpo de respuesta en tamaños de fragmento de tu elección, de la siguiente manera:

local reader = res.body_reader

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

  if chunk then
    -- procesar
  end
until not chunk

send_request

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

En caso de errores, devuelve nil con una cadena que describe el error.

read_response

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

La tabla callback acepta los siguientes campos:

  • header_filter: Una función de callback para filtrar encabezados de respuesta
local res, err = hp:read_response{
    header_filter = function (status, headers)
        if status == 200 then
            return 1
        end
end }
  • body_filter: Una función de callback para filtrar el cuerpo de respuesta
local res, err = hp:read_response{
    body_filter = function (chunk)
        ngx.print(chunk)
    end
}

Además, no hay capacidad para transmitir el cuerpo de respuesta en este método. Si la respuesta es exitosa, res contendrá los siguientes campos: res.status, res.headers, res.body.

Nota Cuando se devuelve verdadero en la función de callback, el proceso de filtrado se interrumpirá.

En caso de errores, devuelve nil con una cadena que describe el error.

read

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

Parser de streaming para la respuesta completa.

El usuario solo necesita llamar al método read repetidamente hasta que se devuelva un tipo de token nil. Para cada token devuelto del método read, solo verifica el primer valor de retorno para el tipo de token actual. El tipo de token puede ser statusline, header, header_end, body, body_end y eof. Sobre el formato del valor res, consulta el ejemplo anterior. Por ejemplo, varios tokens de cuerpo contienen cada fragmento de datos del cuerpo, por lo que el valor res es igual al fragmento de datos del cuerpo.

En caso de errores, devuelve nil con una cadena que describe el error.

eof

syntax: local eof = hp:eof()

Si devuelve true, indica que ya se han consumido todos los datos; de lo contrario, la solicitud aún no ha terminado, necesitas llamar a hp:close para cerrar la conexión forzosamente.

Utilidad

parse_uri

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

Esta es una función de conveniencia que permite usar más fácilmente la interfaz genérica, cuando los datos de entrada son una URI.

get_client_body_reader

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

Devuelve una función iteradora que se puede usar para leer el cuerpo de la solicitud del cliente en un formato de streaming. Por ejemplo:

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
    -- procesar
  end
until not chunk

Este iterador también se puede usar como el valor para el campo body en los parámetros de solicitud, permitiendo transmitir el cuerpo de la solicitud a una solicitud upstream proxy.

local client_body_reader, err = hp:get_client_body_reader()

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

GitHub

Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-httpipe.