Author: Nicolas Frankel
Original post on Foojay: Read More
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 where ca is country (Canada) and fr is french language. Now the traffic needs to routed 10% to and the remaining 90% to. And whenever we’re routing the traffic to we need to set a cookie. So for next call, if the cookie is there, it should directly go to 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.
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.
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 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
Originally published at A Java Geek on June 9th, 2024
The post Random and fixed routes with Apache APISIX appeared first on foojay.