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

httpipe: Драйвер 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-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

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

Этот документ описывает lua-resty-httpipe v0.5, выпущенный 25 ноября 2015 года.


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

Особенности

  • HTTP 1.0/1.1 и HTTPS
  • Гибкий дизайн интерфейса
  • Читающий поток и загрузки
  • Тело запроса/ответа с кодировкой в чанках
  • Устанавливает тайм-аут для операций чтения и отправки
  • Ограничивает максимальный размер тела ответа
  • Keepalive

Синопсис

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

      -- полный потоковый парсер

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

      -- из одного http потока в другой, как в unix pipe

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

}

Типичный вывод для местоположения /simple, определенного выше:

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

Типичный вывод для местоположения /generic, определенного выше:

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

Типичный вывод для местоположения /advanced, определенного выше:

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

Соединение

new

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

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

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

connect

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

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

Пытается подключиться к веб-серверу.

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

В качестве последнего аргумента к этому методу можно указать необязательную таблицу Lua для задания различных параметров подключения:

  • pool : Указывает на пользовательское имя для используемого пула соединений. Если опущено, имя пула соединений будет сгенерировано из строкового шаблона <host>:<port> или <unix-socket-path>.

set_timeout

syntax: hp:set_timeout(time)

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

ssl_handshake

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

Выполняет SSL/TLS рукопожатие на текущем установленном соединении.

Смотрите подробнее: http://wiki.nginx.org/HttpLuaModule#tcpsock:sslhandshake

set_keepalive

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

Пытается поместить текущее соединение в пул соединений ngx_lua cosocket.

Примечание Обычно он будет вызываться автоматически после обработки запроса. Другими словами, мы не можем вернуть соединение обратно в пул, пока не потребуем все данные.

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

В случае успеха возвращает 1. В случае ошибок возвращает nil со строкой, описывающей ошибку.

get_reused_times

syntax: times, err = hp:get_reused_times()

Этот метод возвращает количество (успешно) повторно использованных раз для текущего соединения. В случае ошибки возвращает nil и строку, описывающую ошибку.

Если текущее соединение не поступает из встроенного пула соединений, то этот метод всегда возвращает 0, то есть соединение никогда не использовалось повторно (пока). Если соединение поступает из пула соединений, то возвращаемое значение всегда ненулевое. Таким образом, этот метод также можно использовать для определения, поступает ли текущее соединение из пула.

close

syntax: ok, err = hp:close()

Закрывает текущее соединение и возвращает статус.

В случае успеха возвращает 1. В случае ошибок возвращает nil со строкой, описывающей ошибку.

Запросы

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

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

  • version: Устанавливает версию HTTP. Используйте 10 для HTTP/1.0 и 11 для HTTP/1.1. По умолчанию 11.
  • method: Строка метода HTTP. По умолчанию GET.
  • path: Строка пути. По умолчанию /.
  • query: Указывает параметры запроса. Принимает либо строку, либо таблицу Lua.
  • headers: Таблица заголовков запроса. Принимает таблицу Lua.
  • body: Тело запроса в виде строки или функции-итератора.
  • read_timeout: Устанавливает тайм-аут в миллисекундах для операций сетевого чтения.
  • send_timeout: Устанавливает тайм-аут в миллисекундах для операций сетевой отправки.
  • stream: Если установлено в true, возвращает итерируемый объект res.body_reader вместо res.body.
  • maxsize: Устанавливает максимальный размер в байтах для получения. Тело ответа, превышающее этот размер, вызовет ошибку exceeds maxsize. По умолчанию nil, что означает отсутствие ограничений.
  • ssl_verify: Логическое значение Lua для управления выполнением проверки SSL.

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

  • res.status (число): Статус ответа, например, 200
  • res.headers (таблица): Таблица Lua с заголовками ответа.
  • res.body (строка): Обычное тело ответа.
  • res.body_reader (функция): Функция-итератор для чтения тела в потоковом режиме.
  • res.pipe (httpipe): Новый http pipe, который использует текущий body_reader в качестве входного тела по умолчанию.

Примечание Все заголовки (запроса и ответа) нормализуются по капитализации - например, Accept-Encoding, ETag, Foo-Bar, Baz - в обычном HTTP "стандарте."

В случае ошибок возвращает nil со строкой, описывающей ошибку.

request_uri

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

Простой интерфейс. Опции, указанные в таблице opts, такие же, как в общем интерфейсе, и будут переопределять компоненты, найденные в самом uri.

Возвращает объект res, аналогичный методу hp:request.

В случае ошибок возвращает nil со строкой, описывающей ошибку.

res.body_reader

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

local reader = res.body_reader

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

  if chunk then
    -- обработка
  end
until not chunk

send_request

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

В случае ошибок возвращает nil со строкой, описывающей ошибку.

read_response

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

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

  • header_filter: Функция обратного вызова для фильтрации заголовков ответа
local res, err = hp:read_response{
    header_filter = function (status, headers)
        if status == 200 then
            return 1
        end
end }
  • body_filter: Функция обратного вызова для фильтрации тела ответа
local res, err = hp:read_response{
    body_filter = function (chunk)
        ngx.print(chunk)
    end
}

Кроме того, в этом методе нет возможности потоковой передачи тела ответа. Если ответ успешен, res будет содержать следующие поля: res.status, res.headers, res.body.

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

В случае ошибок возвращает nil со строкой, описывающей ошибку.

read

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

Потоковый парсер для полного ответа.

Пользователю просто нужно повторно вызывать метод read до тех пор, пока не будет возвращен тип токена nil. Для каждого токена, возвращаемого из метода read, просто проверьте первое возвращаемое значение для текущего типа токена. Тип токена может быть statusline, header, header_end, body, body_end и eof. О формате значения res смотрите в приведенном выше примере. Например, несколько токенов тела содержат каждый кусок данных тела, поэтому значение res равно куску данных тела.

В случае ошибок возвращает nil со строкой, описывающей ошибку.

eof

syntax: local eof = hp:eof()

Если возвращает true, это означает, что все данные уже потреблены; в противном случае запрос все еще не завершен, необходимо вызвать hp:close, чтобы принудительно закрыть соединение.

Утилиты

parse_uri

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

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

get_client_body_reader

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

Возвращает функцию-итератор, которую можно использовать для чтения тела запроса клиента в потоковом режиме. Например:

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

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

local client_body_reader, err = hp:get_client_body_reader()

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

GitHub

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