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
要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua。
本文档描述了 lua-resty-httpipe v0.5,于 2015 年 11 月 25 日发布。
Lua HTTP 客户端 cosocket 驱动程序用于 OpenResty / ngx_lua。
特性
- 支持 HTTP 1.0/1.1 和 HTTPS
- 灵活的接口设计
- 流式读取和上传
- 分块编码请求/响应主体
- 设置读取和发送操作的超时
- 限制最大响应主体大小
- 保持连接
概述
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 管道一样
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?)
尝试连接到 Web 服务器。
在实际解析主机名并连接到远程后端之前,此方法将始终查找连接池中由先前调用此方法创建的匹配空闲连接。
可以将可选的 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(number): 响应状态,例如 200res.headers(table): 包含响应头的 Lua 表。res.body(string): 原始响应主体。res.body_reader(function): 用于以流式方式读取主体的迭代器函数。res.pipe(httpipe): 一个新的 http 管道,默认使用当前的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 字段的值,允许将请求主体流式传输到代理的上游请求中。
local client_body_reader, err = hp:get_client_body_reader()
local res, err = hp:request{
path = "/helloworld",
body = client_body_reader,
}
GitHub
您可以在 nginx-module-httpipe 的 GitHub 仓库 中找到有关此模块的其他配置提示和文档。