Перейти к содержанию

http: Драйвер Lua HTTP клиента cosocket для nginx-module-lua

Установка

Если вы еще не настроили подписку на репозиторий RPM, зарегистрируйтесь. Затем вы можете продолжить с следующими шагами.

CentOS/RHEL 7 или 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

Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.

Этот документ описывает lua-resty-http v0.17.2, выпущенную 29 февраля 2024 года.


Lua HTTP клиент cosocket драйвер для OpenResty / ngx_lua.

Особенности

  • HTTP 1.0 и 1.1
  • SSL
  • Интерфейс потоковой передачи для тела ответа, для предсказуемого использования памяти
  • Альтернативный простой интерфейс для одноразовых запросов без ручного управления соединением
  • Кодировки передачи с разделением на части и без
  • Поддержка keepalive соединений
  • Пайплайнинг запросов
  • Трейлеры
  • HTTP прокси соединения
  • mTLS (требуется ngx_lua_http_module >= v0.10.23)

API

Устаревшие

Эти методы могут быть удалены в будущих версиях.

Использование

Существует два основных режима работы:

  1. Простые одноразовые запросы, которые не требуют ручного управления соединением, но буферизуют весь ответ и оставляют соединение либо закрытым, либо возвращенным в пул соединений.

  2. Потоковые запросы, где соединение устанавливается отдельно, затем отправляется запрос, тело читается по частям, и, наконец, соединение вручную закрывается или поддерживается в активном состоянии. Эта техника требует немного больше кода, но предоставляет возможность отбрасывать потенциально большие тела ответов на стороне Lua, а также выполнять пайплайнинг нескольких запросов через одно соединение.

Одноразовый запрос

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

-- Одноразовые запросы используют интерфейс `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, "запрос не удался: ", err)
    return
end

-- На этом этапе весь запрос / ответ завершен, и соединение
-- будет закрыто или возвращено в пул соединений.

-- Таблица `res` содержит ожидаемые поля `status`, `headers` и `body`.
local status = res.status
local length = res.headers["Content-Length"]
local body   = res.body

Потоковый запрос

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

-- Сначала установите соединение
local ok, err, ssl_session = httpc:connect({
    scheme = "https",
    host = "127.0.0.1",
    port = 8080,
})
if not ok then
    ngx.log(ngx.ERR, "соединение не удалось: ", err)
    return
end

-- Затем отправьте запрос, используя `request`, указав путь и заголовок `Host` вместо полного URI.
local res, err = httpc:request({
    path = "/helloworld",
    headers = {
        ["Host"] = "example.com",
    },
})
if not res then
    ngx.log(ngx.ERR, "запрос не удался: ", err)
    return
end

-- На этом этапе статус и заголовки будут доступны для использования в таблице `res`,
-- но тело и любые трейлеры все еще будут на проводе.

-- Мы можем использовать итератор `body_reader`, чтобы потоково читать тело в соответствии с нашим
-- желаемым размером буфера.
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
        -- обработка
    end
until not buffer

local ok, err = httpc:set_keepalive()
if not ok then
    ngx.say("не удалось установить keepalive: ", err)
    return
end

-- На этом этапе соединение будет либо безопасно возвращено в пул, либо закрыто.
````

## Соединение

## new

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

Создает объект HTTP соединения. В случае неудачи возвращает `nil` и строку, описывающую ошибку.

## connect

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

Пытается подключиться к веб-серверу, выполняя следующие действия:

- TCP соединение
- SSL рукопожатие
- Конфигурация HTTP прокси

При этом будет создано отдельное имя пула соединений, которое безопасно использовать с SSL и/или прокси-соединениями, и поэтому этот синтаксис настоятельно рекомендуется вместо оригинального (теперь устаревшего) [синтаксиса только для TCP](#TCP-only-connect).

Таблица опций имеет следующие поля:

* `scheme`: схема для использования, или nil для сокета домена unix
* `host`: целевой хост, или путь к сокету домена unix
* `port`: порт на целевом хосте, по умолчанию будет `80` или `443` в зависимости от схемы
* `pool`: имя пользовательского пула соединений. Опция согласно [документации OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockconnect), за исключением того, что по умолчанию будет использоваться имя пула, построенное с использованием свойств SSL / прокси, что важно для безопасного повторного использования соединений. В случае сомнений оставьте пустым!
* `pool_size`: опция согласно [документации OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
* `backlog`: опция согласно [документации OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
* `proxy_opts`: подтаблица, по умолчанию использует глобальные параметры прокси, см. [set\_proxy\_options](#set_proxy_options).
* `ssl_reused_session`: опция согласно [документации OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_verify`: опция согласно [документации OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake), за исключением того, что по умолчанию она равна `true`.
* `ssl_server_name`: опция согласно [документации OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_send_status_req`: опция согласно [документации OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_client_cert`: будет передан в `tcpsock:setclientcert`. Требуется `ngx_lua_http_module` >= v0.10.23.
* `ssl_client_priv_key`: как выше.

## set\_timeout

`syntax: httpc:set_timeout(time)`

Устанавливает тайм-аут сокета (в мс) для последующих операций. См. [set\_timeouts](#set_timeouts) ниже для более декларативного подхода.

## set\_timeouts

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

Устанавливает порог тайм-аута подключения, порог тайм-аута отправки и порог тайм-аута чтения соответственно, в миллисекундах, для последующих операций сокета (подключение, отправка, получение и итераторы, возвращаемые из receiveuntil).

## set\_keepalive

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

Либо помещает текущее соединение в пул для будущего повторного использования, либо закрывает соединение. Вызов этого метода вместо [close](#close) является "безопасным", поскольку он будет закрыт условно в зависимости от типа запроса. В частности, запрос `1.0` без `Connection: Keep-Alive` будет закрыт, как и запрос `1.1` с `Connection: Close`.

В случае успеха возвращает `1`. В случае ошибок возвращает `nil, err`. В случае, если соединение закрывается условно, как описано выше, возвращает `2` и строку ошибки `соединение должно быть закрыто`, чтобы отличить от неожиданных ошибок.

См. [документацию OpenResty](https://github.com/openresty/lua-nginx-module#tcpsocksetkeepalive) для документации по параметрам.

## set\_proxy\_options

`syntax: httpc:set_proxy_options(opts)`

Настраивает HTTP прокси, который будет использоваться с этим экземпляром клиента. Таблица `opts` ожидает следующие поля:

* `http_proxy`: URI прокси-сервера, который будет использоваться для HTTP-запросов
* `http_proxy_authorization`: значение заголовка по умолчанию `Proxy-Authorization`, которое будет использоваться с `http_proxy`, например, `Basic ZGVtbzp0ZXN0`, которое будет переопределено, если заголовок запроса `Proxy-Authorization` присутствует.
* `https_proxy`: URI прокси-сервера, который будет использоваться для HTTPS-запросов
* `https_proxy_authorization`: как `http_proxy_authorization`, но для использования с `https_proxy` (поскольку с HTTPS авторизация выполняется при подключении, это значение не может быть переопределено путем передачи заголовка запроса `Proxy-Authorization`).
* `no_proxy`: список хостов, разделенных запятыми, которые не должны проксироваться.

Обратите внимание, что этот метод не имеет эффекта при использовании устаревшего [синтаксиса только для TCP](#TCP-only-connect).

## get\_reused\_times

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

См. [документацию OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockgetreusedtimes).

## close

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

См. [документацию OpenResty](https://github.com/openresty/lua-nginx-module#tcpsockclose).

## Запросы

## request

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

Отправляет HTTP-запрос по уже установленному соединению. Возвращает таблицу `res` или `nil` и сообщение об ошибке.

Таблица `params` ожидает следующие поля:

* `version`: номер версии HTTP. По умолчанию `1.1`.
* `method`: строка метода HTTP. По умолчанию `GET`.
* `path`: строка пути. По умолчанию `/`.
* `query`: строка запроса, представленная либо в виде литеральной строки, либо в виде таблицы Lua.
* `headers`: таблица заголовков запроса.
* `body`: тело запроса в виде строки, таблицы строк или функции-итератора, выдающей строки до тех пор, пока не будет возвращено nil. Обратите внимание, что вы должны указать `Content-Length` для тела запроса или указать `Transfer-Encoding: chunked` и реализовать кодирование в вашей функции. См. также: [get\_client\_body\_reader](#get_client_body_reader).

Когда запрос успешен, `res` будет содержать следующие поля:

* `status`: Код состояния.
* `reason`: Фраза причины состояния.
* `headers`: Таблица заголовков. Несколько заголовков с одинаковым именем поля будут представлены в виде таблицы значений.
* `has_body`: логический флаг, указывающий, есть ли тело для чтения.
* `body_reader`: функция-итератор для чтения тела в потоковом режиме.
* `read_body`: метод для чтения всего тела в строку.
* `read_trailers`: метод для объединения любых трейлеров под таблицей `res.headers` после чтения тела.

Если у ответа есть тело, то перед тем, как то же самое соединение можно будет использовать для другого запроса, вы должны прочитать тело, используя `read_body` или `body_reader`.

## request\_uri

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

Интерфейс одноразового запроса (см. [использование](#Использование)). Поскольку этот метод выполняет полный запрос от начала до конца, параметры, указанные в `params`, могут включать все, что содержится как в [connect](#connect), так и в [request](#request), описанных выше. Обратите внимание, что поля `path` и `query` в `params` будут переопределять соответствующие компоненты `uri`, если указаны (`scheme`, `host` и `port` всегда будут взяты из `uri`).

Существуют 3 дополнительных параметра для управления keepalive:

* `keepalive`: Установите в `false`, чтобы отключить keepalive и немедленно закрыть соединение. По умолчанию `true`.
* `keepalive_timeout`: Максимальный тайм-аут бездействия (мс). По умолчанию `lua_socket_keepalive_timeout`.
* `keepalive_pool`: Максимальное количество соединений в пуле. По умолчанию `lua_socket_pool_size`.

Если запрос успешен, `res` будет содержать следующие поля:

* `status`: Код состояния.
* `headers`: Таблица заголовков.
* `body`: Все тело ответа в виде строки.

## request\_pipeline

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

Этот метод работает аналогично методу [request](#request) выше, но `params` вместо этого является вложенной таблицей параметров. Каждый запрос отправляется по порядку, и `responses` возвращается в виде таблицы дескрипторов ответов. Например:

```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, "ошибка чтения сокета")
        break
    end

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

Из-за природы пайплайнинга никакие ответы фактически не читаются, пока вы не попытаетесь использовать поля ответа (статус / заголовки и т.д.). И поскольку ответы читаются по порядку, вы должны прочитать все тело (и любые трейлеры, если они у вас есть) перед тем, как пытаться прочитать следующий ответ.

Обратите внимание, что это не исключает использование потокового читателя тела ответа. Ответы все еще могут быть потоковыми, пока все тело потоково читается перед попыткой получить доступ к следующему ответу.

Обязательно протестируйте хотя бы одно поле (например, статус) перед тем, как попробовать использовать остальные, на случай, если произошла ошибка чтения сокета.

Ответ

res.body_reader

Итератор body_reader может использоваться для потоковой передачи тела ответа в размерах частей по вашему выбору, как показано ниже:

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
        -- обработка
    end
until not buffer

Если итератор вызывается без аргументов, поведение зависит от типа соединения. Если ответ закодирован как разделенный на части, то итератор будет возвращать части по мере их поступления. Если нет, он просто вернет все тело.

Обратите внимание, что указанный размер на самом деле является максимальным размером. Таким образом, в случае передачи с разделением на части вы можете получить буферы меньшего размера, чем запрашиваемый, в качестве остатка от фактически закодированных частей.

res:read_body

syntax: body, err = res:read_body()

Читает все тело в локальную строку.

res:read_trailers

syntax: res:read_trailers()

Объединяет любые трейлеры под таблицей res.headers. Должен быть вызван после чтения тела.

Утилита

parse_uri

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

Это вспомогательная функция, позволяющая легче использовать общий интерфейс, когда входные данные являются URI.

Начиная с версии 0.10, был добавлен необязательный параметр query_in_path, который указывает, следует ли включать строку запроса в возвращаемое значение path, или отдельно как собственное возвращаемое значение. По умолчанию это значение true, чтобы сохранить обратную совместимость. Когда установлено в false, path будет содержать только путь, а query будет содержать аргументы URI, не включая разделитель ?.

get_client_body_reader

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

Возвращает функцию-итератор, которую можно использовать для чтения тела запроса клиента в потоковом режиме. Вы также можете указать необязательный размер чанка по умолчанию (по умолчанию 65536), или уже установленный сокет вместо клиентского запроса.

Пример:

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
        -- обработка
    end
until not buffer

Этот итератор также может быть использован в качестве значения для поля body в параметрах запроса, позволяя потоково передавать тело запроса в проксируемый запрос к upstream.

local client_body_reader, err = httpc:get_client_body_reader()

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

Устаревшие

Эти функции остаются для обратной совместимости, но могут быть удалены в будущих релизах.

TCP только соединение

Следующие версии сигнатуры метода connect устарели в пользу единственного аргумента table, документированного выше.

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

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

ПРИМЕЧАНИЕ: имя пула по умолчанию будет включать только информацию об IP и порте, поэтому его небезопасно использовать в случае SSL и/или прокси-соединений. Укажите свой собственный пул или, лучше всего, не используйте эти сигнатуры.

connect_proxy

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

Вызов этого метода вручную больше не требуется, так как он включен в connect. Он сохраняется на данный момент для совместимости с пользователями старого синтаксиса только для TCP.

Пытается подключиться к веб-серверу через указанный прокси-сервер. Метод принимает следующие аргументы:

  • proxy_uri - Полный URI прокси-сервера, который будет использоваться (например, http://proxy.example.com:3128/). Примечание: Поддерживается только протокол http.
  • scheme - Протокол, который будет использоваться между прокси-сервером и удаленным хостом (http или https). Если https указан как схема, connect_proxy() выполняет запрос CONNECT, чтобы установить TCP туннель к удаленному хосту через прокси-сервер.
  • host - Имя хоста удаленного хоста, к которому нужно подключиться.
  • port - Порт удаленного хоста, к которому нужно подключиться.
  • proxy_authorization - Значение заголовка Proxy-Authorization, отправляемое на прокси-сервер через CONNECT, когда схема https.

Если происходит ошибка во время попытки подключения, этот метод возвращает nil с описанием ошибки в строке. Если соединение было успешно установлено, метод возвращает 1.

Есть несколько ключевых моментов, которые следует учитывать при использовании этого API:

  • Если схема https, вам нужно вручную выполнить TLS рукопожатие с удаленным сервером, используя метод ssl_handshake(), прежде чем отправлять любые запросы через прокси-туннель.
  • Если схема http, вам нужно убедиться, что запросы, которые вы отправляете через соединения, соответствуют RFC 7230 и особенно Разделу 5.3.2., который гласит, что целевой запрос должен быть в абсолютной форме. На практике это означает, что когда вы используете send_request(), path должен быть абсолютным URI к ресурсу (например, http://example.com/index.html, а не просто /index.html).

ssl_handshake

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

Вызов этого метода вручную больше не требуется, так как он включен в connect. Он сохраняется на данный момент для совместимости с пользователями старого синтаксиса только для TCP.

См. документацию OpenResty.

proxy_request / proxy_response

Эти два удобных метода были предназначены просто для демонстрации общего случая реализации обратного проксирования, и автор сожалеет о их включении в модуль. Пользователям рекомендуется реализовать свои собственные функции, а не полагаться на эти функции, которые могут быть удалены в последующем релизе.

proxy_request

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

Выполняет запрос, используя текущие аргументы запроса клиента, фактически проксируя к подключенному upstream. Тело запроса будет читаться в потоковом режиме в соответствии с request_body_chunk_size (см. документацию о читателе тела клиента ниже).

proxy_response

syntax: httpc:proxy_response(res, chunksize?)

Устанавливает текущий ответ на основе данного res. Обеспечивает, чтобы заголовки hop-by-hop не отправлялись вниз по потоку, и будет читать ответ в соответствии с chunksize (см. документацию о читателе тела выше).

GitHub

Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-http.