Pular para conteúdo

http: driver de cliente HTTP cosocket Lua para nginx-module-lua

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

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

Este documento descreve lua-resty-http v0.17.2 lançado em 29 de fevereiro de 2024.


Driver de cliente HTTP cosocket para OpenResty / ngx_lua.

Recursos

  • HTTP 1.0 e 1.1
  • SSL
  • Interface de streaming para o corpo da resposta, para uso previsível de memória
  • Interface alternativa simples para requisições de uma única vez sem um passo de conexão manual
  • Codificações de transferência em pedaços e não em pedaços
  • Conexões keepalives
  • Pipelining de requisições
  • Trailers
  • Conexões de proxy HTTP
  • mTLS (requer ngx_lua_http_module >= v0.10.23)

API

Obsoleto

Esses métodos podem ser removidos em versões futuras.

Uso

Existem dois modos básicos de operação:

  1. Requisições simples de uma única vez, que não requerem gerenciamento manual de conexão, mas que armazenam em buffer toda a resposta e deixam a conexão fechada ou de volta ao pool de conexões.

  2. Requisições em streaming, onde a conexão é estabelecida separadamente, em seguida, a requisição é enviada, o corpo do stream é lido em pedaços e, finalmente, a conexão é fechada manualmente ou mantida viva. Essa técnica requer um pouco mais de código, mas fornece a capacidade de descartar corpos de resposta potencialmente grandes do lado Lua, bem como fazer pipelining de múltiplas requisições sobre uma única conexão.

Requisição de uma única vez

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

-- Requisições de uma única vez usam a 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, "requisição falhou: ", err)
    return
end

-- Neste ponto, toda a requisição / resposta está completa e a conexão
-- será fechada ou retornará ao pool de conexões.

-- A tabela `res` contém os campos esperados `status`, `headers` e `body`.
local status = res.status
local length = res.headers["Content-Length"]
local body   = res.body

Requisição em streaming

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

-- Primeiro, estabeleça uma conexão
local ok, err, ssl_session = httpc:connect({
    scheme = "https",
    host = "127.0.0.1",
    port = 8080,
})
if not ok then
    ngx.log(ngx.ERR, "conexão falhou: ", err)
    return
end

-- Em seguida, envie usando `request`, fornecendo um caminho e o cabeçalho `Host` em vez de um
-- URI completo.
local res, err = httpc:request({
    path = "/helloworld",
    headers = {
        ["Host"] = "example.com",
    },
})
if not res then
    ngx.log(ngx.ERR, "requisição falhou: ", err)
    return
end

-- Neste ponto, o status e os cabeçalhos estarão disponíveis para uso na tabela `res`,
-- mas o corpo e quaisquer trailers ainda estarão na rede.

-- Podemos usar o iterador `body_reader` para transmitir o corpo de acordo com nosso
-- tamanho de buffer desejado.
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
        -- processar
    end
until not buffer

local ok, err = httpc:set_keepalive()
if not ok then
    ngx.say("falha ao definir keepalive: ", err)
    return
end

-- Neste ponto, a conexão estará de volta ao pool com segurança ou fechada.
````

## Conexão

## new

`syntax: httpc, err = http.new()`

Cria o objeto de conexão HTTP. Em caso de falhas, retorna `nil` e uma string descrevendo o erro.

## connect

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

Tenta conectar ao servidor web enquanto incorpora as seguintes atividades:

- Conexão TCP
- Handshake SSL
- Configuração de proxy HTTP

Ao fazer isso, criará um nome de pool de conexão distinto que é seguro para usar com conexões baseadas em SSL e/ou proxy, e, como tal, essa sintaxe é fortemente recomendada em relação à original (agora obsoleta) [sintaxe de conexão apenas TCP](#TCP-only-connect).

A tabela de opções possui os seguintes campos:

* `scheme`: esquema a ser usado, ou nil para socket de domínio unix
* `host`: host de destino, ou caminho para um socket de domínio unix
* `port`: porta no host de destino, que será padrão para `80` ou `443` com base no esquema
* `pool`: nome do pool de conexão personalizado. Opção conforme a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockconnect), exceto que o padrão se tornará um nome de pool construído usando as propriedades de SSL/proxy, o que é importante para reutilização segura de conexões. Quando em dúvida, deixe em branco!
* `pool_size`: opção conforme a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
* `backlog`: opção conforme a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
* `proxy_opts`: sub-tabela, padrão para as opções de proxy globais definidas, veja [set\_proxy\_options](#set_proxy_options).
* `ssl_reused_session`: opção conforme a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_verify`: opção conforme a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake), exceto que o padrão é `true`.
* `ssl_server_name`: opção conforme a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_send_status_req`: opção conforme a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_client_cert`: será passado para `tcpsock:setclientcert`. Requer `ngx_lua_http_module` >= v0.10.23.
* `ssl_client_priv_key`: como acima.

## set\_timeout

`syntax: httpc:set_timeout(time)`

Define o tempo limite do socket (em ms) para operações subsequentes. Veja [set\_timeouts](#set_timeouts) abaixo para uma abordagem mais declarativa.

## set\_timeouts

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

Define o limite de tempo limite de conexão, limite de tempo limite de envio e limite de tempo limite de leitura, respectivamente, em milissegundos, para operações de socket subsequentes (conectar, enviar, receber e iteradores retornados de receiveuntil).

## set\_keepalive

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

Coloca a conexão atual no pool para reutilização futura ou fecha a conexão. Chamar isso em vez de [close](#close) é "seguro" na medida em que fechará condicionalmente dependendo do tipo de requisição. Especificamente, uma requisição `1.0` sem `Connection: Keep-Alive` será fechada, assim como uma requisição `1.1` com `Connection: Close`.

Em caso de sucesso, retorna `1`. Em caso de erro, retorna `nil, err`. No caso em que a conexão é fechada condicionalmente conforme descrito acima, retorna `2` e a string de erro `connection must be closed`, para distinguir de erros inesperados.

Veja a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksetkeepalive) para documentação de parâmetros.

## set\_proxy\_options

`syntax: httpc:set_proxy_options(opts)`

Configura um proxy HTTP a ser usado com esta instância de cliente. A tabela `opts` espera os seguintes campos:

* `http_proxy`: um URI para um servidor proxy a ser usado com requisições HTTP
* `http_proxy_authorization`: um valor padrão de cabeçalho `Proxy-Authorization` a ser usado com `http_proxy`, por exemplo, `Basic ZGVtbzp0ZXN0`, que será substituído se o cabeçalho de requisição `Proxy-Authorization` estiver presente.
* `https_proxy`: um URI para um servidor proxy a ser usado com requisições HTTPS
* `https_proxy_authorization`: como `http_proxy_authorization`, mas para uso com `https_proxy` (uma vez que com HTTPS a autorização é feita ao conectar, este não pode ser substituído passando o cabeçalho de requisição `Proxy-Authorization`).
* `no_proxy`: uma lista separada por vírgulas de hosts que não devem ser proxy.

Observe que este método não tem efeito ao usar a obsoleta [sintaxe de conexão apenas TCP](#TCP-only-connect).

## get\_reused\_times

`syntax: times, err = httpc:get_reused_times()`

Veja a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockgetreusedtimes).

## close

`syntax: ok, err = httpc:close()`

Veja a [documentação do OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockclose).

## Requisitando

## request

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

Envia uma requisição HTTP sobre uma conexão  estabelecida. Retorna uma tabela `res` ou `nil` e uma mensagem de erro.

A tabela `params` espera os seguintes campos:

* `version`: O número da versão HTTP. Padrão é `1.1`.
* `method`: A string do método HTTP. Padrão é `GET`.
* `path`: A string do caminho. Padrão é `/`.
* `query`: A string de consulta, apresentada como uma string literal ou tabela Lua.
* `headers`: Uma tabela de cabeçalhos de requisição.
* `body`: O corpo da requisição como uma string, uma tabela de strings, ou uma função iteradora que gera strings até nil quando esgotada. Observe que você deve especificar um `Content-Length` para o corpo da requisição, ou especificar `Transfer-Encoding: chunked` e ter sua função implementando a codificação. Veja também: [get\_client\_body\_reader](#get_client_body_reader).

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

* `status`: O código de status.
* `reason`: A frase de razão do status.
* `headers`: Uma tabela de cabeçalhos. Múltiplos cabeçalhos com o mesmo nome de campo serão apresentados como uma tabela de valores.
* `has_body`: Uma flag booleana indicando se  um corpo a ser lido.
* `body_reader`: Uma função iteradora para ler o corpo de forma streaming.
* `read_body`: Um método para ler todo o corpo em uma string.
* `read_trailers`: Um método para mesclar quaisquer trailers sob a tabela de `headers`, após ler o corpo.

Se a resposta tiver um corpo, então antes que a mesma conexão possa ser usada para outra requisição, você deve ler o corpo usando `read_body` ou `body_reader`.

## request\_uri

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

A interface de uma única vez (veja [uso](#Uso)). Como este método realiza uma requisição completa de ponta a ponta, as opções especificadas em `params` podem incluir qualquer coisa encontrada tanto em [connect](#connect) quanto em [request](#request) documentadas acima. Observe também que os campos `path` e `query`, em `params` substituirão os componentes relevantes do `uri` se especificados (`scheme`, `host` e `port` sempre serão retirados do `uri`).

Existem 3 parâmetros adicionais para controlar keepalives:

* `keepalive`: Defina como `false` para desabilitar keepalives e fechar imediatamente a conexão. O padrão é `true`.
* `keepalive_timeout`: O tempo limite máximo ocioso (ms). O padrão é `lua_socket_keepalive_timeout`.
* `keepalive_pool`: O número máximo de conexões no pool. O padrão é `lua_socket_pool_size`.

Se a requisição for bem-sucedida, `res` conterá os seguintes campos:

* `status`: O código de status.
* `headers`: Uma tabela de cabeçalhos.
* `body`: O corpo da resposta inteira como uma string.

## request\_pipeline

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

Este método funciona conforme o método [request](#request) acima, mas `params` é em vez disso uma tabela aninhada de tabelas de parâmetros. Cada requisição é enviada em ordem, e `responses` é retornado como uma tabela de handles de resposta. Por exemplo:

```lua
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, "erro de leitura do socket")
        break
    end

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

Devido à natureza do pipelining, nenhuma resposta é realmente lida até que você tente usar os campos de resposta (status / cabeçalhos etc). E como as respostas são lidas em ordem, você deve ler todo o corpo (e quaisquer trailers se os tiver) antes de tentar ler a próxima resposta.

Observe que isso não exclui o uso do leitor de corpo de resposta em streaming. As respostas ainda podem ser transmitidas, desde que todo o corpo seja transmitido antes de tentar acessar a próxima resposta.

Certifique-se de testar pelo menos um campo (como status) antes de tentar usar os outros, caso um erro de leitura de socket tenha ocorrido.

Resposta

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
local buffer_size = 8192

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

    if buffer then
        -- processar
    end
until not buffer

Se o leitor for chamado sem argumentos, o comportamento depende do tipo de conexão. Se a resposta for codificada como em pedaços, então o iterador retornará os pedaços à medida que chegarem. Caso contrário, ele simplesmente retornará todo o corpo.

Observe que o tamanho fornecido é na verdade um tamanho máximo. Portanto, no caso de transferência em pedaços, você pode obter buffers menores do que o tamanho que você pediu, como um resto dos pedaços codificados reais.

res:read_body

syntax: body, err = res:read_body()

Lê todo o corpo em uma string local.

res:read_trailers

syntax: res:read_trailers()

Isso mescla quaisquer trailers sob a tabela res.headers em si. Deve ser chamado após ler o corpo.

Utilitário

parse_uri

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

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

A partir da versão 0.10, o parâmetro opcional query_in_path foi adicionado, que especifica se a string de consulta deve ser incluída no valor de retorno path, ou separadamente como seu próprio valor de retorno. Isso é padrão para true a fim de manter a compatibilidade retroativa. Quando definido como false, path incluirá apenas o caminho, e query conterá os argumentos do URI, não incluindo o delimitador ?.

get_client_body_reader

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

Retorna uma função iteradora que pode ser usada para ler o corpo da requisição do cliente a montante de forma streaming. Você também pode especificar um tamanho de pedaço padrão opcional (o padrão é 65536), ou um socket já estabelecido em vez da requisição do cliente.

Exemplo:

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

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 em uma requisição upstream proxy.

local client_body_reader, err = httpc:get_client_body_reader()

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

Obsoleto

Esses recursos permanecem para compatibilidade retroativa, mas podem ser removidos em futuras versões.

Conexão apenas TCP

As seguintes versões da assinatura do método connect estão obsoletas em favor do único argumento table documentado acima.

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

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

NOTA: o nome do pool padrão incorporará apenas informações de IP e porta, portanto, é inseguro usar em caso de conexões SSL e/ou Proxy. Especifique seu próprio pool ou, melhor ainda, não use essas assinaturas.

connect_proxy

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

Chamar este método manualmente não é mais necessário, uma vez que está incorporado dentro de connect. Ele é mantido por enquanto para compatibilidade com usuários da antiga sintaxe conexão apenas TCP.

Tenta conectar ao servidor web através do servidor proxy fornecido. O método aceita os seguintes argumentos:

  • proxy_uri - URI completo do servidor proxy a ser usado (por exemplo, http://proxy.example.com:3128/). Nota: Apenas o protocolo http é suportado.
  • scheme - O protocolo a ser usado entre o servidor proxy e o host remoto (http ou https). Se https for especificado como o esquema, connect_proxy() faz uma requisição CONNECT para estabelecer um túnel TCP para o host remoto através do servidor proxy.
  • host - O nome do host do host remoto ao qual se conectar.
  • port - A porta do host remoto ao qual se conectar.
  • proxy_authorization - O valor do cabeçalho Proxy-Authorization enviado ao servidor proxy via CONNECT quando o scheme é https.

Se ocorrer um erro durante a tentativa de conexão, este método retorna nil com uma string descrevendo o erro. Se a conexão foi estabelecida com sucesso, o método retorna 1.

Há alguns pontos-chave a serem lembrados ao usar esta API:

  • Se o esquema for https, você precisa realizar o handshake TLS com o servidor remoto manualmente usando o método ssl_handshake() antes de enviar quaisquer requisições através do túnel proxy.
  • Se o esquema for http, você precisa garantir que as requisições que você envia através das conexões estejam em conformidade com RFC 7230 e especialmente Seção 5.3.2. que afirma que o alvo da requisição deve estar em forma absoluta. Na prática, isso significa que quando você usa send_request(), o path deve ser um URI absoluto para o recurso (por exemplo, http://example.com/index.html em vez de apenas /index.html).

ssl_handshake

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

Chamar este método manualmente não é mais necessário, uma vez que está incorporado dentro de connect. Ele é mantido por enquanto para compatibilidade com usuários da antiga sintaxe conexão apenas TCP.

Veja a documentação do OpenResty.

proxy_request / proxy_response

Esses dois métodos de conveniência foram destinados simplesmente a demonstrar um caso de uso comum de implementação de proxy reverso, e o autor se arrepende de sua inclusão no módulo. Os usuários são incentivados a criar suas próprias implementações em vez de depender dessas funções, que podem ser removidas em uma versão subsequente.

proxy_request

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

Realiza uma requisição usando os argumentos da requisição do cliente atual, efetivamente fazendo proxy para o upstream conectado. O corpo da requisição será lido de forma streaming, de acordo com request_body_chunk_size (veja documentação sobre o leitor de corpo do cliente abaixo).

proxy_response

syntax: httpc:proxy_response(res, chunksize?)

Define a resposta atual com base no res fornecido. Garante que cabeçalhos hop-by-hop não sejam enviados a montante e lerá a resposta de acordo com chunksize (veja documentação sobre o leitor de corpo acima).

GitHub

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