跳转至

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

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

本文档描述了 lua-resty-http v0.17.2,于 2024 年 2 月 29 日发布。


Lua HTTP 客户端 cosocket 驱动程序用于 OpenResty / ngx_lua

特性

  • HTTP 1.0 和 1.1
  • SSL
  • 对响应体的流式接口,以便可预测的内存使用
  • 无需手动连接步骤的单次请求的替代简单接口
  • 分块和非分块传输编码
  • 连接保持活动
  • 请求流水线
  • 尾部
  • 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("设置保持活动失败: ", err)
    return
end

-- 此时,连接将安全地返回到池中,或被关闭。

连接

new

syntax: httpc, err = http.new()

创建 HTTP 连接对象。如果失败,返回 nil 和描述错误的字符串。

connect

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

尝试连接到 Web 服务器,同时进行以下操作:

  • TCP 连接
  • SSL 握手
  • HTTP 代理配置

这样将创建一个独特的连接池名称,适合与 SSL 和/或基于代理的连接一起使用,因此此语法强烈推荐,而不是原始的(现已弃用) 仅 TCP 连接语法

选项表具有以下字段:

  • scheme: 要使用的方案,或 nil 表示 Unix 域套接字
  • host: 目标主机,或 Unix 域套接字的路径
  • port: 目标主机的端口,默认为 80443,具体取决于方案
  • pool: 自定义连接池名称。选项参见 OpenResty 文档,默认将成为使用 SSL/代理属性构造的池名称,这对于安全的连接重用很重要。如有疑问,请留空!
  • pool_size: 选项参见 OpenResty 文档
  • backlog: 选项参见 OpenResty 文档
  • proxy_opts: 子表,默认为全局代理选项设置,见 set_proxy_options
  • ssl_reused_session: 选项参见 OpenResty 文档
  • ssl_verify: 选项参见 OpenResty 文档,默认为 true
  • ssl_server_name: 选项参见 OpenResty 文档
  • ssl_send_status_req: 选项参见 OpenResty 文档
  • 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

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 是“安全的”,因为它将根据请求类型有条件地关闭。具体来说,1.0 请求没有 Connection: Keep-Alive 将被关闭,1.1 请求带有 Connection: Close 也将被关闭。

成功时返回 1。出错时返回 nil, err。在上述情况下,如果连接被有条件地关闭,则返回 2 和错误字符串 connection must be closed,以便与意外错误区分开。

有关参数文档,请参见 OpenResty 文档

set_proxy_options

syntax: httpc:set_proxy_options(opts)

配置与此客户端实例一起使用的 HTTP 代理。opts 表期望以下字段:

  • http_proxy: 用于 HTTP 请求的代理服务器 URI
  • http_proxy_authorization: 用于 http_proxy 的默认 Proxy-Authorization 头值,例如 Basic ZGVtbzp0ZXN0,如果存在 Proxy-Authorization 请求头,将被覆盖。
  • https_proxy: 用于 HTTPS 请求的代理服务器 URI
  • https_proxy_authorization: 用于 https_proxyhttp_proxy_authorization,由于 HTTPS 在连接时进行授权,因此无法通过传递 Proxy-Authorization 请求头进行覆盖。
  • no_proxy: 不应被代理的主机的逗号分隔列表。

请注意,当使用已弃用的 仅 TCP 连接 连接语法时,此方法无效。

get_reused_times

syntax: times, err = httpc:get_reused_times()

参见 OpenResty 文档

close

syntax: ok, err = httpc:close()

参见 OpenResty 文档

请求

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

当请求成功时,res 将包含以下字段:

  • status: 状态码。
  • reason: 状态原因短语。
  • headers: 头部表。具有相同字段名的多个头部将以值表的形式呈现。
  • has_body: 布尔标志,指示是否有主体可读。
  • body_reader: 用于以流式方式读取主体的迭代器函数。
  • read_body: 将整个主体读取到字符串中的方法。
  • read_trailers: 在读取主体后,合并任何尾部到 headers 表下的方法。

如果响应有主体,则在同一连接用于另一个请求之前,您必须使用 read_bodybody_reader 读取主体。

request_uri

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

单次接口(见 用法)。由于此方法执行整个端到端请求,因此 params 中指定的选项可以包括上述 connectrequest 中的任何内容。请注意,params 中的 pathquery 字段将覆盖 uri 的相关组件(schemehostport 将始终从 uri 中获取)。

有 3 个额外参数用于控制保持活动:

  • keepalive: 设置为 false 以禁用保持活动并立即关闭连接。默认为 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 方法相同,但 params 是一个嵌套的参数表。每个请求按顺序发送,responses 作为响应句柄的表返回。例如:

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

此迭代器还可以用作请求参数中的主体字段的值,允许将请求主体流式传输到代理的上游请求中。

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 连接 语法的用户。

尝试通过给定的代理服务器连接到 Web 服务器。该方法接受以下参数:

  • proxy_uri - 要使用的代理服务器的完整 URI(例如 http://proxy.example.com:3128/)。注意:仅支持 http 协议。
  • scheme - 在代理服务器和远程主机之间使用的协议(httphttps)。如果将 https 指定为方案,connect_proxy() 将发出 CONNECT 请求以通过代理服务器建立与远程主机的 TCP 隧道。
  • host - 要连接的远程主机的主机名。
  • port - 要连接的远程主机的端口。
  • proxy_authorization - 当 schemehttps 时,通过 CONNECT 发送到代理服务器的 Proxy-Authorization 头值。

如果在连接尝试期间发生错误,则此方法返回 nil 和描述错误的字符串。如果成功建立连接,则该方法返回 1

使用此 API 时需要记住几个关键点:

  • 如果方案为 https,则需要手动使用 ssl_handshake() 方法与远程服务器执行 TLS 握手,然后才能通过代理隧道发送任何请求。
  • 如果方案为 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?)

使用当前客户端请求参数执行请求,实际上是代理到连接的上游。请求主体将根据 request_body_chunk_size 以流式方式读取(请参见 客户端主体读取器文档)。

proxy_response

syntax: httpc:proxy_response(res, chunksize?)

根据给定的 res 设置当前响应。确保不会将逐跳头部发送到下游,并将根据 chunksize 读取响应(请参见 主体读取器文档)。

GitHub

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