This is an automated email from the ASF dual-hosted git repository.

AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 753914238 feat: add traffic-label plugin (#13342)
753914238 is described below

commit 753914238a8e43797a5362fc2e91c302c6a62a4f
Author: AlinsRan <[email protected]>
AuthorDate: Tue May 12 11:49:51 2026 +0800

    feat: add traffic-label plugin (#13342)
---
 apisix/cli/config.lua                   |   1 +
 apisix/plugins/traffic-label.lua        | 222 +++++++++++
 conf/config.yaml.example                |   1 +
 docs/en/latest/config.json              |   1 +
 docs/en/latest/plugins/traffic-label.md | 158 ++++++++
 docs/zh/latest/config.json              |   1 +
 docs/zh/latest/plugins/traffic-label.md | 158 ++++++++
 t/admin/plugins.t                       |   1 +
 t/plugin/traffic-label.t                | 643 ++++++++++++++++++++++++++++++++
 t/plugin/traffic-label2.t               | 553 +++++++++++++++++++++++++++
 10 files changed, 1739 insertions(+)

diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua
index 2af80eb35..f6daf6e9d 100644
--- a/apisix/cli/config.lua
+++ b/apisix/cli/config.lua
@@ -247,6 +247,7 @@ local _M = {
     "gzip",
     -- deprecated and will be removed in a future release
     -- "server-info",
+    "traffic-label",
     "traffic-split",
     "redirect",
     "response-rewrite",
diff --git a/apisix/plugins/traffic-label.lua b/apisix/plugins/traffic-label.lua
new file mode 100644
index 000000000..65eb71e53
--- /dev/null
+++ b/apisix/plugins/traffic-label.lua
@@ -0,0 +1,222 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+local core          = require("apisix.core")
+local proxy_rewrite = require("apisix.plugins.proxy-rewrite")
+local expr          = require("resty.expr.v1")
+local roundrobin    = require("resty.roundrobin")
+local ipairs        = ipairs
+local pairs         = pairs
+
+local lrucache = core.lrucache.new({
+    ttl = 0, count = 512
+})
+
+
+local schema = {
+    type = "object",
+    properties = {
+        rules = {
+            type = "array",
+            items = {
+                type = "object",
+                properties = {
+                    match = {
+                        type = "array",
+                        items = {
+                            anyOf = {
+                                {
+                                    type = "array",
+                                },
+                                {
+                                    type = "string",
+                                },
+                            }
+                        },
+                        minItems = 1,
+                    },
+                    actions = {
+                        type = "array",
+                        items = {
+                            type = "object",
+                            properties = {
+                                set_headers = {
+                                    description = "new headers for request",
+                                    type = "object",
+                                    minProperties = 1,
+                                },
+                                weight = {
+                                    type = "integer",
+                                    default = 1,
+                                    minimum = 1,
+                                },
+                            },
+                        },
+                        minItems = 1,
+                    },
+                },
+                required = {"actions"}
+            },
+            minItems = 1,
+        }
+    },
+    required = {"rules"}
+}
+
+local plugin_name = "traffic-label"
+
+local _M = {
+    version = 0.1,
+    -- priority: fault-injection proxy-mirror *-auth > traffic-label > 
traffic-split
+    priority = 967,
+    name = plugin_name,
+    schema = schema
+}
+
+
+local function check_set_headers_schema(conf)
+    local header_conf = {
+        headers = conf
+    }
+
+    return proxy_rewrite.check_schema(header_conf)
+end
+
+
+local function set_req_headers(header_conf, ctx)
+    local conf = {
+        headers = header_conf
+    }
+
+    -- reuse proxy-rewrite plugin's logic
+    if conf.headers then
+        if not conf.headers_arr then
+            conf.headers_arr = {}
+
+            for field, value in pairs(conf.headers) do
+                core.table.insert_tail(conf.headers_arr, field, value)
+            end
+        end
+
+        local field_cnt = #conf.headers_arr
+        for i = 1, field_cnt, 2 do
+            core.request.set_header(ctx, conf.headers_arr[i],
+                                    
core.utils.resolve_var(conf.headers_arr[i+1], ctx.var))
+        end
+    end
+end
+
+
+local support_action = {
+    ["set_headers"] = {
+        check_schema = check_set_headers_schema,
+        handle = set_req_headers
+    }
+}
+
+
+function _M.check_schema(conf)
+    local ok, err = core.schema.check(schema, conf)
+    if not ok then
+        return false, err
+    end
+
+    for _, rule in ipairs(conf.rules) do
+        local ok, err = expr.new(rule.match or {})
+        if not ok then
+            return false, "failed to validate the 'match' expression: " ..
+                            core.json.encode(rule.match) .. " err: " .. err
+        end
+
+        for _, action in ipairs(rule.actions) do
+            for name, conf in pairs(action) do
+                if name == "weight" then
+                    goto CONTINUE
+                end
+
+                local item = support_action[name]
+                if not item then
+                    return false, "not supported action: " .. name
+                end
+
+                local ok, err = support_action[name].check_schema(conf)
+                if not ok then
+                    return false, "failed to validate the '" .. name .. "' 
action: " .. err
+                end
+
+                ::CONTINUE::
+            end
+        end
+    end
+
+    return true
+end
+
+
+local function new_rr_obj(actions)
+    local id_weight_map = {}
+    for i, action in ipairs(actions) do
+        id_weight_map[i] = action.weight
+    end
+
+    return roundrobin:new(id_weight_map)
+end
+
+
+local function next_action(actions)
+    local rr_up, err = lrucache(actions, nil, new_rr_obj, actions)
+    if not rr_up then
+        core.log.error("lrucache roundrobin failed: ", err)
+        return false
+    end
+
+    local id = rr_up:find()
+    return actions[id]
+end
+
+
+function _M.access(conf, ctx)
+    local match_result
+
+    if not conf.rules_arr then
+        conf.rules_arr = {}
+
+        for _, rule in ipairs(conf.rules) do
+            -- if no rule.match, use {} to match all request
+            local expr, _ = expr.new(rule.match or {})
+            core.table.insert_tail(conf.rules_arr, expr)
+        end
+    end
+
+    for i, rule in ipairs(conf.rules) do
+        local expr = conf.rules_arr[i]
+        match_result = expr:eval(ctx.var)
+
+        if match_result then
+            local action = next_action(rule.actions)
+            -- only one action is currently supported
+            for name, conf in pairs(action) do
+                if name ~= "weight" then
+                    return support_action[name].handle(conf, ctx)
+                end
+            end
+        end
+    end
+end
+
+
+return _M
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index 0e068d542..2e900bb18 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -530,6 +530,7 @@ plugins:                           # plugin list (sorted by 
priority)
   #- brotli                        # priority: 996
   - gzip                           # priority: 995
   #- server-info                    # priority: 990
+  - traffic-label                  # priority: 967
   - traffic-split                  # priority: 966
   - redirect                       # priority: 900
   - response-rewrite               # priority: 899
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index e5459e5bd..836e607d2 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -166,6 +166,7 @@
             "plugins/proxy-mirror",
             "plugins/api-breaker",
             "plugins/traffic-split",
+            "plugins/traffic-label",
             "plugins/request-id",
             "plugins/proxy-control",
             "plugins/client-control",
diff --git a/docs/en/latest/plugins/traffic-label.md 
b/docs/en/latest/plugins/traffic-label.md
new file mode 100644
index 000000000..32e8bbaa2
--- /dev/null
+++ b/docs/en/latest/plugins/traffic-label.md
@@ -0,0 +1,158 @@
+---
+title: traffic-label
+keywords:
+  - Apache APISIX
+  - API Gateway
+  - Plugin
+  - traffic-label
+  - traffic tagging
+  - canary release
+description: The traffic-label Plugin sets request headers based on 
configurable matching rules with weighted distribution, enabling traffic 
tagging and canary deployments.
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+<head>
+  <link rel="canonical" href="https://docs.api7.ai/hub/traffic-label"; />
+</head>
+
+## Description
+
+The `traffic-label` Plugin sets request headers based on configurable matching 
rules. Similar to the [workflow](./workflow.md) Plugin, it evaluates rules in 
order and executes an action on the first match. The key difference is that 
`traffic-label` supports **weighted distribution** within each rule's action 
list, enabling proportional traffic labeling for canary deployments and A/B 
testing.
+
+Each rule consists of:
+
+- **`match`** — An optional list of conditions evaluated using 
[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). If 
omitted, the rule matches all requests.
+- **`actions`** — An array of actions to execute when the rule matches. Each 
action can set request headers and has an optional weight. Traffic is 
distributed proportionally across actions using weighted round-robin.
+
+Rules are evaluated in array order. Evaluation stops at the first matching 
rule.
+
+## Attributes
+
+| Name | Type | Required | Default | Valid values | Description |
+|------|------|----------|---------|--------------|-------------|
+| rules | array[object] | True | | | List of matching rules. Rules are 
evaluated in order; the first match wins. |
+| rules[].match | array | False | `[]` | | Match conditions using 
[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) syntax. 
Each element is either an expression array `[var, operator, value]` or the 
string `"OR"` / `"AND"` to control logical grouping. When omitted, the rule 
matches all requests. |
+| rules[].actions | array[object] | True | | | Actions to execute when the 
rule matches. Traffic is distributed across actions based on their `weight`. |
+| rules[].actions[].set_headers | object | False | | | Request headers to set. 
Overwrites an existing header or adds a new one. Values support NGINX variables 
such as `$remote_addr`. Format: `{"header-name": "value"}`. |
+| rules[].actions[].weight | integer | False | 1 | ≥ 1 | Relative weight for 
this action. Traffic proportion = `this weight / sum of all weights in the 
rule`. An action with only `weight` set passes traffic through without 
modification. |
+
+:::note
+
+- Rules are evaluated in order. Only the first matching rule executes; 
subsequent rules are skipped.
+- Currently, `set_headers` is the only supported action type.
+
+:::
+
+## Examples
+
+The examples below demonstrate how you can configure `traffic-label` in 
different scenarios.
+
+:::note
+
+```bash
+admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 
's/"//g')
+```
+
+:::
+
+### Label Traffic Based on Request Conditions
+
+The following example demonstrates how to set a request header `X-Server-Id` 
to different values based on the `?version` query parameter.
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes"; -X PUT \
+  -H "X-API-KEY: ${admin_key}" \
+  -d '{
+    "id": "traffic-label-route",
+    "uri": "/anything",
+    "plugins": {
+      "traffic-label": {
+        "rules": [
+          {
+            "match": [["arg_version", "==", "v1"]],
+            "actions": [{"set_headers": {"X-Server-Id": "100"}}]
+          },
+          {
+            "match": [["arg_version", "==", "v2"]],
+            "actions": [{"set_headers": {"X-Server-Id": "200"}}]
+          }
+        ]
+      }
+    },
+    "upstream": {
+      "type": "roundrobin",
+      "nodes": {"httpbin.org:80": 1}
+    }
+  }'
+```
+
+Send a request with `?version=v1`:
+
+```shell
+curl "http://127.0.0.1:9080/anything?version=v1";
+```
+
+The upstream will receive `X-Server-Id: 100`. Send a request with 
`?version=v2` and the upstream receives `X-Server-Id: 200`. Requests without a 
`version` parameter match no rule and pass through without modification.
+
+### Distribute Traffic Across Actions by Weight
+
+The following example demonstrates weighted distribution using 
`traffic-label`. When a request matches the rule, traffic is proportionally 
distributed across actions based on their `weight`:
+
+- 30% of requests: `X-Server-Id: 100`
+- 20% of requests: `X-API-Version: v2`
+- 50% of requests: pass through without modification
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes"; -X PUT \
+  -H "X-API-KEY: ${admin_key}" \
+  -d '{
+    "id": "traffic-label-route",
+    "uri": "/anything",
+    "plugins": {
+      "traffic-label": {
+        "rules": [
+          {
+            "match": [["uri", "==", "/anything"]],
+            "actions": [
+              {
+                "set_headers": {"X-Server-Id": "100"},
+                "weight": 3
+              },
+              {
+                "set_headers": {"X-API-Version": "v2"},
+                "weight": 2
+              },
+              {
+                "weight": 5
+              }
+            ]
+          }
+        ]
+      }
+    },
+    "upstream": {
+      "type": "roundrobin",
+      "nodes": {"httpbin.org:80": 1}
+    }
+  }'
+```
+
+The total weight is `3 + 2 + 5 = 10`. Across 10 requests, approximately 3 will 
have `X-Server-Id: 100`, 2 will have `X-API-Version: v2`, and 5 will pass 
through without any added header.
diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json
index 947339973..1a66321fa 100644
--- a/docs/zh/latest/config.json
+++ b/docs/zh/latest/config.json
@@ -155,6 +155,7 @@
             "plugins/proxy-mirror",
             "plugins/api-breaker",
             "plugins/traffic-split",
+            "plugins/traffic-label",
             "plugins/request-id",
             "plugins/proxy-control",
             "plugins/client-control",
diff --git a/docs/zh/latest/plugins/traffic-label.md 
b/docs/zh/latest/plugins/traffic-label.md
new file mode 100644
index 000000000..4180db92e
--- /dev/null
+++ b/docs/zh/latest/plugins/traffic-label.md
@@ -0,0 +1,158 @@
+---
+title: traffic-label
+keywords:
+  - Apache APISIX
+  - API 网关
+  - Plugin
+  - traffic-label
+  - 流量染色
+  - 灰度发布
+description: traffic-label 插件根据可配置的匹配规则和权重分发设置请求头,实现流量染色与灰度发布。
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+<head>
+  <link rel="canonical" href="https://docs.api7.ai/hub/traffic-label"; />
+</head>
+
+## 描述
+
+`traffic-label` 插件根据可配置的匹配规则为请求设置头部信息。与 [workflow](./workflow.md) 
插件类似,该插件按顺序对规则进行求值,并对第一个匹配的规则执行动作。两者的关键区别在于:`traffic-label` 
支持在每条规则的动作列表中设置**权重分发**,从而实现按比例的流量染色,适用于灰度发布和 A/B 测试场景。
+
+每条规则由以下两部分组成:
+
+- **`match`** — 使用 
[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 
定义的可选匹配条件。省略时该规则匹配所有请求。
+- **`actions`** — 规则命中时要执行的动作数组。每个动作可设置请求头,并带有可选的权重。流量按权重使用加权轮询算法分发到各动作。
+
+规则按数组顺序逐条求值,命中第一条后停止。
+
+## 属性
+
+| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
+|------|------|--------|--------|--------|------|
+| rules | array[object] | 是 | | | 匹配规则列表,按数组顺序求值,第一个命中的规则生效。 |
+| rules[].match | array | 否 | `[]` | | 使用 
[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 
语法定义的匹配条件。每个元素为表达式数组 `[变量, 运算符, 值]`,或字符串 `"OR"` / `"AND"` 用于控制逻辑分组。省略时匹配所有请求。 |
+| rules[].actions | array[object] | 是 | | | 规则命中时要执行的动作数组。流量按各动作的 `weight` 
进行分发。 |
+| rules[].actions[].set_headers | object | 否 | | | 
要设置的请求头。已存在同名头部时覆盖,不存在时新增。值支持 NGINX 变量,如 `$remote_addr`。格式:`{"头部名称": "值"}`。 |
+| rules[].actions[].weight | integer | 否 | 1 | ≥ 1 | 该动作的相对权重。流量占比 = 该动作权重 / 
规则中所有动作权重之和。仅设置 `weight` 而不配置其他动作,表示该比例的流量不做任何修改直接通过。 |
+
+:::note
+
+- 规则按顺序求值,仅执行第一个命中的规则,后续规则不再匹配。
+- 目前 `set_headers` 是唯一支持的动作类型。
+
+:::
+
+## 示例
+
+以下示例演示了如何在不同场景中使用 `traffic-label` 插件。
+
+:::note
+
+```bash
+admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 
's/"//g')
+```
+
+:::
+
+### 按请求条件为流量打标签
+
+以下示例演示如何根据 `?version` 查询参数,将请求头 `X-Server-Id` 设置为不同的值。
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes"; -X PUT \
+  -H "X-API-KEY: ${admin_key}" \
+  -d '{
+    "id": "traffic-label-route",
+    "uri": "/anything",
+    "plugins": {
+      "traffic-label": {
+        "rules": [
+          {
+            "match": [["arg_version", "==", "v1"]],
+            "actions": [{"set_headers": {"X-Server-Id": "100"}}]
+          },
+          {
+            "match": [["arg_version", "==", "v2"]],
+            "actions": [{"set_headers": {"X-Server-Id": "200"}}]
+          }
+        ]
+      }
+    },
+    "upstream": {
+      "type": "roundrobin",
+      "nodes": {"httpbin.org:80": 1}
+    }
+  }'
+```
+
+发送带 `?version=v1` 的请求:
+
+```shell
+curl "http://127.0.0.1:9080/anything?version=v1";
+```
+
+上游服务将收到 `X-Server-Id: 100`。发送 `?version=v2` 的请求时,上游服务将收到 `X-Server-Id: 200`。不带 
`version` 参数的请求不命中任何规则,将直接透传。
+
+### 按权重将流量分发到不同动作
+
+以下示例演示使用 `traffic-label` 进行加权分发。当请求命中规则时,流量按动作的 `weight` 按比例分配:
+
+- 30% 的请求:设置 `X-Server-Id: 100`
+- 20% 的请求:设置 `X-API-Version: v2`
+- 50% 的请求:直接通过,不做任何修改
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes"; -X PUT \
+  -H "X-API-KEY: ${admin_key}" \
+  -d '{
+    "id": "traffic-label-route",
+    "uri": "/anything",
+    "plugins": {
+      "traffic-label": {
+        "rules": [
+          {
+            "match": [["uri", "==", "/anything"]],
+            "actions": [
+              {
+                "set_headers": {"X-Server-Id": "100"},
+                "weight": 3
+              },
+              {
+                "set_headers": {"X-API-Version": "v2"},
+                "weight": 2
+              },
+              {
+                "weight": 5
+              }
+            ]
+          }
+        ]
+      }
+    },
+    "upstream": {
+      "type": "roundrobin",
+      "nodes": {"httpbin.org:80": 1}
+    }
+  }'
+```
+
+权重总和为 `3 + 2 + 5 = 10`。每 10 个请求中,约有 3 个将带有 `X-Server-Id: 100`,2 个将带有 
`X-API-Version: v2`,5 个将不带任何新增请求头直接通过。
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index f45256f17..b9f3846ca 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -112,6 +112,7 @@ limit-conn
 limit-count
 limit-req
 gzip
+traffic-label
 traffic-split
 redirect
 response-rewrite
diff --git a/t/plugin/traffic-label.t b/t/plugin/traffic-label.t
new file mode 100644
index 000000000..23e289a48
--- /dev/null
+++ b/t/plugin/traffic-label.t
@@ -0,0 +1,643 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+no_shuffle();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $extra_yaml_config = <<_EOC_;
+plugins:
+    - traffic-label
+    - proxy-rewrite
+_EOC_
+
+    if (!$block->extra_yaml_config) {
+        $block->set_value("extra_yaml_config", $extra_yaml_config);
+    }
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+});
+
+run_tests();
+
+
+__DATA__
+
+=== TEST 1: Use unsupported action
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["uri", "==", "/echo"]
+                                        ],
+                                        "actions":[
+                                            {
+                                                "add_headers": {
+                                                    "X-server-id": 100
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- error_code: 400
+--- response_body eval
+qr/not supported action: add_headers/
+
+
+
+=== TEST 2: Only one operator are supported in the same level
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            "AND",
+                                            "OR",
+                                            ["uri", "==", "/echo"],
+                                            ["arg_foo", "==", "bar"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- error_code: 500
+--- error_log eval
+qr/bad argument/
+
+
+
+=== TEST 3: use traffic-label plugin to override one req header
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["arg_foo", "==", "bar"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 4: trigger traffic-label, mismatch
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 200
+
+
+
+=== TEST 5: trigger traffic-label
+--- request
+GET /echo?foo=bar
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
+
+
+
+=== TEST 6: use traffic-label plugin to add a new req header
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["uri", "==", "/echo"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100,
+                                                    "resp-X-content-type": 
"json"
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 7: trigger traffic-label
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
+X-content-type: json
+
+
+
+=== TEST 8: AND condition in match
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["uri", "==", "/echo"],
+                                            ["arg_foo", "==", "bar"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 9: mismatch the condition
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 200
+
+
+
+=== TEST 10: match the condition
+--- request
+GET /echo?foo=bar
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
+
+
+
+=== TEST 11: OR condition in match
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            "OR",
+                                            ["arg_foo", "==", "bar"],
+                                            ["uri", "==", "/echo"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 12: match the condition
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
+
+
+
+=== TEST 13: wrong weight in rules
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["uri", "==", "/echo"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                },
+                                                "weight": 0.2
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- error_code: 400
+--- response_body eval
+qr/property \\"weight\\" validation failed/
+
+
+
+=== TEST 14: ipmatch operator, mismatch
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["remote_addr", "ipmatch", 
"127.0.0.2"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                },
+                                                "weight": 1
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 15: trigger traffic-label
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 200
+
+
+
+=== TEST 16: ipmatch operator, match
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["remote_addr", "ipmatch", 
"127.0.0.1"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                },
+                                                "weight": 1
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 17: trigger traffic-label
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
+
+
+
+=== TEST 18: nested expr
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            "AND",
+                                            ["remote_addr", "ipmatch", 
"127.0.0.1"],
+                                            [
+                                                "AND",
+                                                ["uri", "==", "/echo"],
+                                                ["arg_foo", "==", "bar"]
+                                            ]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                },
+                                                "weight": 1
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 19: trigger traffic-label, mismatch
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 200
+
+
+
+=== TEST 20: trigger traffic-label, match
+--- request
+GET /echo?foo=bar
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
diff --git a/t/plugin/traffic-label2.t b/t/plugin/traffic-label2.t
new file mode 100644
index 000000000..70271f20d
--- /dev/null
+++ b/t/plugin/traffic-label2.t
@@ -0,0 +1,553 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+no_shuffle();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $extra_yaml_config = <<_EOC_;
+plugins:
+    - traffic-label
+    - proxy-rewrite
+_EOC_
+
+    if (!$block->extra_yaml_config) {
+        $block->set_value("extra_yaml_config", $extra_yaml_config);
+    }
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+});
+
+run_tests();
+
+
+__DATA__
+
+=== TEST 1: use traffic-label plugin with proxy-rewrite plugin
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "proxy-rewrite": {
+                                "uri": "/echo"
+                            },
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["arg_foo", "==", "bar"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/*"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 2: trigger workflow
+--- request
+GET /echo_not_exist
+--- more_headers
+X-server-id: 100
+--- response_headers
+X-Server-id: 100
+
+
+
+=== TEST 3: trigger workflow
+--- request
+GET /echo_not_exist?foo=bar
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
+
+
+
+=== TEST 4: trigger workflow
+--- request
+GET /echo?foo=bar
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
+
+
+
+=== TEST 5: trigger workflow
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 200
+
+
+
+=== TEST 6: If there is no match condition in rule, all requests are matched 
by default
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 7: match the condition
+--- request
+GET /echo
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 100
+
+
+
+=== TEST 8: multiple headers
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["uri", "==", "/echo"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100,
+                                                    "X-request-id": "id2"
+                                                },
+                                                "weight": 1
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 9: trigger traffic-label
+--- pipelined_requests eval
+[
+    "GET /echo",
+    "GET /echo",
+]
+--- more_headers
+X-server-id: 200
+X-request-id: id1
+--- response_headers eval
+[
+    "X-Server-id: 100",
+    "X-request-id: id2"
+]
+
+
+
+=== TEST 10: multiple actions
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["uri", "==", "/echo"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 100
+                                                },
+                                                "weight": 1
+                                            },
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 200
+                                                },
+                                                "weight": 1
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 11: trigger traffic-label
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local uri = "http://127.0.0.1:"; .. ngx.var.server_port
+                        .. "/echo"
+
+            local resp_arr = {}
+            for i = 1, 2 do
+                local httpc = http.new()
+                local res, err = httpc:request_uri(uri, {method = "GET"})
+                if not res then
+                    ngx.say(err)
+                    return
+                end
+                table.insert(resp_arr, res.headers["X-Server-id"])
+            end
+
+            table.sort(resp_arr, cmd)
+
+            ngx.say(require("toolkit.json").encode(resp_arr))
+            ngx.exit(200)
+        }
+    }
+--- request
+GET /t
+--- response_body
+["100","200"]
+
+
+
+=== TEST 12: no action
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["uri", "==", "/echo"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "weight": 1
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 13: match the condition
+--- request
+GET /echo?foo=bar
+--- more_headers
+X-server-id: 200
+--- response_headers
+X-Server-id: 200
+
+
+
+=== TEST 14: multiple rules
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": [
+                                    {
+                                        "match": [
+                                            ["arg_foo", "==", "water"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 200
+                                                }
+                                            }
+                                        ]
+                                    },
+                                    {
+                                        "match": [
+                                            ["arg_foo", "==", "bar"]
+                                        ],
+                                        "actions": [
+                                            {
+                                                "set_headers": {
+                                                    "X-server-id": 300
+                                                }
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 15: match one condition
+--- request
+GET /echo?foo=water
+--- more_headers
+X-server-id: 100
+--- response_headers
+X-Server-id: 200
+
+
+
+=== TEST 16: match one condition
+--- request
+GET /echo?foo=bar
+--- more_headers
+X-server-id: 100
+--- response_headers
+X-Server-id: 300
+
+
+
+=== TEST 17: set plugin without configuration
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- error_code: 400
+--- response_body eval
+qr/property \\"rules\\" is required/
+
+
+
+=== TEST 18: set plugin with empty rules
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "traffic-label": {
+                                "rules": []
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/echo"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- error_code: 400
+--- response_body eval
+qr/expect array to have at least 1 items/

Reply via email to