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
And 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 using HttpServerExchange.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 this case 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. This pattern generally looks like:

Example: Dispatching to a Worker Thread

public void handleRequest(final HttpServerExchange exchange) throws Exception {
    if (exchange.isInIoThread()) {
      exchange.dispatch(this);
      return;
    }
    //handler code
}

Because exchange is not actually dispatched until the call stack returns, you can be sure that more that 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 the 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. You can find a full list of the available handlers as well as their attributes here.

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 may 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.

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 also may 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 provided Undertow handlers can be found here.

10.3. Creating Custom Handlers

  • 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 could also be set for the custom handler via 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);
    }
}

  • 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:

    1. An implementation of HandlerBuilder, which defines the corresponding syntax bits for undertow-handlers.conf and is responsible for creating the HttpHandler, wrapped in a HandlerWrapper.

      Example: HandlerBuilder Class

      package 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;
                  }
              };
          }
      }

    2. An entry in the file META-INF/services/io.undertow.server.handlers.builder.HandlerBuilder. This file must be on the class path, for example, in WEB-INF/classes.

      org.jboss.example.MyHandlerBuilder