The expressions router provides a Domain Specific Language (DSL) that allows for complex routing rule definition. The expressions router ensures good runtime matching performance by providing specific routing comparisons such as non-regex equality checks that are not available in the traditional router.
You can do the following with the expressions router:
Prefix-based path matching
Regex-based path matching that is less of a performance burden than the traditional router
In your kong.conf file, set router_flavor = expressions and restart your Kong Gateway. Once the router is enabled, you can use the expression parameter when you’re creating a Route to specify the Routes.
At runtime, Kong Gateway builds two separate routers for the HTTP and Stream (TCP, TLS, UDP) subsystem. When a request/connection comes in, Kong Gateway looks at which field your configured Routes require, and supplies the value of these fields to the router execution context.
Routes are inserted into each router with the appropriate priority field set. The priority is a positive integer that defines the order of evaluation of the router. The bigger the priority, the sooner a Route is evaluated. In the case of duplicate priority values between two Routes in the same router, their order of evaluation is undefined. The router is updated incrementally as configured Routes change.
As soon as a Route yields a match, the router stops matching and the matched Route is used to process the current request/connection.
For example, if you have the following three Routes:
The router checks Route A first because it has the highest priority. It doesn’t match the incoming request, so the router checks the Route with the next highest priority.
Route B has the next highest priority, so the router checks this one second. It matches the request, so the router doesn’t check Route C.
Expressions routes are always evaluated in the descending priority order they were defined.
Therefore, it is helpful to put more likely matched routes before (as in, higher priority)
less frequently matched routes.
The following examples show how you would prioritize two routes based on if they were likely to be matched or not.
If multiple routes result in the same Service and Plugin config being used,
they should be combined into a single expression Route with the || logical or operator. By combining routes into a single expression, this results in fewer Route objects created and better performance.
Regular expressions (regexes) are powerful tool that can be used to match strings based on
very complex criteria. Unfortunately, this has also made them more expensive to
evaluate at runtime and hard to optimize. Therefore, there are some common
scenarios where regex usage can be eliminated, resulting in significantly
better matching performance.
When performing exact matches (non-prefix matching) of a request path, use the == operator
instead of regex.
Faster performance example:
http.path == "/foo/bar"
Slower performance example:
http.path ~ r#"^/foo/bar$"#
When performing exact matches with the / optional slash at the end, it is tempting to write
regexes. However, this is completely unnecessary with the expressions language.
Each Route contains one or more predicates combined with logical operators, which Kong Gateway uses to match requests with Routes.
A predicate is the basic unit of expressions code which takes the following form:
http.path ^= "/foo/bar"
This predicate example has the following structure:
http.path: Field
^=: Operator
"/foo/bar": Constant value
Predicates are made up of smaller units that you can configure:
Object
Description
Example
Field
The field contains value extracted from the incoming request. For example, the request path or the value of a header field. The field value could also be absent in some cases. An absent field value will always cause the predicate to yield false no matter the operator. The field always displays to the left of the predicate.
http.path
Constant value
The constant value is what the field is compared to based on the provided operator. The constant value always displays to the right of the predicate.
"/foo/bar"
Operator
An operator defines the desired comparison action to be performed on the field against the provided constant value. The operator always displays in the middle of the predicate, between the field and constant value.
^=
Predicate
A predicate compares a field against a pre-defined value using the provided operator and returns true if the field passed the comparison or false if it didn’t.
Types define what you can use for a predicate’s field and constant value. Expressions language is strongly typed. Operations are only performed
if such an operation makes sense in regard to the actual type of field and constant.
Type conversion at runtime is not supported, either explicitly or implicitly. Types
are always known at the time a route is parsed. An error is returned
if the operator cannot be performed on the provided field and constant.
The expressions language currently supports the following types:
Object
Description
Field type
Constant type
String
A string value, always in valid UTF-8. They can be defined with string literal that looks like "content". You can also use the following escape sequences:
\n: Newline character
\r: Carriage return character
\t: Horizontal tab character
\\: The \ character
\": The " character
IpCidr
Range of IP addresses in CIDR format. Can be either IPv4 (net.src.ip in 192.168.1.0/24) or IPv6 (net.src.ip in fd00::/8). The expressions parser rejects any CIDR literal where the host portion contains any non-zero bits. This means that 192.168.0.1/24 won’t pass the parser check because the intention of the author is unclear.
IpAddr
A single IP address in IPv4 Dot-decimal notation (net.src.ip == 192.168.1.1), or the standard IPv6 Address Format (net.src.ip == fd00::1). Can be either IPv4 or IPv6.
Int
A 64-bit signed integer. There is only one integer type in expressions. All integers are signed 64-bit integers. Integer literals can be written as 12345, -12345, or in hexadecimal format, such as 0xab12ff, or in octet format like 0751.
Regex
Regex are written as String literals, but they are parsed when the ~ regex operator is present and checked for validity according to the Rust regex crate syntax. For example, in the following predicate, the constant is parsed as a regex: http.path ~ r#"/foo/bar/.+"#
In addition, the expressions router also supports one composite type, Array. Array types are written as Type[].
For example: String[], Int[]. Currently, arrays can only be present in field values. They are used in
case one field could contain multiple values. For example, http.headers.x or http.queries.x.
The following table describes the available matching fields, as well as their associated type when using an expressions-based router.
Type
Type
Available in HTTP Subsystem
Available in Stream Subsystem
Description
net.protocol
String
Protocol of the route. Roughly equivalent to the protocols field on the Route entity. Note: Configured protocols on the Route entity are always added to the top level of the generated route but additional constraints can be provided by using the net.protocol field directly inside the expression.
tls.sni
String
If the connection is over TLS, the server_name extension from the ClientHello packet.
http.method
String
The method of the incoming HTTP request. (for example, "GET" or "POST")
http.host
String
The Host header of the incoming HTTP request.
http.path
String
The normalized request path according to rules defined in RFC 3986. This field value does not contain any query parameters that might exist.
http.path.segments.<segment_index>
String
A path segment extracted from the incoming (normalized) http.path with zero-based index. For example, for request path "/a/b/c/" or "/a/b/c", http.path.segments.1 will return "b".
Path segments extracted from the incoming (normalized) http.path within the given closed interval joined by /. Indexes are zero-based. For example, for request path "/a/b/c/" or "/a/b/c", http.path.segments.0_1 will return "a/b".
http.path.segments.len
Int
Number of segments from the incoming (normalized) http.path. For example, for request path "/a/b/c/" or "/a/b/c", http.path.segments.len will return 3.
http.headers.<header_name>
String[]
The value(s) of request header <header_name>. Note: The header name is always normalized to the underscore and lowercase form, so Foo-Bar, Foo_Bar, and fOo-BAr all become values of the http.headers.foo_bar field.
http.queries.<query_parameter_name>
String[]
The value(s) of query parameter <query_parameter_name>.
net.src.ip
IpAddr
IP address of the client.
net.src.port
Int
The port number used by the client to connect.
net.dst.ip
IpAddr
Listening IP address where Kong Gateway accepts the incoming connection.
net.dst.port
Int
Listening port number where Kong Gateway accepts the incoming connection.
An operator defines the desired comparison action to be performed on the field against the provided constant value. The operator always displays in the middle of the predicate, between the field and constant value.
The expressions language supports a rich set of operators that can be performed on various data types.
Operator
Name
Description
==
Equals
Field value is equal to the constant value.
!=
Not equals
Field value does not equal the constant value.
~
Regex match
Field value matches regex.
^=
Prefix match
Field value starts with the constant value.
=^
Postfix match
Field value ends with the constant value.
>=
Greater than or equal
Field value is greater than or equal to the constant value.
>
Greater than
Field value is greater than the constant value.
<=
Less than or equal
Field value is less than or equal to the constant value.
<
Less than
Field value is less than the constant value.
in
In
Field value is inside the constant value. This operator is used with IpAddr and IpCidr types to perform an efficient IP list check.
For example, net.src.ip in 192.168.0.0/24 only returns true if the value of net.src.ip is within 192.168.0.0/24.
not in
Not in
Field value is not inside the constant value. This operator is used with IpAddr and IpCidr types to perform an efficient IP list check.
For example, net.src.ip in 192.168.0.0/24 only returns true if the value of net.src.ip is within 192.168.0.0/24.
contains
Contains
Field value contains the constant value. This operator is used to check the existence of a string inside another string.
For example, http.path contains \"foo\" returns true if foo can be found anywhere inside http.path. This will match an HTTP.path that looks like /foo, /abc/foo, or /xfooy, for example.
&&
And
Returns true if both expressions on the left and right side evaluates to true.
||
Or
Returns true if any expressions on the left and right side evaluates to true.
(Expression)
Parenthesis
Groups expressions together to be evaluated first.
!
Not
Negates the result of a parenthesized expression.
The ! operator can only be used with parenthesized expression like !(foo == 1), it cannot be used with a bare predicate like ! foo == 1.”
Depending on the field type, only certain content types and operators are supported.
Field type
Supported content types and their supported operators
String
String: ==, !=, ~, ^=, =^, contains
Regex: ~
IpAddr
IpCidr: in, not in
IpAddr: ==
Int
Int: ==, !=, >=, >, <=, <
Expression
Regex: &&, ||
Notes:
The ~ operator is described as supporting both String ~ String and String ~ Regex.
In reality, Regex constant values can only be written as String on the right hand side.
The presence of ~ operators treats the string value as a regex.
Even with the ~ operator, String escape rules described previously still apply and it
is almost always easier to use raw string literals for the ~ operator.
The ~ operator does not automatically anchor the regex to the beginning of the input.
Meaning http.path ~ r#"/foo/\d"# could match a path like /foo/1 or /some/thing/foo/1.
If you want to match from the beginning of the string (anchoring the regex), then you must
manually specify it with the ^ meta-character. For example, http.path ~ r#"^/foo/\d"#.
Operator behavior differs when performing IP address-related comparisons.
Different families of address types for the field and constant value will
cause the predicate to return the following values:
The following expressions can be used to match HTTP requests.
Name
Example expression
Description
Prefix-based path matching
http.path ^= "/foo/bar"
Matches HTTP requests that have a path starting with /foo/bar.
Regex-based path matching
http.path ~ r#"/foo/bar/\d+"#
Matches HTTP request paths against a regular expression.
Case-insensitive path matching
lower(http.path) == "/foo/bar"
Ignores case when performing the path match.
Match by header value (“all” style matching).
http.headers.x_foo ~ r#"bar\d"#
If there are multiple header values for X-Foo and the client sends more than one X-Foo header with different values, the above example ensures that each instance of the value matches the regex r#"bar\d"#.
This is called “all” style matching.
Match by header value (“any” style matching)
any(http.headers.x_foo) ~ r#"bar\d"#
Returns true for the predicate as soon as any of the values pass the comparison.
Can be combined with other transformations, like lower().
Regex captures
http.path ~ r#"/foo/(?P<component>.+)"#
You can define regex capture groups in any regex operation, which will be made available later for plugins to use. Currently, this is only supported with the http.path field.
The matched value of component will be made available later to plugins such as Request Transformer Advanced.
The following expressions can be used to match TCP, TLS, and UDP requests.
Name
Example expression
Description
Match by source IP and destination port
net.src.ip in 192.168.1.0/24 && net.dst.port == 8080
This matches all clients in the 192.168.1.0/24 subnet and the destination port (which is listened to by Kong Gateway) is 8080. IPv6 addresses are also supported.
Match by SNI (for TLS routes)
tls.sni =^ ".example.com"
This matches all TLS connections with the .example.com SNI ending.
Both traditional and expressions modes use the same routing engine internally. Traditional mode configures routes with JSON, and expressions uses a DSL.
If you are working with APIOps pipelines that manipulate the route using deck file patch, we recommend using the JSON format used by the traditional router.