Routing Rules
Page not available in that version
The current page Routing Rules doesn't exist in version 1.24.0 of the documentation for this product.
The routing configuration using confcli is done using a combination of
logical building blocks, or rules. Each block evaluates the incoming request in
some way and sends it on to one or more sub-blocks. If the block is the name of
a host (see CDNs and hosts), the client is
sent to that host and the evaluation is done.
Existing Blocks
Currently supported blocks are:
allow: Incoming requests, for which a given rule function matches, are immediately sent to the providedonMatchtarget.consistentHashing: Splits incoming requests randomly between preferred hosts, determined by the proprietary consistent hashing algorithm. The amount of hosts to split between is controlled by thespreadFactor.contentPopularity: Splits incoming requests into two sub-blocks depending on how popular the requested content is.deny: Incoming requests, for which a given rule function matches, are immediately denied, and all non-matching requests are sent to theonMisstarget.firstMatch: Incoming requests are matched by an ordered series of rules, where the request will be handled by the first rule for which the condition evaluates to true.random: Splits incoming requests randomly and equally between a list of target sub-blocks. Useful for simple load balancing.split: Splits incoming requests between two sub-blocks depending on how the request is evaluated by a provided function. Can be used for sending clients to different hosts depending on e.g. geographical location or client hardware type.template: Instantiates a reusable, parameterized routing rule subtree defined inservices.routing.templates. See Templates for details.weighted: Randomly splits incoming requests between a list of target sub-blocks, weighted according to each target’s associated weight rule. A higher weight means a higher portion of requests will be routed to a sub-block. Rules can be used to decide whether or not to pick a target.rawGroup: Contains a raw CDN Director configuration routing tree node, to be inserted as is in the generated configuration. This is only meant to be used in the rare cases when it’s impossible to construct the required routing behavior in any other way.rawHost: A host reference for use as endpoints in rawGroup trees.
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: allow
Adding a 'allow' element
rule : {
name (default: ): allow
type (default: allow): ⏎
condition (default: ): customFunction()
onMatch (default: ): rr1
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "allow",
"type": "allow",
"condition": "customFunction()",
"onMatch": "rr1"
}
]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: consistentHashing
Adding a 'consistentHashing' element
rule : {
name (default: ): consistentHashingRule
type (default: consistentHashing):
spreadFactor (default: 1): 2
hashAlgorithm (default: MD5):
targets : [
target : {
target (default: ): rr1
enabled (default: True):
}
Add another 'target' element to array 'targets'? [y/N]: y
target : {
target (default: ): rr2
enabled (default: True):
}
Add another 'target' element to array 'targets'? [y/N]: y
target : {
target (default: ): rr3
enabled (default: True):
}
Add another 'target' element to array 'targets'? [y/N]: n
]
}
Add another 'rule' element to array 'rules'? [y/N]: n
]
Generated config:
{
"rules": [
{
"name": "consistentHashingRule",
"type": "consistentHashing",
"spreadFactor": 2,
"hashAlgorithm": "MD5",
"targets": [
{
"target": "rr1",
"enabled": true
},
{
"target": "rr2",
"enabled": true
},
{
"target": "rr3",
"enabled": true
}
]
}
]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: contentPopularity
Adding a 'contentPopularity' element
rule : {
name (default: ): content
type (default: contentPopularity): ⏎
contentPopularityCutoff (default: 10): 20
onPopular (default: ): rr1
onUnpopular (default: ): rr2
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "content",
"type": "contentPopularity",
"contentPopularityCutoff": 20.0,
"onPopular": "rr1",
"onUnpopular": "rr2"
}
]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: deny
Adding a 'deny' element
rule : {
name (default: ): deny
type (default: deny): ⏎
condition (default: ): customFunction()
onMiss (default: ): rr1
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "deny",
"type": "deny",
"condition": "customFunction()",
"onMiss": "rr1"
}
]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: firstMatch
Adding a 'firstMatch' element
rule : {
name (default: ): firstMatch
type (default: firstMatch): ⏎
targets : [
target : {
onMatch (default: ): rr1
condition (default: ): customFunction()
}
Add another 'target' element to array 'targets'? [y/N]: y
target : {
onMatch (default: ): rr2
condition (default: ): otherCustomFunction()
}
Add another 'target' element to array 'targets'? [y/N]: n
]
}
Add another 'rule' element to array 'rules'? [y/N]: n
]
Generated config:
{
"rules": [
{
"name": "firstMatch",
"type": "firstMatch",
"targets": [
{
"onMatch": "rr1",
"condition": "customFunction()"
},
{
"onMatch": "rr2",
"condition": "otherCustomFunction()"
}
]
}
]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: random
Adding a 'random' element
rule : {
name (default: ): random
type (default: random): ⏎
targets : [
target (default: ): rr1
Add another 'target' element to array 'targets'? [y/N]: y
target (default: ): rr2
Add another 'target' element to array 'targets'? [y/N]: ⏎
]
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "random",
"type": "random",
"targets": [
"rr1",
"rr2"
]
}
]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: split
Adding a 'split' element
rule : {
name (default: ): split
type (default: split): ⏎
condition (default: ): custom_function()
onMatch (default: ): rr2
onMiss (default: ): rr1
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "split",
"type": "split",
"condition": "custom_function()",
"onMatch": "rr2",
"onMiss": "rr1"
}
]
}
Merge and apply the config? [y/n]: y
>> First define a template in services.routing.templates
$ confcli services.routing.templates -w
Running wizard for resource 'templates'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
templates : [
template : {
name (default: ): geo_fence
rules : [
Choose element index or name: split
Adding a 'split' element
rule : {
name (default: ): geo_split
type (default: split): ⏎
condition (default: ): always()
onMatch (default: ): placeholder
onMiss (default: ): placeholder
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
entrypoint (default: ): geo_split
parameters : [
parameter : {
name (default: ): match_host
path (default: ): geo_split.onMatch
}
Add another 'parameter' element to array 'parameters'? [y/N]: y
parameter : {
name (default: ): miss_host
path (default: ): geo_split.onMiss
}
Add another 'parameter' element to array 'parameters'? [y/N]: ⏎
]
}
Add another 'template' element to array 'templates'? [y/N]: ⏎
]
Generated config:
{
"templates": [
{
"name": "geo_fence",
"rules": [
{
"name": "geo_split",
"type": "split",
"condition": "always()",
"onMatch": "placeholder",
"onMiss": "placeholder"
}
],
"entrypoint": "geo_split",
"parameters": [
{
"name": "match_host",
"path": "geo_split.onMatch"
},
{
"name": "miss_host",
"path": "geo_split.onMiss"
}
]
}
]
}
Merge and apply the config? [y/n]: y
>> Then instantiate it in the rules array
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: template
Adding a 'template' element
rule : {
name (default: ): europe_fence
type (default: template): ⏎
template (default: ): geo_fence
parameters : [
parameter : {
name (default: ): match_host
value (default: ): eu-streamer
}
Add another 'parameter' element to array 'parameters'? [y/N]: y
parameter : {
name (default: ): miss_host
value (default: ): eu-fallback
}
Add another 'parameter' element to array 'parameters'? [y/N]: ⏎
]
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "europe_fence",
"type": "template",
"template": "geo_fence",
"parameters": [
{
"name": "match_host",
"value": "eu-streamer"
},
{
"name": "miss_host",
"value": "eu-fallback"
}
]
}
]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: weighted
Adding a 'weighted' element
rule : {
name (default: ): weight
type (default: weighted): ⏎
targets : [
target : {
target (default: ): rr1
weight (default: 100): ⏎
condition (default: always()): always()
}
Add another 'target' element to array 'targets'? [y/N]: y
target : {
target (default: ): rr2
weight (default: 100): si('rr2-input-weight')
condition (default: always()): gt('rr2-bandwidth', 1000000)
}
Add another 'target' element to array 'targets'? [y/N]: y
target : {
target (default: ): rr2
weight (default: 100): custom_func()
condition (default: always()): always()
}
Add another 'target' element to array 'targets'? [y/N]: ⏎
]
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "weight",
"type": "weighted",
"targets": [
{
"target": "rr1",
"weight": "100",
"condition": "always()"
},
{
"target": "rr2",
"weight": "si('rr2-input-weight')",
"condition": "gt('rr2-bandwidth', 1000000)"
},
{
"target": "rr2",
"weight": "custom_func()",
"condition": "always()"
}
]
}
]
}
Merge and apply the config? [y/n]: y
>> First add a raw host block that refers to a regular host
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: rawHost
Adding a 'rawHost' element
rule : {
name (default: ): raw-host
type (default: rawHost): ⏎
hostId (default: ): rr1
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "raw-host",
"type": "rawHost",
"hostId": "rr1"
}
]
}
Merge and apply the config? [y/n]: y
>> And then add a rule using the host node
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: rawGroup
Adding a 'rawGroup' element
rule : {
name (default: ): raw-node
type (default: rawGroup): ⏎
memberOrder (default: sequential): ⏎
members : [
member : {
target (default: ): raw-host
weightFunction (default: ): return 1
}
Add another 'member' element to array 'members'? [y/N]: ⏎
]
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "raw-node",
"type": "rawGroup",
"memberOrder": "sequential",
"members": [
{
"target": "raw-host",
"weightFunction": "return 1"
}
]
}
]
}
Merge and apply the config? [y/n]: y
Templates
Templates let you define reusable, parameterized routing rule subtrees. Use them to extract parts of the routing tree that either repeat with small variations (e.g. the same allow/deny/split structure used for multiple regions with different hosts) or form a logical unit that is easier to manage separately.
A template is defined once in services.routing.templates and instantiated
one or more times in the services.routing.rules array using the template
rule type. The template system is a pre-processing step: template instances are
expanded into regular rules before the configuration reaches the CDN Director, so
the director itself is unaware of templates.
All rule types except rawGroup and rawHost can be used inside templates.
Defining a Template
A template definition contains:
- name: A unique name for the template.
- rules: The routing rule blocks that make up the template subtree. Rules
reference each other by name via target fields (
onMatch,onMiss,targets, etc.) just like in the main rules array. - entrypoint: The name of the rule in the template’s
rulesarray that serves as the entry point to the subtree. - parameters: Declares which fields in the rules can be customized per
instance. Each parameter has a
nameand apath— a dot-separated path to a leaf field in a rule (e.g.my_split.onMatch,my_weighted.targets.0.target).
Parameter paths follow the format <rule_name>.<field_path>, where the first
segment is the name of a rule in the template’s rules array, followed by the
path to a leaf field within that rule. For fields inside arrays, use the
zero-based index as a path segment.
Restrictions on parameters:
- The
nameandtypefields of a rule cannot be parameterized. - Only leaf fields (strings, numbers, booleans) can be targeted — not objects or arrays.
Instantiating a Template
A template instance in the rules array has:
- name: The name of this instance. After expansion, the template’s entrypoint rule is renamed to this name, making it targetable by other rules.
- type: Must be
"template". - template: The name of a template definition in
services.routing.templates. - parameters: Values for the parameters declared in the referenced template.
Each entry has a
name(matching a parameter in the template definition) and avalue(the string value to substitute).
How Expansion Works
When a template instance is expanded:
- The entire rule list of the template is copied.
- Parameter values are substituted at the paths declared in the template definition.
- The entrypoint rule is renamed to the instance name. All other rules
in the template are prefixed with the instance name
(e.g.
inner_splitbecomesinstance_name.inner_split). - Internal target references between rules within the template are automatically rewritten to use the new names.
This means the expanded rules seamlessly integrate into the main rules array — other rules can target the instance by its name as if it were any regular rule.
Type coercion: Parameter values are always provided as strings. For target
fields that expect a non-string type (boolean, integer, or number), the value
is automatically converted. For example, "true" and "false" are converted
to booleans, and numeric strings like "42" or "3.14" are converted to their
respective types. A validation error is raised if the conversion fails.
Example
The following example defines a template that performs geo-fencing using a
split rule, and instantiates it twice — once for Europe and once for Asia —
each routing to different hosts. The entries that will be substituted when the
template is expanded have been given the temporary values placeholder in this
example, but can have any value that is otherwise valid for the field in
question.
{
"templates": [
{
"name": "geo_fence",
"entrypoint": "geo_split",
"parameters": [
{"name": "match_host", "path": "geo_split.onMatch"},
{"name": "miss_host", "path": "geo_split.onMiss"},
{"name": "geo_condition", "path": "geo_split.condition"}
],
"rules": [
{
"name": "geo_split",
"type": "split",
"condition": "placeholder()",
"onMatch": "placeholder",
"onMiss": "placeholder"
}
]
}
],
"rules": [
{
"name": "entry",
"type": "firstMatch",
"targets": [
{"condition": "in_subnet('Europe')", "onMatch": "europe_fence"},
{"condition": "in_subnet('Asia')", "onMatch": "asia_fence"}
]
},
{
"name": "europe_fence",
"type": "template",
"template": "geo_fence",
"parameters": [
{"name": "match_host", "value": "eu-streamer"},
{"name": "miss_host", "value": "eu-fallback"},
{"name": "geo_condition", "value": "in_session_group('Premium')"}
]
},
{
"name": "asia_fence",
"type": "template",
"template": "geo_fence",
"parameters": [
{"name": "match_host", "value": "asia-streamer"},
{"name": "miss_host", "value": "asia-fallback"},
{"name": "geo_condition", "value": "in_session_group('Premium')"}
]
}
]
}
After expansion, the europe_fence instance becomes a regular split rule named
europe_fence with onMatch set to eu-streamer, onMiss set to
eu-fallback, and condition set to in_session_group('Premium'). The
asia_fence instance is expanded similarly with its own parameter values.
Multi-Rule Templates
Templates can contain multiple rules that reference each other. For example, a template with an allow gate followed by a split:
{
"templates": [
{
"name": "guarded_split",
"entrypoint": "gate",
"parameters": [
{"name": "gate_condition", "path": "gate.condition"},
{"name": "match_target", "path": "inner.onMatch"},
{"name": "miss_target", "path": "inner.onMiss"}
],
"rules": [
{
"name": "gate",
"type": "allow",
"condition": "placeholder()",
"onMatch": "inner"
},
{
"name": "inner",
"type": "split",
"condition": "always()",
"onMatch": "placeholder",
"onMiss": "placeholder"
}
]
}
],
"rules": [
{
"name": "my_guarded",
"type": "template",
"template": "guarded_split",
"parameters": [
{"name": "gate_condition", "value": "in_subnet('Allowed')"},
{"name": "match_target", "value": "primary-host"},
{"name": "miss_target", "value": "fallback-host"}
]
}
],
"entrypoint": "my_guarded"
}
After expansion, this produces two rules: my_guarded (the renamed gate
rule, now the entry point) and my_guarded.inner (the prefixed inner rule).
The gate rule’s onMatch is automatically rewritten from inner to
my_guarded.inner.
Rule Language
Some blocks, such as the split and firstMatch types, have a rule field that
contains a small function in a very simple, custom-made programming language, that
is a limited subset of Lua. This field is used to filter any incoming client
requests in order to determine how to rule block should react.
In the case of a split block, the rule is evaluated and if it is true the
client is sent to the onMatch part of the block, otherwise it is sent to the
onMiss part for further evaluation.
In the case of a firstMatch block, the rule for each target will be evaluated
top to bottom in order until either a rule evaluates to true or the list is
exhausted. If a rule evaluates to true, the client will be sent to the onMatch
part of the block, otherwise the next target in the list will be tried. If all
targets have been exhausted, then the entire rule evaluation will fail, and the
routing tree will be restarted with the firstMatch block effectively removed.
Example of Boolean Functions
Let’s say we have the CDN Director set up with a session group that matches Apple
devices (named “Apple”). To route all Apple devices to a specific streamer one
would simply create a split block with the following rule:
in_session_group('Apple')
In order to make more complex rules it’s possible to combine several checks like
this in the same rule. Let’s extend the hypothetical CDN Director above with a
configured subnet with all IP addresses in Europe (named “Europe”). To make a
rule that accepts any clients using an Apple device and living outside of
Europe, but only as long as the reported load on the streamer (as indicated by
the selection input variable
“europe_load_mbps”) is less than 1000 megabits per second one could make an
offload block with the following rule (without linebreaks):
in_session_group('Apple')
and not in_subnet('Europe')
and lt('europe_load_mbps', 1000)
In this example in_session_group('Apple') will be true if the client belongs
to the session group named ‘Apple’. The function call in_subnet('Europe') is
true if the client’s IP belongs to the subnet named ‘Europe’, but the word not
in front of it reverses the value so the entire section ends up being false if
the client is in Europe. Finally lt('europe_load_mbps', 1000) is true if
there is a selection input variable named “europe_load_mbps” and its value is
less than 1000.
Since the three parts are conjoined with the and keyword they must all
be true for the entire rule to match. If the keyword or had been used
instead it would have been enough for any of the parts to be true for the
rule to match.
Example of Numeric Functions
A hypothetical CDN has two streamers with different capacity; Host_1 has
roughly twice the capacity of Host_2. A simple random load balancing would put
undue stress on the second host since it will receive as much traffic as the
more capable Host_1.
This can be solved by using a weighted random distribution rule block with
suitable rules for the two hosts:
{
"targets": [
{
"target": "Host_1",
"condition": "always()",
"weight": "100"
},
{
"target": "Host_2",
"condition": "always()",
"weight": "50"
}
]
}
resulting in Host_1 receiving twice as many requests as Host_2 as its
weight function is double that of Host_2.
If the CDN is capable of reporting the free capacity of the hosts, for example by writing to a selection input variable for each host, it’s easy to write a more intelligent load balancing rule by making the weights correspond to the amount of capacity left on each host:
{
"targets": [
{
"target": "Host_1",
"condition": "always()",
"weight": "si('free_capacity_host_1')"
},
{
"target": "Host_2",
"condition": "always()",
"weight": "si('free_capacity_host_2')"
}
]
}
It is also possible to write custom Lua functions that return suitable weights, perhaps taking the host as an argument:
{
"targets": [
{
"target": "Host_1",
"condition": "always()",
"weight": "intelligent_weight_function('Host_1')"
},
{
"target": "Host_2",
"condition": "always()",
"weight": "intelligent_weight_function('Host_2')"
}
]
}
These different weight rules can of course be combined in the same rule block, with one target having a hard coded number, another using a dynamically updated selection input variable and yet another having a custom-built function.
Due to limitations in the random number generator used to distribute requests, it’s better to use somewhat large values, around 100–1000 or so, than to use small values near 0.
Built-In Functions
The following built-in functions are available when writing rules:
in_session_group(str name): True if session belongs to session group<name>in_all_session_groups(str sg_name, ...): True if session belongs to all specified session groupsin_any_session_group(str sg_name, ...): True if session belongs to any specified session groupin_subnet(str subnet_name): True if client IP belongs to the named subnetgt(str si_var, number value): True if selection_inputs[si_var] > valuegt(str si_var1, str si_var2): True if selection_inputs[si_var1] > selection_inputs[si_var2]ge(str si_var, number value): True if selection_inputs[si_var] >= valuege(str si_var1, str si_var2): True if selection_inputs[si_var1] >= selection_inputs[si_var2]lt(str si_var, number value): True if selection_inputs[si_var] < valuelt(str si_var1, str si_var2): True if selection_inputs[si_var1] < selection_inputs[si_var2]le(str si_var, number value): True if selection_inputs[si_var] <= valuele(str si_var1, str si_var2): True if selection_inputs[si_var1] <= selection_inputs[si_var2]eq(str si_var, number value): True if selection_inputs[si_var] == valueeq(str si_var1, str si_var2): True if selection_inputs[si_var1] == selection_inputs[si_var2]neq(str si_var, number value): True if selection_inputs[si_var] != valueneq(str si_var1, str si_var2): True if selection_inputs[si_var1] != selection_inputs[si_var2]si(str si_var): Returns the value of selection_inputs[si_var] if it is defined and non-negative, otherwise it returns 0.always(): Returns true, useful when creating weighted rule blocks.never(): Returns false, opposite ofalways().
These functions, as well as custom functions written in Lua and uploaded to the CDN Director, can be combined to make suitably precise rules.
Combining Multiple Boolean Functions
The rule language supports flexible boolean logic with and, or and not
operators. Complex expressions can be built using parentheses for explicit
precedence control.
Operator Precedence:
- Parentheses have the highest precedence and force explicit grouping
notbinds tightly to the predicate immediately following itandhas higher precedence thanor- Operators at the same level are left-associative
Examples:
// Simple conjunction
in_session_group('mobile') and in_subnet('asia')
// Simple disjunction
in_session_group('premium') or in_session_group('trial')
// Mixed operators (and binds tighter than or)
in_subnet('internal') or in_session_group('staff') and gt('clearance', 5)
// Equivalent to:
// in_subnet('internal') or (in_session_group('staff') and gt('clearance', 5))
// Explicit grouping with parentheses
(in_session_group('mobile') or in_session_group('tablet')) and not in_subnet('blocked')
// Negation with grouping
not (in_session_group('free') and gt('requests_today', 100))
Short-circuit Evaluation:
Expressions use short-circuit (lazy) evaluation, evaluating left-to-right and stopping as soon as the result is certain:
- For and: stops at the first false (since the entire expression must be false)
- For or: stops at the first true (since only one true makes the entire expression true)
This means expensive predicates should be placed after cheaper ones when possible to minimize unnecessary evaluation.
Custom Functions
It is possible to write extremely complex Lua functions that take many parameters or calculations into consideration when evaluating an incoming client request. By writing such functions and making sure that they return only non-negative integer values and uploading them to the CDN Director they can be used from the rule language. Simply call them like any of the built-in functions listed above, using strings and numbers as arguments if necessary, and their result will be used to determine the routing path to use.
Formal Syntax
The full syntax of the language can be described in just a few lines of BNF grammar:
<rule> := <weight_rule> | <match_rule> | <value_rule>
<weight_rule> := "if" <compound_predicate> "then" <weight> "else" <weight>
<value_rule> := <weight>
<match_rule> := <compound_predicate>
<compound-predicate> := <or-predicate>
<or-predicate> := <and-predicate> ["or" <and-predicate> ...]
<and-predicate> := <term-predicate> ["and" <term-predicate> ...]
<term-predicate> := "(" <compound-predicate> ")" |
"not" "(" <compound-predicate> ")" |
<logical-predicate>
<logical-predicate> := ["not"] <predicate>
<predicate> := <function-name> "(" ")" |
<function-name> "(" <argument> ["," <argument> ...] ")"
<function-name> := <letter> [<function-name-tail> ...]
<function-name-tail> := empty | <letter> | <digit> | "_"
<argument> := <string> | <number>
<weight> := integer | <predicate>
<number> := float | integer
<string> := "'" [<letter> | <digit> | <symbol> ...] "'"
<letter> := "a" | "b" | ... | "z" | "A" | "B" | ... | "Z"
<digit> := "0" | "1" | ... | "9"
<symbol> := any printable character except "'" unless escaped by a backslash
Building a Routing Configuration
This example sets up an entire routing configuration for a system with a ESB3008 Convoy Request Router, two streamers and the Apple devices outside of Europe example used earlier in this document. Any clients not matching the criteria will be sent to an offload CDN with two streamers in a simple uniformly randomized load balancing setup.
Set up Session Group
First make a classifier and a session group that uses it:
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
classifiers : [
classifier can be one of
1: anonymousIp
2: asnIds
3: contentUrlPath
4: contentUrlQueryParameters
5: geoip
6: hostName
7: ipranges
8: random
9: regexMatcher
10: requestHeader
11: stringMatcher
12: subnet
13: userAgent
Choose element index or name: userAgent
Adding a 'userAgent' element
classifier : {
name (default: ): Apple
type (default: userAgent): ⏎
inverted (default: False): ⏎
patternType (default: stringMatch): ⏎
pattern (default: ): *apple*
}
Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
"classifiers": [
{
"name": "Apple",
"type": "userAgent",
"inverted": false,
"patternType": "stringMatch",
"pattern": "*apple*"
}
]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.sessionGroups -w
Running wizard for resource 'sessionGroups'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
sessionGroups : [
sessionGroup : {
name (default: ): Apple
classifiers : [
classifier (default: ): Apple
Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
}
Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
"sessionGroups": [
{
"name": "Apple",
"classifiers": [
"Apple"
]
}
]
}
Merge and apply the config? [y/n]: y
Set up Hosts
Create two host groups and add a Request Router to the first and two streamers to the second, which will be used for offload:
$ confcli services.routing.hostGroups -w
Running wizard for resource 'hostGroups'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
hostGroups : [
hostGroup can be one of
1: dns
2: host
3: redirecting
Choose element index or name: redirecting
Adding a 'redirecting' element
hostGroup : {
name (default: ): internal
type (default: redirecting): ⏎
httpPort (default: 80): ⏎
httpsPort (default: 443): ⏎
headersToForward <A list of HTTP headers to forward to the CDN. (default: [])>: [
headersToForward (default: ): ⏎
Add another 'headersToForward' element to array 'headersToForward'? [y/N]: ⏎
]
queryPassThrough <Controls which query parameters from the client request are forwarded to the backend and included in the redirection URL. (default: OrderedDict())>: {
policy (default: accept): ⏎
exceptions <Query parameters excluded from the policy. When policy is 'accept', these are blocked. When policy is 'deny', these are forwarded. (default: [])>: [
exceptions (default: ): ⏎
Add another 'exceptions' element to array 'exceptions'? [y/N]: ⏎
]
}
allowAnyRedirectType (default: False): ⏎
hosts : [
host : {
name (default: ): rr1
hostname (default: ): rr1.example.com
ipv6_address (default: ): ⏎
healthChecks : [
healthCheck (default: always()): ⏎
Add another 'healthCheck' element to array 'healthChecks'? [y/N]: n
]
}
Add another 'host' element to array 'hosts'? [y/N]: ⏎
]
}
Add another 'hostGroup' element to array 'hostGroups'? [y/N]: y
hostGroup can be one of
1: dns
2: host
3: redirecting
Choose element index or name: host
Adding a 'host' element
hostGroup : {
name (default: ): external
type (default: host): ⏎
httpPort (default: 80): ⏎
httpsPort (default: 443): ⏎
headersToForward <A list of HTTP headers to forward to the CDN. (default: [])>: [
headersToForward (default: ): ⏎
Add another 'headersToForward' element to array 'headersToForward'? [y/N]: ⏎
]
queryPassThrough <Controls which query parameters from the client request are forwarded to the backend and included in the redirection URL. (default: OrderedDict())>: {
policy (default: accept): ⏎
exceptions <Query parameters excluded from the policy. When policy is 'accept', these are blocked. When policy is 'deny', these are forwarded. (default: [])>: [
exceptions (default: ): ⏎
Add another 'exceptions' element to array 'exceptions'? [y/N]: ⏎
]
}
createStreamerSession (default: False): ⏎
addUrlPrefix (default: False): ⏎
hosts : [
host : {
name (default: ): offload-streamer1
hostname (default: ): streamer1.example.com
ipv6_address (default: ): ⏎
healthChecks : [
healthCheck (default: always()): ⏎
Add another 'healthCheck' element to array 'healthChecks'? [y/N]: n
]
}
Add another 'host' element to array 'hosts'? [y/N]: y
host : {
name (default: ): offload-streamer2
hostname (default: ): streamer2.example.com
ipv6_address (default: ): ⏎
healthChecks : [
healthCheck (default: always()): ⏎
Add another 'healthCheck' element to array 'healthChecks'? [y/N]: n
]
}
Add another 'host' element to array 'hosts'? [y/N]: ⏎
]
}
Add another 'hostGroup' element to array 'hostGroups'? [y/N]: ⏎
]
Generated config:
{
"hostGroups": [
{
"name": "internal",
"type": "redirecting",
"httpPort": 80,
"httpsPort": 443,
"headersToForward": [
""
],
"queryPassThrough": {
"policy": "accept",
"exceptions": [
""
]
},
"allowAnyRedirectType": false,
"hosts": [
{
"name": "rr1",
"hostname": "rr1.example.com",
"ipv6_address": "",
"healthChecks": [
"always()"
]
}
]
},
{
"name": "external",
"type": "host",
"httpPort": 80,
"httpsPort": 443,
"headersToForward": [
""
],
"queryPassThrough": {
"policy": "accept",
"exceptions": [
""
]
},
"createStreamerSession": false,
"addUrlPrefix": false,
"hosts": [
{
"name": "offload-streamer1",
"hostname": "streamer1.example.com",
"ipv6_address": "",
"healthChecks": [
"always()"
]
},
{
"name": "offload-streamer2",
"hostname": "streamer2.example.com",
"ipv6_address": "",
"healthChecks": [
"always()"
]
}
]
}
]
}
Merge and apply the config? [y/n]: y
Create Load Balancing and Offload Block
Add both offload streamers as targets in a random block:
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: random
Adding a 'random' element
rule : {
name (default: ): balancer
type (default: random): ⏎
targets : [
target (default: ): offload-streamer1
Add another 'target' element to array 'targets'? [y/N]: y
target (default: ): offload-streamer2
Add another 'target' element to array 'targets'? [y/N]: ⏎
]
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "balancer",
"type": "random",
"targets": [
"offload-streamer1",
"offload-streamer2"
]
}
]
}
Merge and apply the config? [y/n]: y
Then create a split block with the request router and the load balanced CDN as targets:
$ confcli services.routing.rules -w
Running wizard for resource 'rules'
Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string
rules : [
rule can be one of
1: allow
2: consistentHashing
3: contentPopularity
4: deny
5: firstMatch
6: random
7: rawGroup
8: rawHost
9: split
10: template
11: weighted
Choose element index or name: split
Adding a 'split' element
rule : {
name (default: ): offload
type (default: split): ⏎
condition (default: ): in_session_group('Apple') and not in_subnet('Europe') and lt('europe_load_mbps', 1000)
onMatch (default: ): rr1
onMiss (default: ): balancer
}
Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
"rules": [
{
"name": "offload",
"type": "split",
"condition": "in_session_group('Apple') and not in_subnet('Europe') and lt('europe_load_mbps', 1000)",
"onMatch": "rr1",
"onMiss": "balancer"
}
]
}
Merge and apply the config? [y/n]: y
The last step required is to set the entrypoint of the routing tree so the CDN Director knows where to start evaluating:
$ confcli services.routing.entrypoint offload
services.routing.entrypoint = 'offload'
Evaluate
Now that all the rules have been set up properly and the CDN Director has been reconfigured. The translated configuration can be read from the CDN Director’s configuration API:
$ curl -k https://router-host:5001/v2/configuration 2> /dev/null | jq .routing
{
"id": "offload",
"member_order": "sequential",
"members": [
{
"host_id": "rr1",
"id": "offload.rr1",
"weight_function": "return ((in_session_group('Apple') ~= 0) and
(in_subnet('Europe') == 0) and
(lt('europe_load_mbps', 1000) ~= 0) and 1) or 0 "
},
{
"id": "offload.balancer",
"member_order": "weighted",
"members": [
{
"host_id": "offload-streamer1",
"id": "offload.balancer.offload-streamer1",
"weight_function": "return 100"
},
{
"host_id": "offload-streamer2",
"id": "offload.balancer.offload-streamer2",
"weight_function": "return 100"
}
],
"weight_function": "return 1"
}
],
"weight_function": "return 100"
}
Note that the configuration language code has been translated into its Lua equivalent.