Chapter 10. Undertow
10.1. Introduction to Undertow Handler
Undertow is a web server designed to be used for both blocking and non-blocking tasks. It replaces JBoss Web in JBoss EAP 7. Some of its main features are:
- High Performance
- Embeddable
- Servlet 3.1
- Web Sockets
- Reverse Proxy
Request Lifecycle
When a client connects to the server, Undertow creates a io.undertow.server.HttpServerConnection
. When the client sends a request, it is parsed by the Undertow parser, and then the resulting io.undertow.server.HttpServerExchange
is passed to the root handler. When the root handler finishes, one of four things can happen:
The exchange is completed.
An exchange is considered complete if both request and response channels have been fully read or written. For requests with no content, such as GET and HEAD, the request side is automatically considered fully read. The read side is considered complete when a handler has written out the full response and has closed and fully flushed the response channel. If an exchange is already complete, then no action is taken.
The root handler returns normally without completing the exchange.
In this case the exchange is completed by calling
HttpServerExchange.endExchange()
.The root handler returns with an Exception.
In this case a response code of
500
is set and the exchange is ended usingHttpServerExchange.endExchange()
.The root handler can return after
HttpServerExchange.dispatch()
has been called, or after async IO has been started.In this case the dispatched task will be submitted to the dispatch executor, or if async IO has been started on either the request or response channels, then this will be started. In both of these cases, the exchange will not be finished. It is up to your async task to finish the exchange when it is done processing.
By far the most common use of HttpServerExchange.dispatch()
is to move execution from an IO thread, where blocking is not allowed, into a worker thread, which does allow for blocking operations.
Example: Dispatching to a Worker Thread
public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.isInIoThread()) { exchange.dispatch(this); return; } //handler code }
Because the exchange is not actually dispatched until the call stack returns, you can be sure that more than one thread is never active in an exchange at once. The exchange is not thread safe. However, it can be passed between multiple threads as long as both threads do not attempt to modify it at once.
Ending the Exchange
There are two ways to end an exchange, either by fully reading the request channel and calling shutdownWrites()
on the response channel and then flushing it, or by calling HttpServerExchange.endExchange()
. When endExchange()
is called, Undertow will check if the content has been generated yet. If it has, then it will simply drain the request channel and close and flush the response channel. If not and there are any default response listeners registered on the exchange, then Undertow will give each of them a chance to generate a default response. This mechanism is how default error pages are generated.
For more information on configuring Undertow, see Configuring the Web Server in the JBoss EAP Configuration Guide.
10.2. Using Existing Undertow Handlers with a Deployment
Undertow provides a default set of handlers that you can use with any application deployed to JBoss EAP.
To use a handler with a deployment, you need to add a WEB-INF/undertow-handlers.conf
file.
Example: WEB-INF/undertow-handlers.conf
File
allowed-methods(methods='GET')
All handlers can also take an optional predicate to apply that handler in specific cases.
Example: WEB-INF/undertow-handlers.conf
File with Optional Predicate
path('/my-path') -> allowed-methods(methods='GET')
The above example will only apply the allowed-methods
handler to the path /my-path
.
Undertow Handler Default Parameter
Some handlers have a default parameter, which allows you to specify the value of that parameter in the handler definition without using the name.
Example: WEB-INF/undertow-handlers.conf
File Using the Default Parameter
path('/a') -> redirect('/b')
You can also update the WEB-INF/jboss-web.xml
file to include the definition of one or more handlers, but using WEB-INF/undertow-handlers.conf
is preferred.
Example: WEB-INF/jboss-web.xml
File
<jboss-web> <http-handler> <class-name>io.undertow.server.handlers.AllowedMethodsHandler</class-name> <param> <param-name>methods</param-name> <param-value>GET</param-value> </param> </http-handler> </jboss-web>
A full list of the provided Undertow handlers can be found in the Provided Undertow Handlers reference.
10.3. Creating Custom Handlers
There are two ways to define custom handlers:
Defining Custom Handlers Using the WEB-INF/jboss-web.xml File
A custom handler can be defined in the WEB-INF/jboss-web.xml
file.
Example: Define Custom Handler in WEB-INF/jboss-web.xml
<jboss-web> <http-handler> <class-name>org.jboss.example.MyHttpHandler</class-name> </http-handler> </jboss-web>
Example: HttpHandler
Class
package org.jboss.example; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; public class MyHttpHandler implements HttpHandler { private HttpHandler next; public MyHttpHandler(HttpHandler next) { this.next = next; } public void handleRequest(HttpServerExchange exchange) throws Exception { // do something next.handleRequest(exchange); } }
Parameters can also be set for the custom handler using the WEB-INF/jboss-web.xml
file.
Example: Defining Parameters in WEB-INF/jboss-web.xml
<jboss-web> <http-handler> <class-name>org.jboss.example.MyHttpHandler</class-name> <param> <param-name>myParam</param-name> <param-value>foobar</param-value> </param> </http-handler> </jboss-web>
For these parameters to work, the handler class needs to have corresponding setters.
Example: Defining Setter Methods in Handler
package org.jboss.example; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; public class MyHttpHandler implements HttpHandler { private HttpHandler next; private String myParam; public MyHttpHandler(HttpHandler next) { this.next = next; } public void setMyParam(String myParam) { this.myParam = myParam; } public void handleRequest(HttpServerExchange exchange) throws Exception { // do something, use myParam next.handleRequest(exchange); } }
Defining Custom Handlers in the WEB-INF/undertow-handlers.conf File
Instead of using the WEB-INF/jboss-web.xml
for defining the handler, it could also be defined in the WEB-INF/undertow-handlers.conf
file.
myHttpHandler(myParam='foobar')
For the handler defined in WEB-INF/undertow-handlers.conf
to work, two things need to be created:
An implementation of
HandlerBuilder
, which defines the corresponding syntax bits forundertow-handlers.conf
and is responsible for creating theHttpHandler
, wrapped in aHandlerWrapper
.Example:
HandlerBuilder
Classpackage org.jboss.example; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.builder.HandlerBuilder; import java.util.Collections; import java.util.Map; import java.util.Set; public class MyHandlerBuilder implements HandlerBuilder { public String name() { return "myHttpHandler"; } public Map<String, Class<?>> parameters() { return Collections.<String, Class<?>>singletonMap("myParam", String.class); } public Set<String> requiredParameters() { return Collections.emptySet(); } public String defaultParameter() { return null; } public HandlerWrapper build(final Map<String, Object> config) { return new HandlerWrapper() { public HttpHandler wrap(HttpHandler handler) { MyHttpHandler result = new MyHttpHandler(handler); result.setMyParam((String) config.get("myParam")); return result; } }; } }
An entry in the file.
META-INF/services/io.undertow.server.handlers.builder.HandlerBuilder
. This file must be on the class path, for example, inWEB-INF/classes
.org.jboss.example.MyHandlerBuilder
10.4. Developing a Custom HTTP Mechanism
When Elytron is used to secure a web application, it is possible to implement custom HTTP authentication mechanisms that can be registered using the elytron
subsystem. It is then also possible to override the configuration within the deployment to make use of this mechanism without requiring modifications to the deployment.
All custom HTTP mechanisms are required to implement the HttpServerAuthenticationMechanism
interface.
In general, for an HTTP mechanism, the evaluateRequest
method is called to handle the request passing in the HTTPServerRequest
object. The mechanism processes the request and uses one of the following callback methods on the request to indicate the outcome:
-
authenticationComplete
- The mechanism successfully authenticated the request. -
authenticationFailed
- Authentication was attempted but failed. -
authenticationInProgress
- Authentication started but an additional round trip is needed. -
badRequest
- The authentication for this mechanism failed validation of the request. -
noAuthenticationInProgress
- The mechanism did not attempt any stage of authentication.
After creating a custom HTTP mechanism that implements the HttpServerAuthenticationMechanism
interface, the next step is to create a factory that returns instances of this mechanism. The factory must implement the HttpAuthenticationFactory
interface. The most important step in the factory implementation is to double check the name of the mechanism requested. It is important for the factory to return null if it cannot create the required mechanism. The mechanism factory can also take into account properties in the map passed in to decide if it can create the requested mechanism.
There are two different approaches that can be used to advertise the availability of a mechanism factory.
-
The first approach is to implement a
java.security.Provider
with theHttpAuthenticationFactory
registered as an available service once for each mechanism it supports. -
The second approach is to use a
java.util.ServiceLoader
to discover the factory instead. To achieve this, a file namedorg.wildfly.security.http.HttpServerAuthenticationMechanismFactory
should be added underMETA-INF/services
. The only content required in this file is the fully qualified class name of the factory implementation.
The mechanism can then be installed in the application server, as a module ready to be used:
module add --name=org.wildfly.security.examples.custom-http --resources=/path/to/custom-http-mechanism.jar --dependencies=org.wildfly.security.elytron,javax.api
Using a Custom HTTP Mechanism
Add a custom module.
/subsystem=elytron/service-loader-http-server-mechanism-factory=custom-factory:add(module=org.wildfly.security.examples.custom-http)
Add an
http-authentication-factory
to tie the mechanism factory to asecurity-domain
that will be used for the authentication./subsystem=elytron/http-authentication-factory=custom-mechanism:add(http-server-mechanism-factory=custom-factory,security-domain=ApplicationDomain,mechanism-configurations=[{mechanism-name=custom-mechanism}])
Update the
application-security-domain
resource to use the newhttp-authentication-factory
.NoteWhen an application is deployed, it by default uses the
other
security domain. Thus, you need to add a mapping to the application to map it to an Elytron HTTP authentication factory./subsystem=undertow/application-security-domain=other:add(http-authentication-factory=application-http-authentication)
The
application-security-domain
resource can now be updated to use the newhttp-authentication-factory
./subsystem=undertow/application-security-domain=other:write-attribute(name=http-authentication-factory,value=custom-mechanism) /subsystem=undertow/application-security-domain=other:write-attribute(name=override-deployment-config,value=true)
Notice that the command above overrides the deployment configuration. This means that the mechanisms from the
http-authentication-factory
will be used even if the deployment was configured to use a different mechanism. It is thus possible to override the configuration within a deployment to make use of a custom mechanism, without requiring modifications to the deployment itself.Reload the server
reload