Rule Language

CommandBox's server rules are based directly on the "Predicate Language" feature of JBoss Undertow. All of the official documentation for Undertow's predicate language applies to CommandBox. CommandBox doesn't limit the power of what Undertow provides at all. In fact, we give you some additional out-of-the box predicates and handlers you can use.

There are three concepts you need to understand and you'll be writing your own rules in no time.

  • Handler - A wrapper around the request that takes some sort of action such as returning a status code or rewriting the request.

  • Predicate - A conditional that evaluates to true or false. Used to Conditionally apply a handler

  • Exchange Attribute - An abstracted way to refer to any piece of information about the request or response. Can include URL, query string, cookies, headers, or CGI variables.

Since the parsing of your rules is handled by Undertow, it is case sensitive in many areas and CommandBox cannot control this. The following must always be lower case:

  • Predicate names

  • Handler names

  • Parameter names

  • Keywords like and, or, else

The full undertow docs can be found here: https://undertow.io/undertow-docs/undertow-docs-2.0.0/

Handler

A handler will take action on the request. This can redirect the request, rewrite it, abort it, or modify attributes of the request such as setting an HTTP header or status code. If the predicate is omitted, the handler will always fire for all requests. Ex:

disallowed-methods(trace)

Common handlers:

  • set() - Sets an exchange attribute (see below)

  • rewrite() - Rewrite the request URL

  • redirect() - Redirect to another URL

  • header() - Sets an HTTP response header

  • set-error() - Set HTTP response code and returns the corresponding error page

  • ip-access-control() - Configure IP-based allow/deny rules (wildcards and IP ranges allowed)

  • done - Skips remaining rules

  • request-limit() - Limits concurrent requests

  • restart - Restarts process back at start of predicate rule chain (combine with rewrite, etc)

  • proxy() - Creates a proxy to a single host

  • load-balanced-proxy() - Creates a round robin reverse proxy to a list of hosts

  • allowed-methods() / disallowed-methods() - Enforces whitelist/blacklist of HTTP methods on current request

A handler is called using the “name” from the docs and passing any required parameters. Params can be named or positional (if there is only one). Quoting values is only required if they have a space, comma or brace. The following are all equivalent:

set-error(404)
set-error("404")
set-error(response-code=404)
set-error(response-code="404")

Handler parameters that accept an array use the Java array literal syntax which is a comma-delimited list of values in curly braces.

load-balanced-proxy( { 'http://host.com', 'http://host2.com' } )

More than one handler can be run by wrapping them in curly braces and using semicolons between them

path(/restart) -> { rewrite(/foo/a/b); restart; }

A chain of handlers can be configured for the true AND false evaluation of a single predicate using the “else” keyword.

regex('(.*).css') -> set(attribute='%{o,css}', value='true') else set(attribute='%{o,css}', value='false');

Predicates

A predicate is a condition that must be matched for the handler to kick in. If using path-based predicates on Windows, make sure to use the -nocase version for security rules since your file system is not case sensitive.

Common predicates:

  • path()/path-nocase() - match the incoming request path

  • path-suffix()/path-suffix-nocase() - match full file/folder names at the end of the path like file.cfm

  • path-prefix()/path-prefix-nocase() - match full file/folder names at the start of the path like /lucee/admin

  • is-file / is-directory - check if the %{RELATIVE_PATH} is an existing file or directory on disk. While relative path is the default, you can pass another exchange attribute to check as is-file( %{q,myQueryParam} )

  • method() - Match the HTTP method

  • regex()/regex-nocase() - Match a regular expression against any exchange attribute

  • equals()/equals-nocase() and contains()/contains-nocase() - Do a text compare on any exchange attribute

  • path-template() - match a path with placeholders like /foo/{bar} and the placeholders become variables for use in later predicates or handlers in the chain.

A predicate is called by placing parentheses after the name and passing in the required arguments as specified in the docs for that predicate. Quoting values is only required if they have a space, comma or brace. The following are all equivalent:

method(GET)
method("GET")
method(value=GET)
method(value="GET")

Complex conditions can be constructed with more than one predicate using the keywords “and” or “or”.

path-prefix(/tests/) OR path-prefix(/workbench)

A predicate can be negated with the keyword “not

not method(POST)

Exchange Attributes

Exchange attributes are an abstraction Undertow provides to reference any part of the HTTP request or response. There is a textual placeholder that can be used in predicates and handlers that will be expanded in-place.

Common Exchange Attributes:

  • %{REMOTE_IP} - Remote IP address

  • %{PROTOCOL} - Request protocol

  • %{METHOD} - Request method

  • %{REMOTE_USER} - Remote user that was authenticated

  • %{RESPONSE_TIME} - Time taken to process the request, in ms

  • %{c,cookie_name} - Any cookie value

  • %{q,query_param_name} - Any query parameter

  • %{i,request_header_name} - Any request header

  • %{o,response_header_name} - Any response header

  • %{REQUEST_URL} - The requested URL

  • %{RELATIVE_PATH} - The portion after the domain

  • ${anything-here} - Any value from the predicate context (such as regex capture groups or path-template placeholders)

Use an exchange attribute like you would a variable as the parameter to a handler or predicate.

set(attribute='%{o,someHeader}', value=myValue)
not equals('%{REMOTE_IP}', 127.0.0.1) -> set-error(403)

Attributes, Cookie, query params, request headers, and response header names are not case sensitive.

These are all equivalent:

regex(pattern="/(MSIE 6\.0)", value="%{i,user-agent}" ) -> response-code( 404 )
regex(pattern="/(MSIE 6\.0)", value="%{i,USER-AGENT}" ) -> response-code( 404 )
regex(pattern="/(MSIE 6\.0)", value="%{i,UsEr-AgEnT}" ) -> response-code( 404 )