Lua

    HTTP Lua 过滤器允许 Lua 脚本在请求和响应流期间运行。 被作为运行时使用。因此,被支持的 Lua 版本大多是带一些 5.2 特性的 5.1版。更多细节参见 LuaJIT 文档

    该过滤器仅支持加载配置中内置的 Lua 代码。如果需要本地文件系统中的代码,可以使用内置小脚本从本地环境加载剩余的代码。

    在高层次对过滤器设计以及 Lua 支持概括如下:

    • 所有 Lua 环境都是 。这意味着没有真正的全局数据。任何在加载时创建和存在的全局数据将只在每个工作线程中可见。将来可能会通过添加 API 来支持真正的全局支持。
    • 所有脚本作为协程运行。这意味着,即使它们可能执行复杂的异步操作,它们以同步方式编写。这使得脚本相当容易被编写。所有网络/异步处理通过一组 API 被 Envoy 执行。Envoy 将适当退出脚本并在异步任务完成后恢复。
    • 不要在脚本中执行阻塞操作。 Envoy API 被用于所有 IO,这一点对性能很关键。

    当前被支持的高层次特性:

    注意 预计这个列表将在生产环境不断地使用该过滤器的过程中被扩充。API 的表面被有意地保持很小。以达到编写脚本极度简单和安全的目的。在非常复杂或高性能的使用案例中,默认用原生的 C++ 过滤器 API。

    • 在请求流、响应流或二者同时流入时,检查头,正文和尾。
    • 修改头和尾部。
    • 阻塞并缓存全部请求/响应正文用作检查。
    • 执行对外的异步 HTTP 调用至一个上游主机。可以在缓存正文数据时执行这样的调用,因此,当调用结束时,上游头可以被修改。
    • 执行一个直接响应并略过更多的过滤器循环。例如,一个脚本可以做一个上游 HTTP 调用做鉴权,并随后直接以一个 403 响应代码响应。

    脚本示例

    本节提供了一些 Lua 脚本的具体例子,作为更温和的介绍和快速开始。关于支持的 API 的更多细节,请参照流处理 API

    1. -- 使用下面的头、正文和超时对上游主机做一个 HTTP 调用。
    2. "lua_cluster",
    3. {
    4. [":method"] = "POST",
    5. [":path"] = "/",
    6. [":authority"] = "lua_cluster"
    7. },
    8. "hello world",
    9. 5000)
    10. -- 添加来自 HTTP 调用的信息到将要被发送到过滤器链中下一个过滤器的头。
    11. request_handle:headers():add("upstream_foo", headers["foo"])
    12. request_handle:headers():add("upstream_body_size", #body)
    13. end
    1. function envoy_on_request(request_handle)
    2. -- 做一个 HTTP 调用。
    3. local headers, body = request_handle:httpCall(
    4. "lua_cluster",
    5. {
    6. [":method"] = "POST",
    7. [":path"] = "/",
    8. [":authority"] = "lua_cluster"
    9. },
    10. "hello world",
    11. -- 直接响应并从 HTTP 调用设置一个头。没有更多的过滤器循环发生。
    12. request_handle:respond(
    13. {[":status"] = "403",
    14. ["upstream_foo"] = headers["foo"]},
    15. "nope")
    16. end

    当 Envoy 加载配置中脚本,它寻找脚本定义的两个全局函数:

    1. function envoy_on_request(request_handle)
    2. end
    3. function envoy_on_response(response_handle)
    4. end

    一个脚本可以定义这两个函数中的一个或者两个。在请求路径中,Envoy 将作为一个协程运行 envoy_on_request,传递一个 API 句柄。在相应路径中,Envoy 将作为一个协程运行 envoy_on_response,传递一个 API 句柄。

    注意

    所有与 Envoy 通过传递的流句柄发生的交互是至关重要的。流句柄不应该被赋值给任何全局变量,而且不应该被用于协程的外部。如果该句柄使用不正确,Envoy 将失败。

    下列在流句柄上的方法被支持:

    1. headers = handle:headers()

    返回流的头。只要它们还没有被发送到头链中的下一个过滤器,就可以被修改。例如,它们可以在一个 httpCall() 或者 body() 调用返回后被修改。如果头在任何其他情况下被修改,脚本将失败。

    body()

      返回流的正文。这个调用将造成 Envoy 退出脚本直到整个正文被缓存。注意,所有缓存必须遵从适当的流控策略。Envoy 将不会缓存比连接管理器允许的更多的数据。

      返回一个缓存对象

      bodyChunks()

      1. iterator = handle:bodyChunks()

      返回一个迭代器,它可被用于在所有正文块到达时循环访问它们。Envoy 将在块之间退出脚本,但是 将不会缓存 它们。这可被一个脚本用于在数据流入时做检查。

      1. for chunk in request_handle:bodyChunks() do
      2. request_handle:log(0, chunk:length())
      3. end

      迭代器返回的每一块是一个缓存对象

      trailers()

      返回流的尾。如果没有尾,可能返回空。尾在被发送到下一个过滤器之前可能被修改。

      返回一个头对象

      log*()

      1. handle:logTrace(message)
      2. handle:logDebug(message)
      3. handle:logInfo(message)
      4. handle:logWarn(message)
      5. handle:logErr(message)
      6. handle:logCritical(message)

      使用 Envoy 的应用日志功能保存一条消息。message 是被保存的字符串。

      1. headers, body = handle:httpCall(cluster, headers, body, timeout)

      对一台上游主机做一个 HTTP 调用。Envoy 将推出脚本直到调用结束或者有一个错误。cluster 是一个字符串,映射为一个配置好的集群管理器。headers 一个要发送的键值对的表。注意,:method:path:authority 头必须被设置。body 是一个可选的要发送的正文数据的字符串。timeout 是一个整数,指定以微秒为单位的调用超时。

      返回 headers,这是响应头的一个表。返回 body,这是响应正文的字符串。如果没有正文,则返回空。

      respond()

      立即响应并且不做更多的循环。这个调用仅在请求流中合法。另外,只有当请求头还没有被传递给后续过滤器时才可能。这意味着,下面的 Lua 代码是不合法的:

      1. function envoy_on_request(request_handle)
      2. for chunk in request_handle:bodyChunks() do
      3. request_handle:respond(
      4. {[":status"] = "100"},
      5. "nope")
      6. end
      7. end

      headers 一张要发送的键值对的表。注意,:status 头必须被设置。body 是一个字符串并提供了可选的响应正文,可能为空。

      metadata()

      1. metadata = handle:metadata()

      返回当前路由条目元数据。注意,元数据应该在过滤器名下指定,例如envoy.lua。下面是一个 在路由条目metadata 的例子。

      1. metadata:
      2. filter_metadata:
      3. envoy.lua:
      4. foo: bar
      5. baz:
      6. - bad
      7. - baz

      头对象 API

      add()

      1. headers:add(key, value)

      添加一个头。key 是一个提供头键的字符串。value是一个提供头值的字符串。

      注意

      Envoy 特殊处理特定头。这些被称为 O(1) 或 inline 头。一个内建头的列表可以在找到。如果一个内建头已经呈现在头映射中,add() 将没有效果。如果试图去 add() 一个非内建的头,附加的头将会被添加,因此,合成的头包含多个同名的头。如果想要将头换为另一个值,可以考虑使用 replace 函数。注意,我们理解这个行为令人迷惑并且我们可能在将来的一版中改变。

      get()

      得到一个头。key 是一个提供头键的字符串。返回一个头值的字符串,如果没有这个头则返回空。

      1. for key, value in pairs(headers) do
      2. end

      循环访问每个头。key 是一个提供头键的字符串。value是一个提供头值的字符串。

      注意

      在当前的实现中,头不能在循环期间被修改。另外,如果想要在循环后修改头,循环必须完成。这意味着,不用过早使用跳出或其他机制退出循环体。这个要求可能在将来被放松。

      remove()

      1. headers:remove(key)

      移除一个头。 key 提供了要移除的头键。

      replace()

      1. headers:replace(key, value)

      替换一个头。key 是一个提供头键的字符串。value是一个提供头值的字符串。如果头不存在,它用 add() 函数添加。

      length()

      1. size = buffer:length()

      获得以字节为单位的缓存的大小。返回一个整数。

      getBytes()

      1. buffer:getBytes(index, length)

      从缓存中获得字节。Envoy 默认将不会拷贝所有的缓存数据给 Lua。这将造成一个缓存段被拷贝。index 是一个整数并提供了缓存要开始拷贝的索引。length 是一个整数并提供了要拷贝的缓存长度。index + length 必须小于缓存的长度。

      元数据对象 API

      1. metadata:get(key)

      获得一个元数据。 key 是一个提供了元数据键的字符串。返回给定元数据键对应的值。值的类型可以是: null, boolean, number, stringtable

      __pairs()

      1. for key, value in pairs(metadata) do
      2. end