WEB应用

APISIX高级路由之通过Body参数转发请求

Jager · 10月28日 · 2021年 · · 3825次已读

最近在主导部门统一接入服务项目,其中涉及 7 层网关组件的选型。在过去一年多时间内,我们部门的业务主要使用的 Kong 网关,也打算作为一个长期方案,结果杀出了 APISIX 这个黑马,经过分析讨论,最终敲定基于 APISIX 来开发统一接入服务。所以,最近有一些零散的折腾存货,会陆续整理到博客,方便有相同诉求的朋友。

APISIX 的高级路由非常厉害,可以基于任意变量来做转发路由,比如可以基于 Header、cookie、querystring 参数等。而我们这里历史上有个非常变态的用法:基于 Body 里面某个参数的值来路由,即不同的值要转发到不同的后端 IP:PORT(这个问题主要是因为服务拆分时偷懒,没有推动客户端修改请求留下的尾巴)。

APISIX 前面的版本中,发现并不支持解析 Body 然后通过 Body 里面的指定参数来做路由,最近更新到 2.10.0 LTS 版本后,发现这个特性赫然支持了:

如下表所示,目前 APISIX 的 route 里面已经支持植入一个 lua 函数,那么就变得非常灵活了!因此,上面这个变态需求也成为了可能。

根据需求,写了如下 lua 函数,意思是当 body 里面有个 foo 参数且值为 bar 的时候命中此路由:

function(vars)
    local core = require ('apisix.core')
    local body, err = core.request.get_body()
    if not body then
        return false
    end
  
    local data, err = core.json.decode(body)
    if not data then
        return false
    end
  
    -- 当匹配 body 里面 foo 字段等于 bar 的时候,路由生效
    if data['foo'] == 'bar' then
        return true
    end
  
    return false
  end

加到 APISIX 的路由当中,示例代码如下(这里关键是需要加\n 来换行):

{
  "uri": "/hello",
  "filter_func": "function(vars)\n  local core = require ('apisix.core')\n  local body, err = core.request.get_body()\n  if not body then\n      return false\n  end\n\n  local data, err = core.json.decode(body)\n  if not data then\n      return false\n  end\n\n  if data['foo'] == 'bar' then\n      return true\n  end\n\n  return false\nend",
  "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1": 9080
        }
    }
  "status": 1
}

需要注意的是,加了这条路由之后,对 /hello 的所有请求都会命中到这个路由,并不会因为 Body 里面没有 foo 或者 foo 的值不为 bar 而出现 404 报错。这里也不太理解官方的设计逻辑,可能是因为此时只有一条路由,已经没有其他选择了。

因此,还需要加一条不含过滤的路由,转发到另一个后端,协议如下:

{
  "uri": "/hello",
  "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1": 9081
        }
    }
  "status": 1
}

此时,请求才能正常被区分开来,即请求 Body 里面带了 foo 且值为 bar 的请求,会转发到 9080 端口,否则转发到 9081 端口。

0 条回应
  1. 马内 2021-10-29 · 18:59

    网站每日ip 1千,交换友链,https://money1.us/521

  2. yndsht 2021-11-3 · 9:26

    老张干啥去了,这么久不更新博客了,今天终于更新了!

  3. 二花 2022-9-1 · 0:28

    哇柳暗花明啊,这就是我想要的功能,我这两天还一直在研究自己写个插件来干这个活,能直接 filter_func 就方便多了,超级感谢!

    我这边也是业务侧有些奇葩需求要用 post 的 body 参数来进行路由,而且这个参数还 base64 + json 套娃,

    • avatar
      Jager 2022-10-28 · 11:40

      用这个方案需要加两条路由,生产环境实测发现当body超过一定体积的时候可能会404报错,解决办法:将apisix配置里面的router_http值设置为radixtree_host_uri

      • 二花 2023-5-30 · 14:24

        哈哈补充下相关信息,我们最终还是在后端服务上实现了这个需求,在网关上搞风险太大了,而且后端实际仍然需要解码,这个方案数据要在网关跟后端被解码两次,成本上也不划算。

      • 二花 2023-5-30 · 14:27

        之前业务侧这需求是先 base64 解码,解出来的二进制数据再 gzip 解压,解压完是个 json,再从 json 里提取某个 value,根据其前缀决定路由,其实当时我就觉得这需求有点离谱...后面沟通也还是决定在他们后端做了。

  4. 张戈粉丝 2022-10-28 · 11:34

    看到你的文章,感觉很不错,想与你友情链接
    网站名:电脑教程网
    网站域名:https://dnjcw.com.cn/
    同意的话给我发邮件internetyewu@163.com

  5. ioii 2023-5-18 · 14:36

    您好,这句(加了这条路由之后,对 /hello 的所有请求都会命中到这个路由,并不会因为 Body 里面没有 foo 或者 foo 的值不为 bar 而出现 404 报错)
    因为只有这条路由,请求都走这条能理解,但是按照匹配规则,匹配不到body的值就不会转发到上游服务才对,是老版本的特性?,我使用的是2.15版本,我看是不会的.

    • avatar
      Jager 2023-12-28 · 9:58

      这个文章测试的时候应该是2.11版