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

http2: Реализация протокола HTTP/2 (Клиентская сторона) для 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-http2

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-http2

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

Этот документ описывает lua-resty-http2 v1.0, выпущенную 20 ноября 2019 года.


local http2 = require "resty.http2"

local host = "127.0.0.1"
local port = 8080
local sock = ngx.socket.tcp()
local ok, err = sock:connect(host, port)
if not ok then
    ngx.log(ngx.ERR, "failed to connect ", host, ":", port, ": ", err)
    return
end

local headers = {
    { name = ":authority", value = "test.com" },
    { name = ":method", value = "GET" },
    { name = ":path", value = "/index.html" },
    { name = ":scheme", value = "http" },
    { name = "accept-encoding", value = "gzip" },
    { name = "user-agent", value = "example/client" },
}

local on_headers_reach = function(ctx, headers)
    -- Обработка заголовков ответа
end

local on_data_reach = function(ctx, data)
    -- Обработка тела ответа
end

local opts = {
    ctx = sock,
    recv = sock.receive,
    send = sock.send,
}

local client, err = http2.new(opts)
if not client then
    ngx.log(ngx.ERR, "failed to create HTTP/2 client: ", err)
    return
end

local ok, err = client:request(headers, nil, on_headers_reach, on_data_reach)
if not ok then
    ngx.log(ngx.ERR, "client:process() failed: ", err)
    return
end

sock:close()

В качестве более формального примера, пожалуйста, ознакомьтесь с util/example.lua.

Описание

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

Существуют некоторые врожденные ограничения, которые не решены.

Не может использоваться через соединения с рукопожатием SSL/TLS. tcpsock:sslhandshake не поддерживает расширения ALPN или NPN, поэтому в настоящее время можно использовать только обычные соединения, библиотека начнет сессию HTTP/2, отправив префикс соединения, т.е. строку:

PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n

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

Можно отправить только один HTTP-запрос. В настоящее время реализованные API поддерживают отправку только одного HTTP-запроса. PR приветствуются для решения этой проблемы.

Повторное использование сессии HTTP/2. Протокол HTTP/2 разработан как постоянный, в то время как объект Cosocket связан с конкретным HTTP-запросом. Необходимо закрыть объект Cosocket или установить его в состояние alive до завершения запроса, эта модель конфликтует с повторным использованием сессии HTTP/2, только обходной путь может решить эту проблему, смотрите client:keepalive для получения подробностей.

Реализованный API

resty.http2

Чтобы загрузить этот модуль, просто выполните следующее:

local http2 = require "resty.http2"

http2.new

синтаксис: local client, err = http2.new(opts)

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

Единственный параметр opts, который является таблицей Lua, содержит некоторые поля:

  • recv, функция Lua, которая используется для чтения байтов;

  • send, функция Lua, которая используется для отправки байтов;

  • ctx, непрозрачные данные, действующие как контекст вызывающего;

Функции recv и send будут вызываться следующим образом:

local data, err = recv(ctx, size)
local ok, err = send(ctx, data)
  • preread_size, число Lua, которое влияет на начальный размер окна отправки партнера (рекламируется через фрейм SETTINGS), по умолчанию 65535;

  • max_concurrent_stream, число Lua, которое ограничивает максимальное количество одновременно открытых потоков в сессии HTTP/2, по умолчанию 128;

  • max_frame_size, число Lua, которое ограничивает максимальный размер фрейма, который может отправить партнер, по умолчанию 16777215.

  • key, строка Lua, которая представляет кэшированную сессию HTTP/2, которую вызывающий хочет повторно использовать, если не найдено, будет создана новая сессия HTTP/2. Смотрите client:keepalive для получения дополнительных сведений.

client:acknowledge_settings

синтаксис: local ok, err = client:acknowledge_settings()

Подтверждает фрейм SETTINGS партнера, настройки будут применены автоматически.

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

client:request

синтаксис: local ok, err = client:request(headers, body?, on_headers_reach, on_data_reach)

Отправляет HTTP-запрос партнеру,

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

headers должен быть массивоподобной таблицей Lua, представляющей заголовки HTTP-запроса, каждая запись выглядит как { name = "header1", value = "value1" }.

Стоит отметить, что эта библиотека не заботится о семантике заголовков HTTP, поэтому ответственность за это лежит на вызывающих, и вызывающие должны реализовать любые необходимые преобразования, например, Host должен быть преобразован в :authority. Также следующие заголовки будут игнорироваться, так как они специфичны для соединения.

  • Connection
  • Keep-Alive
  • Proxy-Connection
  • Upgrade
  • Transfer-Encoding

body может быть строкой Lua, представляющей тело HTTP-запроса. Это также может быть функцией Lua для реализации потоковой загрузки. Когда body является функцией Lua, она будет вызываться следующим образом:

local part_data, last, err = body(size)

В случае неудачи body должна предоставить 3-й возвращаемый параметр err, чтобы сообщить этой библиотеке о возникновении фатальных ошибок, тогда этот метод будет немедленно прерван, и фрейм GOAWAY будет отправлен партнеру с кодом ошибки INTERNAL_ERROR.

Когда все данные были сгенерированы, 2-й возвращаемый параметр last должен быть предоставлен, и его значение должно быть true.

on_headers_reach должна быть функцией Lua, как обратный вызов, которая будет вызвана, когда будут получены полные заголовки HTTP-ответа, она будет вызываться следующим образом:

local abort = on_headers_reach(ctx, headers)

2-й параметр headers — это хешоподобная таблица Lua, представляющая заголовки HTTP-ответа, полученные от партнера.

on_headers_reach может решить, прерывать ли сессию HTTP/2, вернув логическое значение abort библиотеке, сессия HTTP/2 будет прервана, если on_headers_reach вернет истинное значение.

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

local abort = on_data_reach(ctx, data)

2-й параметр data — это строка Lua, представляющая тело HTTP-ответа, полученное в этот раз.

Значение возвращаемого значения такое же, как и у on_headers_reach.

После того как этот метод вернется, сессия HTTP/2 все еще будет активна, можно решить закрыть эту сессию, вызвав client:close или продолжить делать что-то еще.

client:send_request

синтаксис: local stream, err = client:send_request(headers, body?)

Отправляет заголовки и тело (если есть) партнеру.

Значения headers и body такие же, как в client:request.

Соответствующий созданный объект потока будет возвращен, когда этот метод завершится.

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

client:read_headers

синтаксис: local headers, err = client:read_headers(stream)

Читает заголовки ответа от партнера, параметр stream — это тот, который был создан с помощью client:send_request.

Возвращаемые заголовки — это хешоподобная таблица Lua, которая содержит все заголовки HTTP-ответа, которые могут содержать некоторые псевдозаголовки, такие как ":status", вызывающие должны выполнять некоторые преобразования, если это необходимо.

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

client:read_body

синтаксис: local body, err = client:read_body(stream)

Читает фрейм DATA от партнера, параметр stream — это тот, который был создан с помощью client:send_request.

Возвращаемые данные — это строка Lua, представляющая часть тела ответа. Пустая строка будет возвращена, если все тело было прочитано.

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

client:close

синтаксис: local ok, err = client:close(code)

Закрывает текущую сессию HTTP/2 с кодом ошибки code.

Смотрите resty.http2.error, чтобы узнать о кодах ошибок.

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

client:keepalive

синтаксис: client:keepalive(key)

Кэширует текущую сессию HTTP/2 для повторного использования, обратите внимание, что неправильно сформированные сессии HTTP/2 никогда не будут кэшироваться. Сессия HTTP/2 будет отсоединена от соединения, точнее, от текущего объекта Cosocket.

Отсоединенная сессия HTTP/2 будет сохранена во внутренней хешоподобной таблице Lua, уникальный параметр key будет использоваться для индексации этой сессии, когда вызывающие захотят ее повторно использовать.

После того как эта сессия будет установлена как активная, вызывающие также должны установить объект Cosocket как keepalive.

Существует врожденное ограничение между сопоставлением сессии HTTP/2 и основным соединением. Сессия HTTP/2 может использоваться только в TCP-соединении, потому что она имеет состояние; если вызывающие хранят соединение в пуле, который кэширует несколько соединений, связь теряется, поскольку неясно, какое соединение выбрано для объекта Cosocket, следовательно, какое HTTP/2 сессия должна быть сопоставлена, также неизвестно.

Нет элегантного способа решить эту проблему, если только модель Cosocket не может назначить идентификатор основному соединению. Сейчас вызывающие могут использовать пул соединений единственного размера, чтобы обойти это ограничение, например:

...

sock:connect(host, port, { pool = "h2" })

...

sock:setkeepalive(75, 1)
client:keepalive("test")

resty.http2.protocol

Этот модуль реализует некоторые низкоуровневые API, относящиеся к протоколу.

Чтобы загрузить этот модуль, просто выполните следующее:

local protocol = require "resty.http2.protocol"

protocol.session

синтаксис: local session, err = protocol.session(recv, send, ctx, preread_size?, max_concurrent_stream?, max_frame_size?)

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

Значение каждого параметра такое же, как описано в http2.new.

Начальный фрейм SETTINGS и фрейм WINDOW_UPDATE будут отправлены перед тем, как эта функция вернется.

session:adjust_window

синтаксис: local ok = session:adjust_window(delta)

Регулирует размер окна отправки каждого потока, поток будет сброшен, если измененный размер окна отправки превышает MAX_WINDOW_SIZE, в этом случае ok будет nil.

session:frame_queue

синтаксис: session:frame_queue(frame)

Добавляет frame в текущую очередь вывода сессии.

session:flush_queue

синтаксис: local ok, err = session:flush_queue()

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

session:submit_request

синтаксис: local ok, err = session:submit_request(headers, no_body, priority?, pad?)

Отправляет HTTP-запрос в текущую сессию HTTP/2, в случае неудачи будет возвращено nil и строка Lua, описывающая причину ошибки.

Значение каждого параметра:

  • headers, должна быть хешоподобной таблицей Lua, представляющей заголовки HTTP-запроса, стоит отметить, что эта библиотека не заботится о семантике заголовков HTTP, поэтому ответственность за это лежит на вызывающих, и вызывающие должны преобразовать любые необходимые псевдозаголовки. Например, :authority должен быть передан вместо Host;

  • no_body, логическое значение, указывающее, есть ли у этого запроса тело. Когда оно истинно, сгенерированный фрейм HEADERS будет содержать флаг END_HEADERS;

  • priority, хешоподобная таблица Lua, которая используется для определения пользовательских зависимостей потоков:

  • priority.sid представляет идентификатор зависимого потока;
  • priority.excl, указывает, станет ли новый поток единственной зависимостью потока, указанного priority.sid;
  • priority.weight определяет вес нового потока;

  • pad, данные для дополнения.

session:submit_window_update

синтаксис: local ok, err = session:submit_window_update(incr)

Отправляет фрейм WINDOW_UPDATE для всей сессии HTTP/2 с увеличением incr, в случае неудачи будет возвращено nil и строка Lua, описывающая причину ошибки.

session:recv_frame

синтаксис: local frame, err = session:recv_frame()

Принимает фрейм HTTP/2, в случае неудачи будет возвращено nil и строка Lua, описывающая причину ошибки.

Соответствующее действие будет выполнено автоматически, например, фрейм GOAWAY будет отправлен, если партнер нарушает конвенции протокола HTTP/2; фрейм WINDOW_UPDATE будет отправлен, если окно отправки партнера становится слишком маленьким.

session:close

синтаксис: session:close(code?, debug_data?)

Генерирует фрейм GOAWAY с кодом ошибки code и отладочными данными debug_data, код ошибки по умолчанию — NO_ERROR, а отладочные данные — nil.

Обратите внимание, что эта функция просто ставит фрейм GOAWAY в очередь на вывод, вызывающие должны вызвать session:flush_queue, чтобы действительно отправить фреймы.

session:detach

синтаксис: session:detach()

Отсоединяет текущую сессию HTTP/2 от объекта Cosocket.

session:attach

синтаксис: local ok, err = session:attach(recv, send, ctx)

Присоединяет текущую сессию HTTP/2 к объекту Cosocket, в случае неудачи будет возвращено nil и строка Lua, описывающая причину ошибки.

Значения recv, send и ctx такие же, как описано в http.new.

resty.http2.stream

Этот модуль реализует некоторые низкоуровневые API, относящиеся к потокам.

Чтобы загрузить этот модуль, просто выполните следующее:

local h2_stream = require "resty.http2.stream"

h2_stream.new

синтаксис: local stream = h2_stream.new(sid, weight, session)

Создает новый поток с идентификатором sid, весом weight и сессией HTTP/2, к которой он принадлежит.

h2_stream.new_root

синтаксис: local root_stream = h2_stream.new_root(session)

Создает корневой поток с его сессией.

Идентификатор корневого потока — 0x0, и он действительно является виртуальным потоком, который используется для управления всей сессией HTTP/2.

stream:submit_headers

синтаксис: local ok, err = stream:submit_headers(headers, end_stream, priority?, pad?)

Отправляет некоторые HTTP-заголовки в поток.

Первый параметр headers должен быть хешоподобной таблицей Lua, представляющей заголовки HTTP-запроса, стоит отметить, что эта библиотека не заботится о семантике заголовков HTTP, поэтому ответственность за это лежит на вызывающих, и вызывающие должны преобразовать любые необходимые псевдозаголовки. Например, :authority должен быть передан вместо Host;

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

priority должна быть хешоподобной таблицей Lua (если есть), которая используется для определения пользовательских зависимостей потоков: * priority.sid представляет идентификатор зависимого потока; * priority.excl, указывает, станет ли новый поток единственной зависимостью потока, указанного priority.sid; * priority.weight определяет вес нового потока;

Последний параметр pad представляет данные для дополнения.

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

stream:submit_data

синтаксис: local ok, err = stream:submit_data(data, pad, last)

Отправляет некоторое тело запроса в поток, data должна быть строкой Lua, с необязательными данными для дополнения.

Последний параметр last указывает, является ли это последней отправкой, текущий фрейм DATA будет прикреплен с флагом END_STREAM, если last истинно.

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

stream:submit_window_update

синтаксис: local ok, err = session:submit_window_update(incr)

Отправляет фрейм WINDOW_UPDATE для потока с увеличением incr, в случае неудачи будет возвращено nil и строка Lua, описывающая причину ошибки.

stream:set_dependency

синтаксис: stream:set_dependency(depend, excl)

Устанавливает зависимости текущего потока к потоку с идентификатором depend.

Второй параметр excl указывает, станет ли текущий поток единственным дочерним потоком depend.

Когда depend отсутствует, целевой поток будет корневым, а excl будет считаться false.

stream:rst

синтаксис: stream:rst(code)

Генерирует фрейм RST_STREAM с кодом ошибки code. В случае отсутствия code будет выбран код NO_ERROR.

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

resty.http2.frame

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

Чтобы загрузить этот модуль, просто выполните следующее:

local h2_frame = require "resty.http2.frame"

h2_frame.header.new

синтаксис: local hd = h2_frame.header.new(length, typ, flags, id)

Создает заголовок фрейма с длиной полезной нагрузки length, типом фрейма type и принимает flags как флаги фрейма, который принадлежит потоку id.

h2_frame.header.pack

синтаксис: h2_frame.header.pack(hd, dst)

Сериализует заголовок фрейма hd в назначение dst. dst должен быть массивоподобной таблицей Lua.

h2_frame.header.unpack

синтаксис: h2_frame.header.unpack(src)

Десериализует заголовок фрейма из строки Lua src, длина src должна быть не менее 9 октетов.

h2_frame.priority.pack

синтаксис: h2_frame.priority.pack(pf, dst)

Сериализует фрейм PRIORITY в назначение dst. dst должен быть массивоподобной таблицей Lua.

pf должен быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • depend, идентификатор зависимого потока;
  • excl, указывает, станет ли текущий поток, где находится этот фрейм PRIORITY, единственным дочерним потоком потока, идентифицированного depend;
  • weight, назначает новый вес weight текущему потоку;

h2_frame.priority.unpack

синтаксис: local ok, err = h2_frame.priority.unpack(pf, src, stream)

Десериализует фрейм PRIORITY из строки Lua src, длина src должна быть не менее размера, указанного в pf.header.length.

pf должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма PRIORITY, т.е. pf.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм PRIORITY.

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

В случае неудачи будет возвращено nil и код ошибки.

h2_frame.rst_stream.pack

синтаксис: h2_frame.rst_stream.pack(rf, dst)

Сериализует фрейм RST_STREAM в назначение dst. dst должен быть массивоподобной таблицей Lua.

rf должен быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • error_code, код ошибки;

h2_frame.rst_stream.unpack

синтаксис: local ok, err = h2_frame.rst_stream.unpack(rf, src, stream)

Десериализует фрейм RST_STREAM из строки Lua src. Длина src должна быть не менее размера, указанного в rf.header.length.

rf должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма RST_STREAM, т.е. rf.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм RST_STREAM.

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

В случае неудачи будет возвращено nil и код ошибки.

h2_frame.rst_stream.new

синтаксис: local rf = h2_frame.rst_stream.new(error_code, sid)

Создает фрейм RST_STREAM с кодом ошибки error_code, который принадлежит потоку sid.

h2_frame.settings.pack

синтаксис: h2_frame.settings.pack(sf, dst)

Сериализует фрейм SETTINGS в назначение dst. dst должен быть массивоподобной таблицей Lua.

sf должен быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • item, конкретные настройки, которые должны быть массивоподобной таблицей Lua, каждый элемент должен быть хешоподобной таблицей Lua:
  • id, идентификатор настройки, может быть:
    • SETTINGS_ENABLE_PUSH (0x2)
    • SETTINGS_MAX_CONCURRENT_STREAMS (0x3)
    • SETTINGS_INITIAL_WINDOW_SIZE (0x4)
    • SETTINGS_MAX_FRAME_SIZE (0x5)
  • value, соответствующее значение настройки;

h2_frame.settings.unpack

синтаксис: local ok, err = h2_frame.settings.unpack(sf, src, stream)

Десериализует фрейм SETTINGS из строки Lua src. Длина src должна быть не менее размера, указанного в sf.header.length.

sf должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма SETTINGS, т.е. sf.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм SETTINGS (должен быть корневым потоком).

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

В случае неудачи будет возвращено nil и код ошибки.

h2_frame.settings.new

синтаксис: local sf = h2_frame.settings.new(flags, payload)

Создает фрейм SETTINGS с флагами flags и полезной нагрузкой payload.

payload должна быть массивоподобной таблицей Lua, каждый элемент должен быть хешоподобной таблицей Lua: * id, идентификатор настройки, может быть: * SETTINGS_ENABLE_PUSH (0x2) * SETTINGS_MAX_CONCURRENT_STREAMS (0x3) * SETTINGS_INITIAL_WINDOW_SIZE (0x4) * SETTINGS_MAX_FRAME_SIZE (0x5) * value, соответствующее значение настройки;

h2_frame.ping.pack

синтаксис: h2_frame.ping.pack(pf, dst)

Сериализует фрейм PING в назначение dst. dst должен быть массивоподобной таблицей Lua.

pf должен быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • opaque_data_hi, старшие 32 бита соответствующих данных пинга;
  • opaque_data_lo, младшие 32 бита соответствующих данных пинга;

h2_frame.ping.unpack

синтаксис: local ok, err = h2_frame.ping.unpack(pf, src, stream)

Десериализует фрейм PING из строки Lua src. Длина src должна быть не менее размера, указанного в sf.header.length.

pf должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма PING, т.е. pf.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм PING (должен быть корневым потоком).

В случае неудачи будет возвращено nil и код ошибки.

h2_frame.goaway.pack

синтаксис: h2_frame.goaway.pack(gf, dst)

Сериализует фрейм GOAWAY в назначение dst. dst должен быть массивоподобной таблицей Lua.

gf должен быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • last_stream_id, последний инициализированный партнером идентификатор потока;
  • error_code, код ошибки;
  • debug_data, отладочные данные;

h2_frame.goaway.unpack

синтаксис: local ok, err = h2_frame.goaway.unpack(gf, src, stream)

Десериализует фрейм GOAWAY из строки Lua src. Длина src должна быть не менее размера, указанного в gf.header.length.

gf должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма GOAWAY, т.е. gf.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм GOAWAY (должен быть корневым потоком).

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

h2_frame.goaway.new

синтаксис: local gf = h2_frame.goaway.new(last_sid, error_code, debug_data)

Создает фрейм GOAWAY с последним инициализированным партнером идентификатором потока last_sid и кодом ошибки error_code. Опционально, с отладочными данными debug_data.

h2_frame.window_update.pack

синтаксис: h2_frame.window_update.pack(wf, dst)

Сериализует фрейм WINDOW_UPDATE в назначение dst. dst должен быть массивоподобной таблицей Lua.

wf должен быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • window_size_increment, увеличение размера окна;

h2_frame.window_update.unpack

синтаксис: local ok, err = h2_frame.window_update.unpack(wf, src, stream)

Десериализует фрейм WINDOW_UPDATE из строки Lua src. Длина src должна быть не менее размера, указанного в wf.header.length.

wf должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма WINDOW_UPDATE, т.е. wf.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм WINDOW_UPDATE.

В случае неудачи будет возвращено nil и код ошибки.

h2_frame.window_update.new

синтаксис: local wf = h2_frame.window_update.new(sid, window)

Создает фрейм WINDOW_UPDATE с идентификатором потока sid и увеличивает размер окна, указанный window.

h2_frame.headers.pack

синтаксис: h2_frame.headers.pack(hf, dst)

Сериализует фрейм HEADERS в назначение dst. dst должен быть массивоподобной таблицей Lua.

hf должна быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • pad, данные для дополнения;
  • depend, идентификатор зависимого потока;
  • excl, указывает, станет ли поток, которому принадлежит текущий фрейм HEADERS, единственным дочерним потоком потока depend;
  • weight, указывает вес потока, которому принадлежит текущий фрейм HEADERS;
  • block_frags, обычные HTTP-заголовки (после сжатия hpack);

h2_frame.headers.unpack

синтаксис: local ok,err = h2_frame.headers.unpack(hf, src, stream)

Десериализует фрейм HEADERS из строки Lua src, длина src должна быть не менее размера, указанного в hf.header.length.

hf должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма HEADERS, т.е. hf.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм HEADERS.

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

В случае неудачи будет возвращено nil и код ошибки.

h2_frame.headers.new

синтаксис: local hf = h2_frame.headers.new(frags, pri?, pad?, end_stream, end_headers, sid)

Создает фрейм HEADERS, который принимает блок фрагментов frags.

Параметр pri может быть использован для указания зависимостей потока, pri должна быть хешоподобной таблицей Lua, которая содержит:

  • sid, идентификатор зависимого потока;
  • excl, указывает, станет ли поток sid единственным дочерним потоком зависимого потока;
  • weight, определяет вес текущего потока (указанного sid);

Параметр pad указывает данные для дополнения, которые являются необязательными.

Когда end_stream истинно, текущий фрейм HEADERS будет принимать флаг END_STREAM, аналогично, когда end_headers истинно, текущий фрейм HEADERS будет принимать флаг END_HEADERS.

Необходимо учитывать, что если текущий фрейм HEADERS не содержит все заголовки, то один или несколько фреймов CONTINUATION должны следовать в соответствии с протоколом HTTP/2.

h2_frame.continuation.pack

синтаксис: h2_frame.continuation.pack(cf, dst)

Сериализует фрейм CONTINUATION в назначение dst. dst должен быть массивоподобной таблицей Lua.

cf должна быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • block_frags, обычные HTTP-заголовки (после сжатия hpack);

h2_frame.continuation.unpack

синтаксис: local ok, err = h2_frame.continuation.unpack(cf, src, stream)

Десериализует фрейм CONTINUATION из строки Lua src, длина src должна быть не менее размера, указанного в cf.header.length.

cf должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма CONTINUATION, т.е. cf.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм CONTINUATION.

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

В случае неудачи будет возвращено nil и код ошибки.

h2_frame.continuation.new

синтаксис: local cf = h2_frame.continuation.new(frags, end_headers, sid)

Создает фрейм CONTINUATION, который принимает блок фрагментов frags.

Когда end_headers истинно, текущий фрейм CONTINUATION будет принимать флаг END_HEADERS.

Необходимо учитывать, что если текущий фрейм CONTINUATION не содержит все заголовки, то один или несколько фреймов CONTINUATION должны следовать в соответствии с протоколом HTTP/2.

Параметр sid указывает поток, которому принадлежит текущий фрейм CONTINUATION.

h2_frame.data.pack

синтаксис: h2_frame.data.pack(df, dst)

Сериализует фрейм DATA в назначение dst. dst должен быть массивоподобной таблицей Lua.

df должна быть хешоподобной таблицей Lua, которая содержит:

  • header, заголовок фрейма;
  • payload, тело HTTP-запроса/ответа;

h2_frame.data.unpack

синтаксис: local ok, err = h2_frame.data.unpack(df, src, stream)

Десериализует фрейм DATA из строки Lua src, длина src должна быть не менее размера, указанного в df.header.length.

df должна быть хешоподобной таблицей Lua, которая уже содержит заголовок текущего фрейма DATA, т.е. df.header.

Последний параметр stream указывает поток, которому принадлежит текущий фрейм DATA.

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

В случае неудачи будет возвращено nil и код ошибки.

h2_frame.data.new

синтаксис: local df = h2_frame.data.new(payload, pad, last, sid)

Создает фрейм DATA, который принимает полезную нагрузку payload.

Параметр pad указывает данные для дополнения, которые являются необязательными.

Когда last истинно, текущий фрейм DATA будет принимать флаг END_STREAM.

Параметр sid указывает поток, которому принадлежит текущий фрейм DATA.

h2_frame.push_promise.unpack

синтаксис: local df = h2_frame.data.new(payload, pad, last, sid)

В настоящее время любой входящий фрейм PUSH_PROMISE будет отклонен.

Этот метод всегда возвращает nil и ошибку PROTOCOL_ERROR.

resty.http2.hpack

Этот модуль реализует некоторые низкоуровневые API HPACK.

Чтобы загрузить этот модуль, просто выполните следующее:

local hpack = require "resty.http2.hpack"

hpack.encode

синтаксис: hpack.encode(src, dst, lower)

Кодирует строку Lua src в назначение dst, dst должен быть массивоподобной таблицей Lua. Сначала будут пробоваться коды Хаффмана.

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

hpack.indexed

синтаксис: local v = hpack.indexed(index)

Возвращает индекс после использования представления индексированного заголовка.

hpack.incr_indexed

синтаксис: local v = hpack.indexed(index)

Возвращает индекс после использования литерального заголовка с инкрементной индексацией.

hpack.new

синтаксис: local h = hpack.new(size)

Создает экземпляр hpack, так как декодирование HPACK имеет состояние.

Параметр size представляет максимальный размер таблицы hpack, по умолчанию 4096 байт.

Возвращаемое значение h представляет экземпляр HPACK. Один из членов h важен, т.е. h.cached, который сохраняет все фрагменты блоков заголовков, и h:decode будет анализировать данные внутри h.cached.

В настоящее время h2_frame.headers.unpack и h2_frame.continuation.unpack будут добавлять фрагменты блоков заголовков в h.cached, как только блок будет завершен, декодирование будет выполнено.

h:insert_entry

синтаксис: local ok = h:insert_entry(header_name, header_value)

Пытается вставить запись заголовка с именем header_name и значением header_value в динамическую таблицу HPACK.

Вставка может не удаться, если эта запись слишком велика. Необходимая эвакуация записей произойдет, если места будет недостаточно.

Этот метод вернет true, если вставка успешна, или false, если нет.

h:resize

синтаксис: local ok = h:resize(new_size)

Регулирует размер динамической таблицы до new_size, в настоящее время new_size не может превышать 4096, иначе операция изменения размера завершится неудачей.

Когда размер динамической таблицы уменьшается, некоторые записи будут эвакуированы в соответствии с правилами HPACK.

Этот метод вернет true, если операция изменения размера успешна, или false, если нет.

h:decode

синтаксис: local ok, err = h:decode(dst)

Декодирует фрагменты блоков заголовков внутри h.cached, декодированные заголовки будут сохранены в dst, хешоподобной таблице Lua.

В случае неудачи будет возвращено nil и код ошибки.

h:get_indexed_header

синтаксис: local entry = h:get_indexed_header(index)

Возвращает запись заголовка в соответствии с индексом index.

Возвращаемое значение будет nil, если индекс недействителен, в противном случае entry будет хешоподобной таблицей Lua с двумя элементами:

  • entry.name, имя заголовка;
  • entry.value, значение заголовка;

resty.http2.error

Этот модуль реализует некоторые низкоуровневые API, относящиеся к ошибкам.

Существует множество определенных кодов ошибок, которые в основном соответствуют протоколу HTTP/2:

  • h2_error.NO_ERROR
  • h2_error.PROTOCOL
  • h2_error.INTERNAL_ERROR
  • h2_error.FLOW_CONTROL_ERROR
  • h2_error.SETTINGS_TIMEOUT
  • h2_error.STREAM_CLOSED
  • h2_error.FRAME_SIZE_ERROR
  • h2_error.REFUSED_STREAM
  • h2_error.CANCEL
  • h2_error.COMPRESSION_ERROR
  • h2_error.CONNECT_ERROR
  • h2_error.ENHANCE_YOUR_CALM
  • h2_error.INADEQUATE_SECURITY
  • h2_error.HTTP_1_1_REQUIRED

И три пользовательских кода ошибки:

  • h2_error.STREAM_PROTOCOL_ERROR, ошибка протокола на уровне потока;
  • h2_error.STREAM_FLOW_CONTROL_ERROR, ошибка управления потоком на уровне потока;
  • h2_error.STREAM_FRAME_SIZE_ERROR, ошибка размера фрейма на уровне потока;

Ошибки на уровне потока не повлияют на все соединение, но сбросят текущий поток.

Чтобы загрузить этот модуль, просто выполните следующее:

h2_error.strerror

синтаксис: local msg = h2_error.strerror(code)

Возвращает строку Lua, которая описывает код ошибки code, будет возвращено "unknown error", если код ошибки неизвестен.

h2_error.is_stream_error

синтаксис: local ok = h2_error.is_stream_error(code)

Определяет, является ли код ошибки code ошибкой на уровне потока.

См. также

GitHub

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