My ideas for blog posts inevitably start to dry up after over two years at Apache APISIX. Hence, I did some triage on the APISIX repo. I stumbled upon this one question: We have a requirement to use a plugin, where we need to route the traffic on percentage basis. I'll give an example for better understanding. We have an URL https://xyz.com/ca/fr/index.html where ca is country (canada) and fr is french language. Now the traffic needs to routed 10% to https://xyz.com/ca/en/index.html and the remaining 90% to https://xyz.com/ca/fr/index.html. And whenever we're routing the traffic to https://xyz.com/ca/en/index.html we need to set a cookie. So for next call, if the cookie is there, it should directly go to https://xyz.com/ca/en/index.html else it should go via a 10:90 traffic split. What is the best possible way to achieve this ??-- help request: Setting cookie based on a condition The use case is interesting, and I decided to tackle it. I'll rephrase the requirements first: If no cookie is set, randomly forward the request to one of the upstreams If a cookie has been set, forward the request to the correct upstream. For easier testing: I change the odds from 10:90 to 50:50 I use the root instead of a host plus a path Finally, I assume that the upstream sets the cookie. Newcomers to Apache APISIX understand the matching algorithm very quickly: if a request matches a route's host, method, and path, forward it to the upstream set. routes: - id: 1 uri: /hello host: foo.com methods: - GET - PUT - POST upstream_id: 1 curl --resolve foo.com:127.0.0.1 http://foo.com/hello #1 curl -X POST --resolve foo.com:127.0.0.1 http://foo.com/hello #2 curl -X PUT --resolve foo.com:127.0.0.1 http://foo.com/hello #2 curl --resolve bar.com:127.0.0.1 http://bar.com/hello #3 curl --resolve foo.com:127.0.0.1 http://foo.com/hello/john #4 Matches host, method as curl defaults to GET, and path Matches host, method, and path Doesn't match host Doesn't match path, as the configured path doesn't hold a * character path is the only required parameter; neither host nor methods are. host defaults to any host and methods to any method. Beyond these three main widespread matching parameters, others are available, e.g., remote_addrs or vars. Let's focus on the latter. The documentation on the Route API is pretty concise: Matches based on the specified variables consistent with variables in Nginx. Takes the form [[var, operator, val], [var, operator, val], ...]]. Note that this is case sensitive when matching a cookie name. See lua-resty-expr for more details. -- Route API One can only understand vars in the Router Radix Tree documentation. The Router Radix Tree powers the Apache APISIX's matching engine. Nginx provides a variety of built-in variables that can be used to filter routes based on certain criteria. Here is an example of how to filter routes by Nginx built-in variables: -- How to filter route by Nginx built-in variable?$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { "uri": "/index.html", "vars": [ ["http_host", "==", "iresty.com"], ["cookie_device_id", "==", "a66f0cdc4ba2df8c096f74c9110163a9"], ["arg_name", "==", "json"], ["arg_age", ">", "18"], ["arg_address", "~~", "China.*"] ], "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' This route will require the request header host equal iresty.com, request cookie key _device_id equal a66f0cdc4ba2df8c096f74c9110163a9, etc. You can learn more at radixtree-new. Among all Nginx variables, we can find $cookie_xxx. Hence, we can come up with the following configuration: routes: - name: Check for French cookie uri: / vars: [[ "cookie_site", "==", "fr" ]] #1 upstream_id: 1 - name: Check for English cookie uri: / vars: [[ "cookie_site", "==", "en" ]] #2 upstream_id: 2 Match if a cookie named site has value fr Match if a cookie named site has value en We need to configure the final route, the one used when no cookie is set. We use the traffic-split plugin to assign a route randomly. The traffic-split Plugin can be used to dynamically direct portions of traffic to various Upstream services. This is done by configuring match, which are custom rules for splitting traffic, and weighted_upstreams which is a set of Upstreams to direct traffic to.When a request is matched based on the match attribute configuration, it will be directed to the Upstreams based on their configured weights. You can also omit using the match attribute and direct all traffic based on weighted_upstreams. -- traffic-split The third route is the following: - name: Let the fate decide uri: / upstream_id: 1 #1 plugins: traffic-split: rules: - weighted_upstreams: - weight: 50 #1 - upstream_id: 2 #2 weight: 50 #2 The weight of the upstream 1 is 50 The upstream 2 weight is also 50 out of the total weight sum. It's a half-half chance of APISIX forwarding it to either upstream At this point, we need to solve one remaining issue: the order in which APISIX will evaluate the routes. When routes' paths are disjoint, the order plays no role; when they are overlapping, it does. For example, if APISIX evaluates the last route first, it will forward the request to a random upstream, even though a cookie might have been set. We need to force the evaluation of the first two routes first. For that, APISIX offers the priority parameter; its value is 0 by default. It evaluates routes matching by order of decreasing priority. We need to override it to evaluate the random route last. - name: Let the fate decide uri: / upstream_id: 1 priority: -1 #... You can try the setup in a browser or with curl. With curl, we can set the "first" request like this: curl -v localhost:9080 If the upstream sets the cookie correctly, you should see the following line among the different response headers: Set-Cookie: site=fr Since curl doesn't store cookies by default, the value should change across several calls. If we set the cookie, the value stays constant: curl -v --cookie 'site=en' localhost:9080 #1 The cookie name is case-sensitive; beware The browser keeps the cookie, so it's even simpler. Just go to http:localhost:9080 and refresh several times: the content is the same as well. The content will change if you change the cookie to another possible value and request again. The complete source code for this post can be found on GitHub. To go further: Setting cookie based on a condition router-radixtree Route Admin API My ideas for blog posts inevitably start to dry up after over two years at Apache APISIX . Hence, I did some triage on the APISIX repo . I stumbled upon this one question: Apache APISIX APISIX repo We have a requirement to use a plugin, where we need to route the traffic on percentage basis. I'll give an example for better understanding. We have an URL https://xyz.com/ca/fr/index.html where ca is country (canada) and fr is french language. Now the traffic needs to routed 10% to https://xyz.com/ca/en/index.html and the remaining 90% to https://xyz.com/ca/fr/index.html. And whenever we're routing the traffic to https://xyz.com/ca/en/index.html we need to set a cookie. So for next call, if the cookie is there, it should directly go to https://xyz.com/ca/en/index.html else it should go via a 10:90 traffic split. What is the best possible way to achieve this ?? -- help request: Setting cookie based on a condition We have a requirement to use a plugin, where we need to route the traffic on percentage basis. I'll give an example for better understanding. We have an URL https://xyz.com/ca/fr/index.html where ca is country (canada) and fr is french language. Now the traffic needs to routed 10% to https://xyz.com/ca/en/index.html and the remaining 90% to https://xyz.com/ca/fr/index.html . And whenever we're routing the traffic to https://xyz.com/ca/en/index.html we need to set a cookie. So for next call, if the cookie is there, it should directly go to https://xyz.com/ca/en/index.html else it should go via a 10:90 traffic split. What is the best possible way to achieve this ?? https://xyz.com/ca/fr/index.html https://xyz.com/ca/en/index.html https://xyz.com/ca/fr/index.html https://xyz.com/ca/en/index.html https://xyz.com/ca/en/index.html -- help request: Setting cookie based on a condition help request: Setting cookie based on a condition The use case is interesting, and I decided to tackle it. I'll rephrase the requirements first: If no cookie is set, randomly forward the request to one of the upstreams If a cookie has been set, forward the request to the correct upstream. If no cookie is set, randomly forward the request to one of the upstreams If a cookie has been set, forward the request to the correct upstream. For easier testing: I change the odds from 10:90 to 50:50 I use the root instead of a host plus a path I change the odds from 10:90 to 50:50 I use the root instead of a host plus a path Finally, I assume that the upstream sets the cookie. Newcomers to Apache APISIX understand the matching algorithm very quickly: if a request matches a route's host, method, and path, forward it to the upstream set. routes: - id: 1 uri: /hello host: foo.com methods: - GET - PUT - POST upstream_id: 1 routes: - id: 1 uri: /hello host: foo.com methods: - GET - PUT - POST upstream_id: 1 curl --resolve foo.com:127.0.0.1 http://foo.com/hello #1 curl -X POST --resolve foo.com:127.0.0.1 http://foo.com/hello #2 curl -X PUT --resolve foo.com:127.0.0.1 http://foo.com/hello #2 curl --resolve bar.com:127.0.0.1 http://bar.com/hello #3 curl --resolve foo.com:127.0.0.1 http://foo.com/hello/john #4 curl --resolve foo.com:127.0.0.1 http://foo.com/hello #1 curl -X POST --resolve foo.com:127.0.0.1 http://foo.com/hello #2 curl -X PUT --resolve foo.com:127.0.0.1 http://foo.com/hello #2 curl --resolve bar.com:127.0.0.1 http://bar.com/hello #3 curl --resolve foo.com:127.0.0.1 http://foo.com/hello/john #4 Matches host, method as curl defaults to GET, and path Matches host, method, and path Doesn't match host Doesn't match path, as the configured path doesn't hold a * character Matches host, method as curl defaults to GET , and path curl GET Matches host, method, and path Doesn't match host Doesn't match path, as the configured path doesn't hold a * character * path is the only required parameter; neither host nor methods are. host defaults to any host and methods to any method. path host methods host methods Beyond these three main widespread matching parameters, others are available, e.g. , remote_addrs or vars . Let's focus on the latter. The documentation on the Route API is pretty concise: e.g. remote_addrs vars Matches based on the specified variables consistent with variables in Nginx. Takes the form [[var, operator, val], [var, operator, val], ...]]. Note that this is case sensitive when matching a cookie name. See lua-resty-expr for more details. -- Route API Matches based on the specified variables consistent with variables in Nginx. Takes the form [[var, operator, val], [var, operator, val], ...]] . Note that this is case sensitive when matching a cookie name. See lua-resty-expr for more details. [[var, operator, val], [var, operator, val], ...]] lua-resty-expr -- Route API Route API One can only understand vars in the Router Radix Tree documentation. The Router Radix Tree powers the Apache APISIX's matching engine. vars Nginx provides a variety of built-in variables that can be used to filter routes based on certain criteria. Here is an example of how to filter routes by Nginx built-in variables: -- How to filter route by Nginx built-in variable? $ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { "uri": "/index.html", "vars": [ ["http_host", "==", "iresty.com"], ["cookie_device_id", "==", "a66f0cdc4ba2df8c096f74c9110163a9"], ["arg_name", "==", "json"], ["arg_age", ">", "18"], ["arg_address", "~~", "China.*"] ], "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' This route will require the request header host equal iresty.com, request cookie key _device_id equal a66f0cdc4ba2df8c096f74c9110163a9, etc. You can learn more at radixtree-new. Nginx provides a variety of built-in variables that can be used to filter routes based on certain criteria. Here is an example of how to filter routes by Nginx built-in variables: -- How to filter route by Nginx built-in variable? How to filter route by Nginx built-in variable? $ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { "uri": "/index.html", "vars": [ ["http_host", "==", "iresty.com"], ["cookie_device_id", "==", "a66f0cdc4ba2df8c096f74c9110163a9"], ["arg_name", "==", "json"], ["arg_age", ">", "18"], ["arg_address", "~~", "China.*"] ], "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' $ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { "uri": "/index.html", "vars": [ ["http_host", "==", "iresty.com"], ["cookie_device_id", "==", "a66f0cdc4ba2df8c096f74c9110163a9"], ["arg_name", "==", "json"], ["arg_age", ">", "18"], ["arg_address", "~~", "China.*"] ], "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' This route will require the request header host equal iresty.com , request cookie key _device_id equal a66f0cdc4ba2df8c096f74c9110163a9 , etc. You can learn more at radixtree-new . host iresty.com _device_id a66f0cdc4ba2df8c096f74c9110163a9 radixtree-new Among all Nginx variables, we can find $cookie_xxx . Hence, we can come up with the following configuration: $cookie_xxx routes: - name: Check for French cookie uri: / vars: [[ "cookie_site", "==", "fr" ]] #1 upstream_id: 1 - name: Check for English cookie uri: / vars: [[ "cookie_site", "==", "en" ]] #2 upstream_id: 2 routes: - name: Check for French cookie uri: / vars: [[ "cookie_site", "==", "fr" ]] #1 upstream_id: 1 - name: Check for English cookie uri: / vars: [[ "cookie_site", "==", "en" ]] #2 upstream_id: 2 Match if a cookie named site has value fr Match if a cookie named site has value en Match if a cookie named site has value fr site fr Match if a cookie named site has value en site en We need to configure the final route, the one used when no cookie is set. We use the traffic-split plugin to assign a route randomly. traffic-split The traffic-split Plugin can be used to dynamically direct portions of traffic to various Upstream services. This is done by configuring match, which are custom rules for splitting traffic, and weighted_upstreams which is a set of Upstreams to direct traffic to. When a request is matched based on the match attribute configuration, it will be directed to the Upstreams based on their configured weights. You can also omit using the match attribute and direct all traffic based on weighted_upstreams. -- traffic-split The traffic-split Plugin can be used to dynamically direct portions of traffic to various Upstream services. traffic-split This is done by configuring match , which are custom rules for splitting traffic, and weighted_upstreams which is a set of Upstreams to direct traffic to. match weighted_upstreams When a request is matched based on the match attribute configuration, it will be directed to the Upstreams based on their configured weights . You can also omit using the match attribute and direct all traffic based on weighted_upstreams . match weights match weighted_upstreams -- traffic-split traffic-split The third route is the following: - name: Let the fate decide uri: / upstream_id: 1 #1 plugins: traffic-split: rules: - weighted_upstreams: - weight: 50 #1 - upstream_id: 2 #2 weight: 50 #2 - name: Let the fate decide uri: / upstream_id: 1 #1 plugins: traffic-split: rules: - weighted_upstreams: - weight: 50 #1 - upstream_id: 2 #2 weight: 50 #2 The weight of the upstream 1 is 50 The upstream 2 weight is also 50 out of the total weight sum. It's a half-half chance of APISIX forwarding it to either upstream The weight of the upstream 1 is 50 1 50 The upstream 2 weight is also 50 out of the total weight sum. It's a half-half chance of APISIX forwarding it to either upstream 2 50 At this point, we need to solve one remaining issue: the order in which APISIX will evaluate the routes. When routes' paths are disjoint, the order plays no role; when they are overlapping, it does. For example, if APISIX evaluates the last route first, it will forward the request to a random upstream, even though a cookie might have been set. We need to force the evaluation of the first two routes first. For that, APISIX offers the priority parameter; its value is 0 by default. It evaluates routes matching by order of decreasing priority. We need to override it to evaluate the random route last. priority 0 - name: Let the fate decide uri: / upstream_id: 1 priority: -1 #... - name: Let the fate decide uri: / upstream_id: 1 priority: -1 #... You can try the setup in a browser or with curl . With curl, we can set the "first" request like this: curl curl -v localhost:9080 curl -v localhost:9080 If the upstream sets the cookie correctly, you should see the following line among the different response headers: Set-Cookie: site=fr Set-Cookie: site=fr Since curl doesn't store cookies by default, the value should change across several calls. If we set the cookie, the value stays constant: curl -v --cookie 'site=en' localhost:9080 #1 curl -v --cookie 'site=en' localhost:9080 #1 The cookie name is case-sensitive; beware The cookie name is case-sensitive; beware The browser keeps the cookie, so it's even simpler. Just go to http:localhost:9080 and refresh several times: the content is the same as well. The content will change if you change the cookie to another possible value and request again. http:localhost:9080 The complete source code for this post can be found on GitHub . GitHub To go further: To go further: Setting cookie based on a condition router-radixtree Route Admin API Setting cookie based on a condition Setting cookie based on a condition router-radixtree router-radixtree Route Admin API Route Admin API