Pular para conteúdo

httpipe: driver de cliente HTTP Lua cosocket para nginx-module-lua, interfaces são mais flexíveis

Instalação

Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Em seguida, você pode prosseguir com os seguintes passos.

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

Para usar esta biblioteca Lua com o NGINX, certifique-se de que o nginx-module-lua esteja instalado.

Este documento descreve lua-resty-httpipe v0.5 lançado em 25 de novembro de 2015.


Driver de cliente HTTP Lua para OpenResty / ngx_lua.

Recursos

  • HTTP 1.0/1.1 e HTTPS
  • Design de interface flexível
  • Leitor de streaming e uploads
  • Corpo de requisição/resposta com codificação em pedaços
  • Define o tempo limite para operações de leitura e envio
  • Limita o tamanho máximo do corpo da resposta
  • Keepalive

Sinopse

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, "falha ao criar httpipe: ", err)
          return ngx.exit(503)
      end

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

      local res, err = hp:request("127.0.0.1", 9090, {
                                     method = "GET", path = "/echo" })
      if not res then
          ngx.log(ngx.ERR, "falha ao fazer requisição: ", 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, "falha ao criar httpipe: ", err)
          return ngx.exit(503)
      end

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

      local ok, err = hp:connect("127.0.0.1", 9090)
      if not ok then
          ngx.log(ngx.ERR, "falha ao conectar: ", err)
          return ngx.exit(503)
      end

      local ok, err = hp:send_request{ method = "GET", path = "/echo" }
      if not ok then
          ngx.log(ngx.ERR, "falha ao enviar requisição: ", 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("falha ao ler: ", err)
              return
          end

          ngx.say("lido: ", 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 seg

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

      -- de um stream http para outro, assim como um pipe unix

      local pipe = r0.pipe

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

      --[[
          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)
    ';
  }

}

Uma saída típica da localização /simple definida acima é:

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

Uma saída típica da localização /generic definida acima é:

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

Uma saída típica da localização /advanced definida acima é:

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

Conexão

new

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

Cria o objeto httpipe. Em caso de falhas, retorna nil e uma string descrevendo o erro.

O argumento chunk_size especifica o tamanho do buffer usado pelas operações de leitura do cosocket. O padrão é 8192.

connect

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

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

Tenta conectar-se ao servidor web.

Antes de realmente resolver o nome do host e conectar-se ao backend remoto, este método sempre procurará no pool de conexões por conexões ociosas correspondentes criadas por chamadas anteriores deste método.

Uma tabela Lua opcional pode ser especificada como o último argumento para este método para definir várias opções de conexão:

  • pool : Especifica um nome personalizado para o pool de conexões sendo usado. Se omitido, o nome do pool de conexões será gerado a partir do template de string <host>:<port> ou <unix-socket-path>.

set_timeout

syntax: hp:set_timeout(time)

Define o tempo limite (em ms) de proteção para operações subsequentes, incluindo o método connect.

ssl_handshake

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

Realiza o handshake SSL/TLS na conexão atualmente estabelecida.

Veja mais: http://wiki.nginx.org/HttpLuaModule#tcpsock:sslhandshake

set_keepalive

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

Tenta colocar a conexão atual no pool de conexões do ngx_lua cosocket.

Nota Normalmente, será chamado automaticamente após o processamento da requisição. Em outras palavras, não podemos liberar a conexão de volta ao pool a menos que você consuma todos os dados.

Você pode especificar o tempo máximo de inatividade (em ms) quando a conexão está no pool e o tamanho máximo do pool para cada processo trabalhador do nginx.

Em caso de sucesso, retorna 1. Em caso de erros, retorna nil com uma string descrevendo o erro.

get_reused_times

syntax: times, err = hp:get_reused_times()

Este método retorna o número de vezes (com sucesso) que a conexão atual foi reutilizada. Em caso de erro, retorna nil e uma string descrevendo o erro.

Se a conexão atual não vier do pool de conexões embutido, então este método sempre retornará 0, ou seja, a conexão nunca foi reutilizada (ainda). Se a conexão vier do pool de conexões, então o valor retornado será sempre diferente de zero. Portanto, este método também pode ser usado para determinar se a conexão atual vem do pool.

close

syntax: ok, err = hp:close()

Fecha a conexão atual e retorna o status.

Em caso de sucesso, retorna 1. Em caso de erros, retorna nil com uma string descrevendo o erro.

Requisições

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?)

A tabela opts aceita os seguintes campos:

  • version: Define a versão HTTP. Use 10 para HTTP/1.0 e 11 para HTTP/1.1. O padrão é 11.
  • method: A string do método HTTP. O padrão é GET.
  • path: A string do caminho. O padrão é /.
  • query: Especifica parâmetros de consulta. Aceita uma string ou uma tabela Lua.
  • headers: Uma tabela de cabeçalhos de requisição. Aceita uma tabela Lua.
  • body: O corpo da requisição como uma string ou uma função iteradora.
  • read_timeout: Define o tempo limite em milissegundos para operações de leitura de rede especificamente.
  • send_timeout: Define o tempo limite em milissegundos para operações de envio de rede especificamente.
  • stream: Se definido como true, retorna um objeto res.body_reader iterável em vez de res.body.
  • maxsize: Define o tamanho máximo em bytes a ser buscado. Um corpo de resposta maior que isso fará com que a função retorne um erro exceeds maxsize. O padrão é nil, o que significa sem limite.
  • ssl_verify: Um valor booleano Lua para controlar se deve realizar a verificação SSL.

Quando a requisição é bem-sucedida, res conterá os seguintes campos:

  • res.status (número): O status da resposta, por exemplo, 200
  • res.headers (tabela): Uma tabela Lua com os cabeçalhos da resposta.
  • res.body (string): O corpo da resposta em texto simples.
  • res.body_reader (função): Uma função iteradora para ler o corpo de forma streaming.
  • res.pipe (httpipe): Um novo pipe http que usa o body_reader atual como corpo de entrada por padrão.

Nota Todos os cabeçalhos (de requisição e resposta) são normalizados para capitalização - por exemplo, Accept-Encoding, ETag, Foo-Bar, Baz - no "padrão" HTTP normal.

Em caso de erros, retorna nil com uma string descrevendo o erro.

request_uri

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

A interface simples. As opções fornecidas na tabela opts são as mesmas da interface genérica e substituirão componentes encontrados na própria URI.

Retorna um objeto res igual ao método hp:request.

Em caso de erros, retorna nil com uma string descrevendo o erro.

res.body_reader

O iterador body_reader pode ser usado para transmitir o corpo da resposta em tamanhos de pedaços de sua escolha, da seguinte forma:

local reader = res.body_reader

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

  if chunk then
    -- processar
  end
until not chunk

send_request

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

Em caso de erros, retorna nil com uma string descrevendo o erro.

read_response

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

A tabela callback aceita os seguintes campos:

  • header_filter: Uma função de callback para filtro de cabeçalhos de resposta
local res, err = hp:read_response{
    header_filter = function (status, headers)
        if status == 200 then
            return 1
        end
end }
  • body_filter: Uma função de callback para filtro do corpo da resposta
local res, err = hp:read_response{
    body_filter = function (chunk)
        ngx.print(chunk)
    end
}

Além disso, não há capacidade de transmitir o corpo da resposta neste método. Se a resposta for bem-sucedida, res conterá os seguintes campos: res.status, res.headers, res.body.

Nota Quando retornar true na função de callback, o processo de filtro será interrompido.

Em caso de erros, retorna nil com uma string descrevendo o erro.

read

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

Parser de streaming para a resposta completa.

O usuário só precisa chamar o método read repetidamente até que um tipo de token nil seja retornado. Para cada token retornado do método read, basta verificar o primeiro valor de retorno para o tipo de token atual. O tipo de token pode ser statusline, header, header_end, body, body_end e eof. Sobre o formato do valor res, consulte o exemplo acima. Por exemplo, vários tokens de corpo contendo cada pedaço de dados do corpo, então o valor res é igual ao pedaço de dados do corpo.

Em caso de erros, retorna nil com uma string descrevendo o erro.

eof

syntax: local eof = hp:eof()

Se retornar true, indicando que já consumiu todos os dados; Caso contrário, a requisição ainda não terminou, você precisa chamar hp:close para fechar a conexão forçosamente.

Utilitário

parse_uri

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

Esta é uma função de conveniência que permite usar mais facilmente a interface genérica, quando os dados de entrada são uma URI.

get_client_body_reader

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

Retorna uma função iteradora que pode ser usada para ler o corpo da requisição do cliente a jusante de forma streaming. Por exemplo:

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

Este iterador também pode ser usado como o valor para o campo body nos parâmetros da requisição, permitindo que você transmita o corpo da requisição para uma requisição upstream proxy.

local client_body_reader, err = hp:get_client_body_reader()

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

GitHub

Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-httpipe.