使用Lua插件

Lua是一种轻量级、高效的脚本语言,在云原生API网关开发中,Lua可以用于编写和执行各种网关程序,例如API网关、消息网关、反向代理等。通过Lua脚本,开发人员可以实现请求的路由、过滤、鉴权等功能,并进行定制化的处理。在一些代理中,比如Nginx和Envoy,Lua可以被嵌入用于处理请求和响应,并进行日志输出和其他定制化操作。本文中的Lua脚本用于在Envoy代理中处理请求和响应,并将请求和响应的头部和正文信息以日志的形式输出。

使用限制

云原生API网关版本为2.0.0以上。

注意事项

出于安全考虑,云原生API网关默认禁用以下Lua库和函数。

  • debug.debug

  • debug.getfenv

  • debug.getregistry

  • dofile

  • io

  • loadfile

  • os.execute

  • os.getenv

  • os.remove

  • os.rename

  • os.tmpname

操作步骤

  1. 登录云原生API网关控制台

  2. 在左侧导航栏,选择实例,并在顶部菜单栏选择地域。

  3. 实例页面,单击目标网关实例名称。

  4. 在左侧导航栏,选择插件,单击安装插件

  5. 安装插件页面的搜索框中搜索lua,找到lua资源卡片并单击安装并配置

  6. 规则配置页面,配置如下参数。

    • 生效范围处,选择Lua插件的应用范围。

    • 选择接口/路由级插件规则域名级插件规则时单击添加规则,在添加规则弹框中配置相关参数,然后在插件规则框中填写Lua代码,最后单击确定

    • 选择实例级插件规则时,在插件规则框中填写Lua代码,最后单击保存

API参考

关于网关提供的Lua API详细信息,请参见Lua

如果您有使用JSON的诉求,可通过如下方式。

  • 引入JSON包:local json = require "json"

  • 序列化JSON:local json_str = json.encode(obj)

  • 反序列化JSON:local obj = json.decode(json_str)

如果在序列化或反序列化时出现错误,Lua将调用error函数抛出错误,并终止当前处理。

常见用例

打印完整的请求应答信息到插件日志

  1. 登录云原生API网关控制台

  2. 在左侧导航栏,选择实例,并在顶部菜单栏选择地域。

  3. 实例页面,单击目标网关实例名称。

  4. 在左侧导航栏,选择插件,然后单击目标插件操作列下的规则配置

  5. 规则配置中配置以下Lua代码。

    说明

    根据此代码配置,只会打印以下content-type类型的请求Body和应答Body,且Body不能超过1024字节(1 KB)。

    • application/x-www-form-urlencoded

    • application/json

    • text/plain

    local maxBodySize = 1024
    
    function check_content_readable(type)
      if type == nil then
        return false
      end
      if string.find(type, "application/x-www-form-urlencoded",1,true) or string.find(type, "application/json",1,true) or string.find(type, "text/plain",1,true) then
         return true
      end
      return false
    end
    
    function envoy_on_request(request_handle)
      local headers = request_handle:headers()
      local headersStr = ""
      local contentType
      for key, value in pairs(headers) do
        if key == "content-type" then
           contentType = value
        end
        headersStr = headersStr  .. key .. "=" .. value .. ", "
      end
      request_handle:streamInfo():dynamicMetadata():set("envoy.lua","request_headers",headersStr)
      local requestBody = ""
      if check_content_readable(contentType) then
        for chunk in request_handle:bodyChunks() do
          if (chunk:length() > 0) then
            requestBody = requestBody .. chunk:getBytes(0, chunk:length())
          end
          if (#requestBody > maxBodySize) then
             requestBody = requestBody .. "<truncated>"
             break
          end
        end
      end
      request_handle:streamInfo():dynamicMetadata():set("envoy.lua","request_body",string.gsub(requestBody,"\n","\\n"))
    end
    
    function envoy_on_response(response_handle)
      local headers = response_handle:headers()
      local headersStr = ""
      local contentType
      local contentEncoding = false
      for key, value in pairs(headers) do
        if key == "content-type" then
           contentType = value
        elseif key == "content-encoding" then
           contentEncoding = true
        end
        headersStr = headersStr .. key .. "=" .. value .. ", "
      end
      local responseBody = ""
      if check_content_readable(contentType) and not contentEncoding then
        for chunk in response_handle:bodyChunks() do
          if (chunk:length() > 0) then
            responseBody = responseBody .. chunk:getBytes(0, chunk:length())
          end
          if (#responseBody > maxBodySize) then
             responseBody = responseBody .. "<truncated>"
             break
          end
        end
      end
      local reqHeaders = ""
      local reqBody = ""
      local metadata = response_handle:streamInfo():dynamicMetadata():get("envoy.lua")
      if metadata ~= nil then
        local headers = response_handle:streamInfo():dynamicMetadata():get("envoy.lua")["request_headers"]
        if headers ~= nil then
          reqHeaders = headers
        end
        local body = response_handle:streamInfo():dynamicMetadata():get("envoy.lua")["request_body"]
        if body ~= nil then
          reqBody = body
        end
      end
      response_handle:logInfo("request Headers: [" .. reqHeaders .. "] request Body: [" .. string.gsub(reqBody,"\n","\\n") .. "] response Headers: [" .. headersStr .. "] response Body: [" .. string.gsub(responseBody,"\n","\\n") .. "]")
    end
  6. 单击目标Lua插件,选择日志页签,单击立即开启日志投递功能

  7. 日志投递开启后,插件日志将投递到SLS并可在页面中查看。

    如下所示,可以使用访问日志中的request-id,在Lua插件日志中找到完整的请求和响应信息。

    image

将完整的请求应答信息添加到访问日志

  1. 登录云原生API网关控制台

  2. 在左侧导航栏,选择实例,并在顶部菜单栏选择地域。

  3. 实例页面,单击目标网关实例名称。

  4. 在左侧导航栏,选择插件,然后单击目标插件操作列下的规则配置

  5. 规则配置中配置以下Lua代码。

  6. 网关的访问日志参数支持配置自定义动态元数据,首先需要在插件中定义元数据。以下代码一共定义了四个元数据信息,分别对应请求头、请求Body、响应头、响应Body:

    • envoy.lua:request_headers

    • envoy.lua:request_body

    • envoy.lua:response_headers

    • envoy.lua:response_body

    local maxBodySize = 1024
    
    function check_content_readable(type)
      if type == nil then
        return false
      end
      if string.find(type, "application/x-www-form-urlencoded",1,true) or string.find(type, "application/json",1,true) or string.find(type, "text/plain",1,true) then
         return true
      end
      return false
    end
    
    function envoy_on_request(request_handle)
      local headers = request_handle:headers()
      local headersStr = ""
      local contentType
      for key, value in pairs(headers) do
        if key == "content-type" then
           contentType = value
        end
        headersStr = headersStr  .. key .. "=" .. value .. ", "
      end
      request_handle:streamInfo():dynamicMetadata():set("envoy.lua","request_headers",headersStr)
      local requestBody = ""
      if check_content_readable(contentType) then
        for chunk in request_handle:bodyChunks() do
          if (chunk:length() > 0) then
            requestBody = requestBody .. chunk:getBytes(0, chunk:length())
          end
          if (#requestBody > maxBodySize) then
             requestBody = requestBody .. "<truncated>"
             break
          end
        end
      end
      request_handle:streamInfo():dynamicMetadata():set("envoy.lua","request_body",string.gsub(requestBody,"\n","\\n"))
    end
    
    function envoy_on_response(response_handle)
      local headers = response_handle:headers()
      local headersStr = ""
      local contentType
      local contentEncoding = false
      for key, value in pairs(headers) do
        if key == "content-type" then
           contentType = value
        elseif key == "content-encoding" then
           contentEncoding = true
        end
        headersStr = headersStr .. key .. "=" .. value .. ", "
      end
      response_handle:streamInfo():dynamicMetadata():set("envoy.lua","response_headers",headersStr)
      local responseBody = ""
      if check_content_readable(contentType) and not contentEncoding then
        for chunk in response_handle:bodyChunks() do
          if (chunk:length() > 0) then
            responseBody = responseBody .. chunk:getBytes(0, chunk:length())
          end
          if (#responseBody > maxBodySize) then
             responseBody = responseBody .. "<truncated>"
             break
          end
        end
      end
      response_handle:streamInfo():dynamicMetadata():set("envoy.lua","response_body",string.gsub(responseBody,"\n","\\n"))
    end
  7. 在左侧导航栏,选择参数配置。在可观测性参数区域对日志格式进行调整。在日志格式调整弹框中添加对应的元数据信息,就可以在访问日志中看到对应的信息。

    image