跳转至

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): 响应状态,例如 200
  • res.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.statusres.headersres.body

注意 当在回调函数中返回 true 时,过滤过程将被中断。

如果出错,则返回 nil 和描述错误的字符串。

read

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

完整响应的流式解析器。

用户只需重复调用 read 方法,直到返回 nil 的令牌类型。对于从 read 方法返回的每个令牌,只需检查第一个返回值以获取当前令牌类型。令牌类型可以是 statuslineheaderheader_endbodybody_endeof。有关 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 仓库 中找到有关此模块的其他配置提示和文档。