Routing Rules

Configuring routing rules
You're viewing a development version of router, the latest released version is 1.24.0

The current page Routing Rules doesn't exist in version 1.24.0 of the documentation for this product.
We can take you to the closest parent section instead: /docs/acd/components/router/1.24.0/configuration/

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 provided onMatch target.
  • 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 the spreadFactor.
  • 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 the onMiss target.
  • 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 in services.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 rules array 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 name and a path — 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 name and type fields 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 a value (the string value to substitute).

How Expansion Works

When a template instance is expanded:

  1. The entire rule list of the template is copied.
  2. Parameter values are substituted at the paths declared in the template definition.
  3. The entrypoint rule is renamed to the instance name. All other rules in the template are prefixed with the instance name (e.g. inner_split becomes instance_name.inner_split).
  4. 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 groups
  • in_any_session_group(str sg_name, ...): True if session belongs to any specified session group
  • in_subnet(str subnet_name): True if client IP belongs to the named subnet
  • gt(str si_var, number value): True if selection_inputs[si_var] > value
  • gt(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] >= value
  • ge(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] < value
  • lt(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] <= value
  • le(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] == value
  • eq(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] != value
  • neq(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 of always().

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
  • not binds tightly to the predicate immediately following it
  • and has higher precedence than or
  • 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.