跳转至

http2: NGINX模块lua的HTTP/2协议(客户端)实现

安装

如果您尚未设置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

要在NGINX中使用此Lua库,请确保已安装nginx-module-lua

本文档描述了lua-resty-http2 v1.0,于2019年11月20日发布。


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对象或将其设置为存活,这种模型与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,一个不透明数据,作为调用者的上下文;

recvsend函数将像这样被调用:

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可以是表示HTTP请求体的Lua字符串。它也可以是一个Lua函数以实现流式上传。当body是一个Lua函数时,它将像这样被调用:

local part_data, last, err = body(size)

如果失败,body应提供第三个返回值err以告知该库发生了一些致命错误,然后该方法将立即中止,并将向对等方发送一个带有错误代码INTERNAL_ERROR的GOAWAY帧。

当所有数据生成完毕时,第二个返回值last应提供,并且其值必须为true

on_headers_reach应为一个Lua函数,作为回调,当完整的HTTP响应头被接收时将被调用,它将像这样被调用:

local abort = on_headers_reach(ctx, headers)

第二个参数headers是一个类哈希的Lua表,表示从对等方接收到的HTTP响应头。

on_headers_reach可以通过返回布尔值abort来决定是否中止HTTP/2会话,如果on_headers_reach返回一个真值,则HTTP/2会话将被中止。

最后一个参数on_data_reach是一个Lua函数,作为回调,当每次接收到响应体时将被调用,它将像这样被调用:

local abort = on_data_reach(ctx, data)

第二个参数data是一个Lua字符串,表示这次接收到的HTTP响应体。

返回值的含义与on_headers_reach相同。

在此方法返回后,HTTP/2会话仍然存活,可以通过调用client:close来决定关闭此会话或继续执行其他操作。

client:send_request

语法local stream, err = client:send_request(headers, body?)

将头和主体(如果有)发送到对等方。

headersbody的含义与client:request中的相同。

当此方法返回时,将提供相应创建的流对象。

如果失败,将返回nil和描述错误原因的Lua字符串。

client:read_headers

语法local headers, err = client:read_headers(stream)

从对等方读取响应头,参数stream是由client:send_request创建的。

返回的headers是一个类哈希的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)

使用错误代码code关闭当前HTTP/2会话。

请参见resty.http2.error以了解错误代码。

如果失败,将返回nil和描述错误原因的Lua字符串。

client:keepalive

语法client:keepalive(key)

缓存当前HTTP/2会话以供重用,请注意,格式不正确的HTTP/2会话将永远不会被缓存。HTTP/2会话将与连接分离,准确地说,是当前的Cosocket对象。

分离的HTTP/2会话将保存在一个内部类哈希的Lua表中,唯一的参数key将在调用者希望重用此会话时用于索引此会话。

在将此会话设置为存活后,调用者还应将Cosocket对象设置为保持活动状态。

在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/2会话提交HTTP请求,如果失败,将返回nil和描述错误原因的Lua字符串。

每个参数的含义:

  • headers,应为一个类哈希的Lua表,表示HTTP请求头,值得注意的是,该库不处理HTTP头的语义,因此调用者有责任提供这些,并且调用者应转换任何必要的伪头。例如,:authority应传递而不是Host

  • no_body,一个布尔值,指示此请求是否有主体。当为true时,生成的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)

为整个HTTP/2会话提交一个增量为incr的WINDOW_UPDATE帧,如果失败,将返回nil和描述错误原因的Lua字符串。

session:recv_frame

语法local frame, err = session:recv_frame()

接收一个HTTP/2帧,如果失败,将返回nil和描述错误原因的Lua字符串。

相应的操作将自动执行,例如,如果对等方违反HTTP/2协议约定,将发送GOAWAY帧;如果对等方的发送窗口变得太小,将发送WINDOW_UPDATE帧。

session:close

语法session:close(code?, debug_data?)

生成一个带有错误代码code和调试数据debug_data的GOAWAY帧,默认错误代码为NO_ERROR,调试数据为nil

请注意,此函数仅将GOAWAY帧排入输出队列,调用者应调用session:flush_queue以真正发送帧。

session:detach

语法session:detach()

与Cosocket对象分离当前HTTP/2会话。

session:attach

语法local ok, err = session:attach(recv, send, ctx)

将当前HTTP/2会话与Cosocket对象关联,如果失败,将返回nil和描述错误原因的Lua字符串。

recvsendctx的含义与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标志,基本上如果没有请求体需要发送,调用者可以将其设置为true。

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指示这是否是最后一次提交,如果last为true,则当前DATA帧将附加END_STREAM标志。

如果失败,将返回nil和描述相应错误的Lua字符串。

stream:submit_window_update

语法local ok, err = session:submit_window_update(incr)

为流提交一个增量为incr的WINDOW_UPDATE帧,如果失败,将返回nil和描述错误原因的Lua字符串。

stream:set_dependency

语法stream:set_dependency(depend, excl)

将当前流的依赖关系设置为标识符为depend的流。

第二个参数excl指示当前流是否将成为depend的唯一子流。

depend缺失时,目标流将是根流,excl将被视为false

stream:rst

语法stream:rst(code)

生成一个带有错误代码code的RST_STREAM帧。如果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序列化到目标dstdst必须是一个类数组的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帧序列化到目标dstdst必须是一个类数组的Lua表。

pf必须是一个类哈希的Lua表,包含:

  • header,帧头;
  • depend,依赖的流标识符;
  • excl,指定当前流在此PRIORITY帧中是否成为由depend标识的流的唯一子流;
  • weight,为当前流分配新的权重weight

h2_frame.priority.unpack

语法local ok, err = h2_frame.priority.unpack(pf, src, stream)

从Lua字符串src反序列化PRIORITY帧,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帧序列化到目标dstdst必须是一个类数组的Lua表。

rf必须是一个类哈希的Lua表,包含:

  • header,帧头;
  • error_code,错误代码;

h2_frame.rst_stream.unpack

语法local ok, err = h2_frame.rst_stream.unpack(rf, src, stream)

从Lua字符串src反序列化RST_STREAM帧。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)

创建一个带有错误代码error_code的RST_STREAM帧,属于流sid

h2_frame.settings.pack

语法h2_frame.settings.pack(sf, dst)

将SETTINGS帧序列化到目标dstdst必须是一个类数组的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)

从Lua字符串src反序列化SETTINGS帧。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)

创建一个带有标志flags和负载项payload的SETTINGS帧。

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帧序列化到目标dstdst必须是一个类数组的Lua表。

pf必须是一个类哈希的Lua表,包含:

  • header,帧头;
  • opaque_data_hi,对应PING数据的最高32位值;
  • opaque_data_lo,对应PING数据的最低32位值;

h2_frame.ping.unpack

语法local ok, err = h2_frame.ping.unpack(pf, src, stream)

从Lua字符串src反序列化PING帧。src的长度必须至少为sf.header.length中指定的大小。

pf应为一个类哈希的Lua表,已经包含当前PING帧的头,即pf.header

最后一个参数stream指定当前PING帧所属的流(必须是根流)。

如果失败,将返回nil和错误代码。

h2_frame.goaway.pack

语法h2_frame.goaway.pack(gf, dst)

将GOAWAY帧序列化到目标dstdst必须是一个类数组的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)

从Lua字符串src反序列化GOAWAY帧。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)

创建一个带有最后一个对等方初始化的流标识符last_sid和错误代码error_code的GOAWAY帧。可选地,带有调试数据debug_data

h2_frame.window_update.pack

语法h2_frame.window_update.pack(wf, dst)

将WINDOW_UPDATE帧序列化到目标dstdst必须是一个类数组的Lua表。

wf必须是一个类哈希的Lua表,包含:

  • header,帧头;
  • window_size_increment,窗口大小增量;

h2_frame.window_update.unpack

语法local ok, err = h2_frame.window_update.unpack(wf, src, stream)

从Lua字符串src反序列化WINDOW_UPDATE帧。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)

创建一个带有流标识符sid的WINDOW_UPDATE帧,并增大由window指定的窗口大小。

h2_frame.headers.pack

语法h2_frame.headers.pack(hf, dst)

将HEADERS帧序列化到目标dstdst必须是一个类数组的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)

从Lua字符串src反序列化HEADERS帧,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)

创建一个带有块片段frags的HEADERS帧。

参数pri可以用于指定流依赖关系,pri应为一个类哈希的Lua表,包含:

  • sid,依赖的流标识符;
  • excl,流sid是否将成为依赖流的唯一子流;
  • weight,定义当前流(由sid指定)的权重;

pad指定填充数据,这是可选的。

end_stream为true时,当前HEADERS帧将带有END_STREAM标志,同样,当end_headers为true时,当前HEADERS帧将带有END_HEADERS标志。

需要注意的是,如果当前HEADERS帧不包含整个头,则根据HTTP/2协议必须跟随一个或多个CONTINUATION帧。

h2_frame.continuation.pack

语法h2_frame.continuation.pack(cf, dst)

将CONTINUATION帧序列化到目标dstdst必须是一个类数组的Lua表。

cf必须是一个类哈希的Lua表,包含:

  • header,帧头;
  • block_frags,原始HTTP头(经过hpack压缩后);

h2_frame.continuation.unpack

语法local ok, err = h2_frame.continuation.unpack(cf, src, stream)

从Lua字符串src反序列化CONTINUATION帧,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)

创建一个带有块片段frags的CONTINUATION帧。

end_headers为true时,当前CONTINUATION帧将带有END_HEADERS标志。

需要注意的是,如果当前CONTINUATION帧不包含整个头,则根据HTTP/2协议必须跟随一个或多个CONTINUATION帧。

sid指定当前CONTINUATION帧所属的流。

h2_frame.data.pack

语法h2_frame.data.pack(df, dst)

将DATA帧序列化到目标dstdst必须是一个类数组的Lua表。

df必须是一个类哈希的Lua表,包含:

  • header,帧头;
  • payload,HTTP请求/响应体;

h2_frame.data.unpack

语法local ok, err = h2_frame.data.unpack(df, src, stream)

从Lua字符串src反序列化DATA帧,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)

创建一个带有负载payload的DATA帧。

pad指定填充数据,这是可选的。

last为true时,当前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

此模块实现了一些低级HPACK API。

要加载此模块,只需执行以下操作:

local hpack = require "resty.http2.hpack"

hpack.encode

语法hpack.encode(src, dst, lower)

将Lua字符串src编码到目标dstdst必须是一个类数组的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](#hdecode)将分析h.cached中的数据。

目前,h2_frame.headers.unpackh2_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)

返回描述错误代码code的Lua字符串,如果错误代码未知,将返回"unknown error"

h2_error.is_stream_error

语法local ok = h2_error.is_stream_error(code)

判断错误代码code是否为流级错误。

另见

GitHub

您可以在nginx-module-http2的GitHub仓库中找到此模块的其他配置提示和文档。