Predicates Attributes and Handlers
Introduction
Predicates and Exchange attributes are an abstraction that allow handlers to read, write and make decisions based on certain attributes of a request without hard coding this into the handler. These form the basis of Undertow’s text based handler configuration format. Some examples are shown below:
Use the reverse proxy to send all requests to /reports to a different backend server:
path-prefix('/reports') -> reverse-proxy({'http://reports1.mydomain.com','http://reports2.mydomain.com'})
Redirect all requests from /a to /b. The first example only redirects if there is an exact match, the later examples match all paths that start with /a:
path('/a') -> redirect('/b')
path-prefix('/a') -> redirect('/b${remaining}')
regex('/a(.*)') -> set(attribute='%{o,Location}', value='/b${1}') -> response-code(302)
Exchange Attributes
An exchange attribute represents the value of part of the exchange. For example the path attribute represents the request path, the method attribute represents the HTTP. Even though these attributes can be retrieved and modified directly this requires a handler to hard code the attribute that they wish to use. For example Undertow provides a handler that checks an attribute against an access control list. There are lots of different attributes we may wish to check against the ACL (e.g. username, User-Agent header, request path).
Predicates
A predicate is a function that takes a value (in this case the HttpServerExchange
) and returns a true or false value.
This allows actions to be taken based on the return value of the predicate. In general any handler that needs to make
a boolean decision based on the exchange should use a predicate to allow for maximum flexibility.
The provided predicate handler can be used to make a decision between which handler to invoke based on the value of a predicate.
Programmatic Representation of Exchange Attributes
An exchange attribute is represented by the io.undertow.attribute.ExchangeAttribute
interface:
/**
* Representation of a string attribute from a HTTP server exchange.
*/
public interface ExchangeAttribute {
/**
* Resolve the attribute from the HTTP server exchange. This may return null if the attribute is not present.
* @param exchange The exchange
* @return The attribute
*/
String readAttribute(final HttpServerExchange exchange);
/**
* Sets a new value for the attribute. Not all attributes are writable.
* @param exchange The exchange
* @param newValue The new value for the attribute
*/
void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException;
}
Undertow provides implementation of a lot of attributes out of the box, most of which can be accessed using the
io.undertow.attribute.ExchangeAttributes
utility class. Some of the attributes that are provided include request and
response headers, cookies, path, query parameters, the current user and more.
Programmatic Representation of Predicates
Predicates are represented by the io.undertow.predicate.Predicate
interface:
/**
* A predicate.
*
* This is mainly uses by handlers as a way to decide if a request should have certain
* processing applied, based on the given conditions.
*/
public interface Predicate {
/**
* Attachment key that can be used to store additional predicate context that allows the predicates to store
* additional information. For example a predicate that matches on a regular expression can place additional
* information about match groups into the predicate context.
*
* Predicates must not rely on this attachment being present, it will only be present if the predicate is being
* used in a situation where this information may be required by later handlers.
*
*/
AttachmentKey<Map<String, Object>> PREDICATE_CONTEXT = AttachmentKey.create(Map.class);
boolean resolve(final HttpServerExchange value);
}
Undertow provides built in predicates that can be created using the io.undertow.predicate.Predicates
utility class.
This includes basic boolean logic predicates (and, or and not), as well as other useful predicates such as path
matching (including prefix and suffix based matches), regular expression matching, contains and exists. Many of these
predicates operate on exchange attributes, so they can be used to match arbitrary parts of the exchange. The following
example demonstrates a predicate that matches any exchange that has no Content-Type header where the method is POST:
Predicate predicate = Predicates.and(
Predicates.not(Predicates.exists(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE))),
Predicates.equals("POST", ExchangeAttributes.requestMethod()));
Textual Representation
Undertows predicate language is still considered tech preview. Its syntax will likely change in a future version as the language is expanded. |
All these attributes and predicates are all well and good, but unless there is a way for the end user to configure them without resorting to programmatic means they are not super useful. Fortunately Undertow provides a way to do just that.
Exchange Attributes
Exchange attributes may have up to two textual representations, a long one and a short one. The long version takes the form %{attribute}, while the short version is a percent sign followed by a single character. A list of the built in attributes provided by Undertow is below:
Attribute | Short Form | Long Form |
---|---|---|
Remote IP address |
|
|
Local IP address |
|
|
Bytes sent, excluding HTTP headers, or '-' if no bytes were sent |
|
|
Bytes sent, excluding HTTP headers |
|
|
Remote host name |
|
|
Request protocol |
|
|
Remote logical username from identd (always returns '-') |
|
|
Request method |
|
|
Local port |
|
|
Query string (prepended with a '?' if it exists, otherwise an empty string) |
|
|
First line of the request |
|
|
HTTP status code of the response |
|
|
Date and time, in Common Log Format format |
|
|
Remote user that was authenticated |
|
|
Requested URL path |
|
|
Request relative path |
|
|
Local server name |
|
|
Time taken to process the request, in millis |
|
|
Time taken to process the request, in seconds |
|
|
Time taken to process the request, in micros |
|
|
Time taken to process the request, in nanos |
|
|
Current request thread name |
|
|
SSL cypher |
|
|
SSL client certificate |
|
|
SSL session id |
|
|
Cookie value |
|
|
Query parameter |
|
|
Request header |
|
|
Response header |
|
|
Value from the predicate context |
|
Any tokens that do not follow one of the above patterns are assumed to be literals. For example assuming a
user name of 'Stuart' and a request method of 'GET' the attribute text Hello %u the request method is %m
will give
the value Hello Stuart the request method is GET.
These attributes are used anywhere that text based configuration is required, e.g. specifying the log pattern in the access log.
Some handlers may actually modify these attributes. In order for this to work the attribute must not be read only, and must consist of only a single token from the above table.
Textual Representation of Predicates
Sometimes it is also useful to have a textual representation of a predicate. For examples when configuring a handler in Wildfly we may want it only to run if a certain condition is met, and when doing rewrite handling we generally do not want to re-write all requests, only a subset of them.
To this end Undertow provides a way to specify a textual representation of a predicate. In its simplest form, a
predicate is represented as predicate-name[name1=value1,name2=value2]
.
For example, the following predicates all match POST requests:
method(POST)
method(value=POST)
equals({%{METHOD}, POST})
equals(%m, "POST")
regex(pattern="POST", value="%m", full-match=true)
Lets examine these a bit more closely. The first one method(POST)
uses the built in method predicate that matches
based on the method. As this predicate takes only a single parameter (that is the default parameter) it is not necessary
to explicitly specify the parameter name. Also note that POST is not quoted, quoting is only necessary if the token
contains spaces, commas or square braces.
The second example method(value=POST)
is the same as the first, except that the parameter name is explicitly specified.
The third and fourth examples demonstrates the 'equals' predicate. This predicate actually takes one parameter that is an array, and will return true if all items in the array are equal. Arrays are generally enclosed in curly braces, however in this case where there is a single parameter that is the default parameter the braces can be omitted.
The final examples shows the use of the regex predicate. This takes 3 parameters, the pattern to match, the value to match against and full-match, which determines if the pattern must match the whole value or simply part of it.
Some predicates may also capture additional information about the match and store it in the predicate context. For example the regex predicate will store the match under the key '0', and any match groups under the key '1', '2' etc.
These contextual values can then be retrieved by later predicates of handlers using the syntax ${0}
, ${1}
etc.
Predicates can be combined using the boolean operators 'and', 'or' and not. Some examples are shown below:
not method(POST)
method(POST) and path-prefix("/uploads")
path-template(value="/user/{username}/*") and equals(%u, ${username})
regex(pattern="/user/(.*?)./.*", value=%U, full-match=true) and equals(%u, ${1})
The first predicate will match everything except post requests. The second will match all post requests to /uploads.
The third predicate will match all requests to URL’s of the form /user/{username}/* where the username is equal to the
username of the currently logged in user. In this case the username part of the URL is captured, and the equals handler
can retrieve it using the ${username}
syntax shown above. The fourth example is the same as the third, however it uses
a regex with a match group rather than a path template.
The complete list of built in predicates is shown below:
Name | Parameters | Default Parameter | Additional context |
---|---|---|---|
auth-required |
|||
contains |
search: String[] (required), value: attribute (required) |
||
directory |
value: attribute |
value |
Only usable within the scope of Servlet deployment |
dispatcher |
value: String (required) |
value |
Only usable within the scope of Servlet deployment |
equals |
value: attribute[] (required) |
value |
|
exists |
value: attribute (required) |
value |
|
file |
value: attribute |
value |
Only usable within the scope of Servlet deployment |
max-content-size |
value: Long (required) |
value |
|
method |
value: String[] (required) |
value |
|
min-content-size |
value: Long (required) |
value |
|
path |
path: String[] (required) |
path |
|
path-prefix |
path: String[] (required) |
path |
Unmatched under ${remaining} |
path-suffix |
path: String[] (required) |
path |
|
path-template |
match: attribute, value: String (required) |
value |
Path template elements under the name |
regex |
case-sensitive: Boolean, full-match: Boolean, pattern: String (required), value: attribute |
pattern |
Match groups under number |
secure |
Textual Representation of Handlers
Handlers are represented in a similar way to predicates. Handlers and predicates are combined into the Undertow predicate language.
The general form of this language is predicate → handler
. If the predicate evaluates to true the handler is executes.
If there is only a handler present then the handler is always executed. Handlers are executed in order and separated
by line breaks or semi colons. Curly braces can be used to create a sub grouping, with all handlers (and possibly predicates)
in the sub grouping being executed. The 'else' keyword can be used to execute a different handler or sub grouping if the
predicate evaluates to false. Sub grouping can contain other predicates and sub groupings.
The 'restart' handler is a special handler that will restart execution at the beginning of the predicated handler list. The 'done' handler will skip any remaining rules.
Some examples are below:
path(/skipallrules) and true -> done
method(GET) -> set(attribute='%{o,type}', value=get)
regex('(.*).css') -> { rewrite('${1}.xcss'); set(attribute='%{o,chained}', value=true) }
regex('(.*).redirect$') -> redirect('${1}.redirected')
set(attribute='%{o,someHeader}', value=always)
path-template('/foo/{bar}/{f}') -> set[attribute='%{o,template}', value='${bar}')
path-template('/bar->foo') -> {
redirect(/);
} else {
path(/some-other-path) -> header(header=my-header,value=my-value)
}
regex('(.*).css') -> set(attribute='%{o,css}', value='true') else set(attribute='%{o,css}', value='false');
path(/restart) -> {
rewrite(/foo/a/b);
restart;
}
Built in Handlers
Access Log Handler
Name: |
access-log |
Class: |
io.undertow.server.handlers.accesslog.AccessLogHandler |
Parameters: |
format: String (required) |
Default Parameter |
format |
A handler that will log access attempts to JBoss Logging. The output can be configured via the format parameter which takes exchange attributes.
Access Control Handler
Name: |
access-control |
Class: |
io.undertow.server.handlers.AccessControlListHandler |
Parameters: |
acl: String[] (required), default-allow: boolean, attribute: ExchangeAttribute (required) |
Default Parameter |
This handler is used to specify access control lists. These lists consist of an array of strings, which
follow the format {pattern} allow|deny
, where {pattern} is a regular expression. These rules are applied
against the specified exchange attribute until a match is found. If the result in deny
then the request
is rejected with a 403 response, otherwise the next handler is invoked.
If no match is found the default behaviour is to deny.
Allowed Methods
Name: |
allowed-methods |
Class: |
io.undertow.server.handlers.AllowedMethodsHandler |
Parameters: |
methods: String[] (required) |
Default Parameter |
methods |
This handler takes a list of allowed methods. If an incoming request’s method is in the specific method list then the request is allowed, otherwise it is rejected with a 405 response (method not allowed).
Blocking Handler
Name: |
blocking |
Class: |
io.undertow.server.handlers.BlockingHandler |
Parameters: |
|
Default Parameter |
This handler will mark the request as blocking and dispatch it to the XNIO worker thread.
Buffer Request Handler
Name: |
buffer-request |
Class: |
io.undertow.server.handlers.RequestBufferingHandler |
Parameters: |
buffers: int (required) |
Default Parameter |
buffers |
This handler will pause request processing while it attempts to read the request body. It uses Undertow
buffers to store the request body, so the amount of data that can be buffered is determined by the
buffer size multiplied by the buffers
parameter.
Once either all data is read or the configured maximum amount of data has been read then the next handler will be invoked.
This can be very useful when use a blocking processing model, as the request will be read using non-blocking IO, and as the request will not be dispatched to the thread pool until the data has been read.
Byte Range Handler
Name: |
byte-range |
Class: |
io.undertow.server.handlers.ByteRangeHandler |
Parameters: |
send-accept-ranges: boolean |
Default Parameter |
send-accept-ranges |
A handler that adds generic support for range requests. This handler will work with any request, however in general it is less efficient than supporting range requests directly, as the full response will be generated and then pieces that are not requested will be discarded. Nonetheless for dynamic content this is often the only way to fully support ranges.
If the handler that generated the response already handled the range request then this handler will have no effect.
By default the Accept-Range
header will not be appended to responses, unless the send-accept-ranges
parameter is true.
Canonical Path Handler
Name: |
canonical-path |
Class: |
io.undertow.server.handlers.CanonicalPathHandler |
Parameters: |
|
Default Parameter |
Handler that turns a path into a canonical path by resolving ../
and ./
segments. If these segments
result in a path that would be outside the root then these segments are simply discarded.
This can help prevent directory traversal attacks, as later handlers will only every see a path that is not attempting to escape the server root.
Clear Handler
Name: |
clear |
Class: |
io.undertow.server.handlers.SetAttributeHandler |
Parameters: |
attribute: ExchangeAttribute (required) |
Default Parameter |
attribute |
A special form of the set-attribute handler that sets an attribute to null.
Compress Handler
Name: |
compress |
Class: |
io.undertow.server.handlers.encoding.EncodingHandler |
Parameters: |
|
Default Parameter |
A handler that adds support for deflate and gzip compression.
Disable Cache Handler
Name: |
disable-cache |
Class: |
io.undertow.server.handlers.DisableCacheHandler |
Parameters: |
|
Default Parameter |
A handler that will set headers to disable the browser cache. The headers that are set are:
-
Cache-Control: no-cache, no-store, must-revalidate
-
Pragma: no-cache
-
Expires: 0
Disallowed Methods Handler
Name: |
disallowed-methods |
Class: |
io.undertow.server.handlers.DisallowedMethodsHandler |
Parameters: |
methods: String[] (required) |
Default Parameter |
methods |
This handler takes a list of disallowed methods. If an incoming request’s method is in the specific method list then the request is rejected with a 405 response (method not allowed), otherwise it is allowed.
Done Handler
Name: |
done |
Class: |
N/A |
Parameters: |
|
Default Parameter |
This is a pseudo handler that will finish execution of the current predicated handlers, and invoke whatever handler is configured after the current predicated handlers block.
Request Dumping Handler
Name: |
dump-request |
Class: |
io.undertow.server.handlers.RequestDumpingHandler |
Parameters: |
|
Default Parameter |
A handler that will dump all relevant details from a request to the log. As this is quite expensive a predicate should generally be used to control which requests are dumped.
Eager Form Parsing Handler
Name: |
eager-form-parser |
Class: |
io.undertow.server.handlers.form.EagerFormParsingHandler |
Parameters: |
|
Default Parameter |
Handler that eagerly parses form data. The request chain will pause while the data is being read, and then continue when the form data is fully passed.
This is not strictly compatible with servlet, as it removes the option for the user to parse the request themselves. It also removes the option to control the charset that the request will be decoded to. |
Error File Handler
Name: |
error-file |
Class: |
io.undertow.server.handlers.error.FileErrorPageHandler |
Parameters: |
file: String (required), response-codes: int[] (required) |
Default Parameter |
A handler that will respond with a file based error page if the request has finished with one of the specified error codes and no response body has been generated.
Forwarded Handler
Name: |
forwarded |
Class: |
io.undertow.server.handlers.ForwardedHandler |
Parameters: |
|
Default Parameter |
This handler implements rfc7239 and handles the Forwarded
header. It does this by updating the
exchange so its peer and local addresses reflect the values in the header.
This should only be installed behind a reverse proxy that has been configured to send the Forwarded
header, otherwise a remote user can spoof their address by sending a header with bogus values.
In general either this handler or proxy-peer-address
handler should be used, they should not both
be installed at once.
Header Handler
Name: |
header |
Class: |
io.undertow.server.handlers.SetHeaderHandler |
Parameters: |
header: String (required), value: ExchangeAttribute (required) |
Default Parameter |
The handler sets a response header with the given name and value.
Http Continue Accepting Handler
Name: |
http-continue-accept |
Class: |
io.undertow.server.handlers.HttpContinueAcceptingHandler |
Parameters: |
|
Default Parameter |
A handler that will respond to requests that expect a 100-continue response.
IP Access Control Handler
Name: |
ip-access-control |
Class: |
io.undertow.server.handlers.IPAddressAccessControlHandler |
Parameters: |
acl: String[] (required), default-allow: boolean, failure-status: int |
Default Parameter |
acl |
A handler that provided IP based access control. The ACL list is of the form {pattern} allow|deny
, where
{pattern} can be one of the following (both IPv4 and IPv6 are accepted):
-
An exact IP address (e.g. 192.168.0.1)
-
An Wildcard IP address (e.g. 192.168.0.*)
-
A Wildcard in slash notation: (e.g. 192.168.0.0/24)
By default anything that is not matched will be denied.
The failure-status
param allows you to set the response code to be set on failure, 403
will be sent
by default.
JVM Route Handler
Name: |
jvm-route |
Class: |
io.undertow.server.JvmRouteHandler |
Parameters: |
session-cookie-name: String, value: String (required) |
Default Parameter |
value |
A handler that appends a specified JVM route to session cookie values. This can enable sticky sessions for load balancers that support it.
Learning Push Handler
Name: |
learning-push |
Class: |
io.undertow.server.handlers.LearningPushHandler |
Parameters: |
max-age: int, max-entries: int |
Default Parameter |
Mark Secure Handler
Name: |
mark-secure |
Class: |
io.undertow.servlet.handlers.MarkSecureHandler |
Parameters: |
|
Default Parameter |
A handler that will mark a request as secure. This means that javax.servlet.ServletRequest#isSecure()
will return true, and the security layer will consider the request as being sent over a confidential
channel.
Path Separator Handler
Name: |
path-separator |
Class: |
io.undertow.server.handlers.PathSeparatorHandler |
Parameters: |
|
Default Parameter |
A handler that only takes effect on windows systems (or other systems that do not use '/' as the path separator character). Any instances of the path seperator character in the URL are replaced with a '/'.
Proxy Peer Address Handler
Name: |
proxy-peer-address |
Class: |
io.undertow.server.handlers.ProxyPeerAddressHandler |
Parameters: |
|
Default Parameter |
A handler that handles X-Forwarded-*
headers by updating the values on the current exchange to match
what was sent in the header.
This should only be installed behind a reverse proxy that has been configured to send the X-Forwarded-*
header, otherwise a remote user can spoof their address by sending a header with bogus values.
The headers that are read are:
-
X-Forwarded-For
-
X-Forwarded-Proto
-
X-Forwarded-Host
-
X-Forwarded-Port
In general either this handler or forwarded
handler should be used, they should not both
be installed at once.
Redirect Handler
Name: |
redirect |
Class: |
io.undertow.server.handlers.RedirectHandler |
Parameters: |
value: ExchangeAttribute (required) |
Default Parameter |
value |
A handler that will redirect to the location specified by value.
Request Limiting Handler
Name: |
request-limit |
Class: |
io.undertow.server.handlers.RequestLimitingHandler |
Parameters: |
requests: int (required) |
Default Parameter |
requests |
A handler that will limit the number of concurrent requests to the limit specified, requests that exceed the limit will be queued.
Resolve Local Name Handler
Name: |
resolve-local-name |
Class: |
io.undertow.server.handlers.LocalNameResolvingHandler |
Parameters: |
|
Default Parameter |
A handler that will resolve the exchange destination address, if it is not already resolved.
Resolve Peer Name Handler
Name: |
resolve-peer-name |
Class: |
io.undertow.server.handlers.PeerNameResolvingHandler |
Parameters: |
|
Default Parameter |
A handler that will resolve the exchange source address, if it is not already resolved.
Resource Handler
Name: |
resource |
Class: |
io.undertow.server.handlers.resource.ResourceHandler |
Parameters: |
allow-listing: boolean, location: String (required) |
Default Parameter |
location |
A handler that will serve files from the local file system at the specified location.
Response Code Handler
Name: |
response-code |
Class: |
io.undertow.server.handlers.ResponseCodeHandler |
Parameters: |
value: int (required) |
Default Parameter |
value |
A handler that sets the specified status code and then ends the exchange.
Response Rate Limiting Handler
Name: |
response-rate-limit |
Class: |
io.undertow.server.handlers.ResponseRateLimitingHandler |
Parameters: |
bytes: int (required), time: long (required) |
Default Parameter |
A handler that limits the speed of responses. This speed is set in terms of bytes per time block.
The time block is specified in MS, so if you wanted a limit of 1kb per second you would set bytes to 1024 and time to 1000.
Restart Handler
Name: |
restart |
Class: |
N\A |
Parameters: |
|
Default Parameter |
A pseudo handler that restarts execution of the current predicated handler block. Care must be taken to avoid infinite loops, usually by making sure that the exchange has been modified in such a way that it will not end up on the restart handler before calling restart.
Reverse Proxy Handler
Name: |
reverse-proxy |
Class: |
io.undertow.server.handlers.proxy.ProxyHandler |
Parameters: |
hosts: String[] (required), rewrite-host-header: Boolean |
Default Parameter |
hosts |
A handler that will proxy requests to the specified hosts, using round-robin based load balancing.
Rewrite Handler
Name: |
rewrite |
Class: |
io.undertow.server.handlers.SetAttributeHandler |
Parameters: |
value: ExchangeAttribute (required) |
Default Parameter |
value |
A handler that rewrites the current path.
Set Attribute Handler
Name: |
set |
Class: |
io.undertow.server.handlers.SetAttributeHandler |
Parameters: |
attribute: ExchangeAttribute (required), value: ExchangeAttribute (required) |
Default Parameter |
A handler that can be used to set any writable attribute on the exchange.
Secure Cookie Handler
Name: |
secure-cookie |
Class: |
io.undertow.server.handlers.SecureCookieHandler |
Parameters: |
|
Default Parameter |
A handler that will mark any cookies that are set over a secure channel as being secure cookies.
SSL Headers Handler
Name: |
ssl-headers |
Class: |
io.undertow.server.handlers.SSLHeaderHandler |
Parameters: |
|
Default Parameter |
A handler that will set SSL information on the connection based on headers received from the load balancer.
This is for situations where SSL is terminated at the load balancer, however SSL information is still required on the back end.
The headers that are read are:
-
SSL_CLIENT_CERT
-
SSL_CIPHER
-
SSL_SESSION_ID
-
SSL_CIPHER_USEKEYSIZE
This handler should only be used if the front end load balancer is configured to either set or clear these headers, otherwise remote users can trick the server into thinking that SSL is in use over a plaintext connection.
Store Response Header
Name: |
store-response |
Class: |
io.undertow.server.handlers.StoredResponseHandler |
Parameters: |
|
Default Parameter |
A handler that reads the full response and stores it in an attachment on the exchange. Generally used in combination with the request dumping handler to dump the response body.
Stuck Thread Detection Handler
Name: |
stuck-thread-detector |
Class: |
io.undertow.server.handlers.StuckThreadDetectionHandler |
Parameters: |
threshhold: int |
Default Parameter |
threshhold |
A handler that will print a log message if a request takes longer than the specified number of seconds to complete.
Trace Handler
Name: |
trace |
Class: |
io.undertow.server.handlers.HttpTraceHandler |
Parameters: |
|
Default Parameter |
A handler that responds to HTTP TRACE requests.
Uncompress Handler
Name: |
uncompress |
Class: |
io.undertow.server.handlers.encoding.RequestEncodingHandler |
Parameters: |
|
Default Parameter |
A handler that can decompress a content-encoded request. Note that such requests are not part of the HTTP standard, and as such represent a non-compatible extension. This will generally used for RPC protocols to enabled compressed invocations.
URL Decoding Handler
Name: |
url-decoding |
Class: |
io.undertow.server.handlers.URLDecodingHandler |
Parameters: |
charset: String (required) |
Default Parameter |
charset |
A handler that will decode the request path (including query parameters) into the specified charset. To use this handler request decoding must be disabled on the listener.