Configuring a Custom Undertow Filter in WildFly

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

Internal

Example


https://github.com/NovaOrdis/playground/tree/master/jboss/undertow/custom-filter

Overview

This article explains how to write and install a custom Undertow filter within WildFly. Note that the filter code is executed by one of the XNIO Worker's IO threads. The thread enters and exits the filter's code while the request processing is done later, on a different thread.

In order to install the filter, you will need to wrap the filter class in a WildFly module, deploy the module and configure the Undertow subsystem to use the custom filter.

Write the Filter Class

For this specific example, the filter measures the request processing time. The implementation is interesting because the start watch event happens on a different thread (a XNIO Worker IO thread) than the one that triggers the stop watch event (a XNIO Worker worker thread), unlike in Tomcat's case, where a filters' pre-servlet-invocation and post-servlet-invocation code is executed on the same thread. That is why we will need to register an ExchangeCompletedListener instance with the exchange.


Important: the handler's code is executed by one of the XNIO Worker's IO threads. Those threads service NIO selectors, and it is critically important NOT to block them. For more details on non-blocking IO in Java, see NIO Concepts.

This pattern is common for all Undertow's filters.

package com.novaordis.playground.wildfly.undertow.customfilter;

import io.undertow.server.ExchangeCompletionListener;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ResponseTimeHandler implements HttpHandler {

    private static final Logger log = LoggerFactory.getLogger(ResponseTimeHandler.class);

    private HttpHandler next;
    private String param1;

    public ResponseTimeHandler(HttpHandler next) {
        this.next = next;
    }

    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {

        //
        // it is very important NOT to run anything that could block the thread - this is executed by one of the
        // XNIO Worker's IO threads.
        //

        StopWatch stopWatch = new StopWatch();
        stopWatch.setT0(System.currentTimeMillis());
        exchange.addExchangeCompleteListener(stopWatch);
        next.handleRequest(exchange);
    }

    public void setParam1(String s) {
        this.param1 = s;
    }

    private class StopWatch implements ExchangeCompletionListener {

        private long t0;

        /**
         * This code gets executed on one of XNIO Worker's worker thread (as opposite to the IO threads), after
         * the exchange is completed.
         */
        @Override
        public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) {

            try {

                // stop the watch and calculate the difference
                long t1 = System.currentTimeMillis();
                log.info("request took " + (t1 - t0) + " ms");
            }
            finally {

                if (nextListener != null) {
                    nextListener.proceed();
                }
            }
        }

        public void setT0(long t0) {
            this.t0 = t0;
        }
    }
}

The latest version of the code is available here:

https://github.com/NovaOrdis/playground/tree/master/jboss/undertow/custom-filter/src/main/java/com/novaordis/playground/wildfly/undertow/customfilter/ResponseTimeHandler.java

Filter Parameters

Filters can accept arbitrary parameters specified in the configuration file. If a filter is configured with parameter, then the implementation class must expose corresponding mutators, otherwise java.beans.IntrospectionException exceptions will be thrown at runtime. See setParam1() above. For details on how to specify parameters in the configuration file, see Specify Optional Filter Parameters.

Note that as per EAP 7.0 Beta, throwing an exception from the mutator method is silently ignored.

Maven Dependencies

<dependency>
  <groupId>io.undertow</groupId>
  <artifactId>undertow-core</artifactId>
  <version>1.3.12.Final</version>
  <scope>provided</scope>
</dependency>

Create and Deploy a WildFly Module

In order to be made available to the Undertow subsystem, the custom filter code must be deployed as a WildFly Module. For more details on how to build and deploy a custom module, see:

Writing a Custom WildFly Module

Express the Module's Dependency on io.undertow.core

The filter code must implement io.undertow.server.HttpHandler so the module must have access to those classes. This is achieved by declaring the module's dependency on io.undertow.core (note that other dependencies such as org.slf4j are required as well):

<?xml version="1.0" encoding="UTF-8"?>

<module xmlns="urn:jboss:module:1.1" name="com.novaordis.playground.wildfly.undertow.customfilter" slot="1">

    <resources>
        <resource-root path="custom-undertow-filter-1.jar"/>
    </resources>

    <dependencies>
        <module name="io.undertow.core"/>
        <module name="org.slf4j"/>
    </dependencies>

</module>

Configure the Undertow Subsystem

Declare the Filter in the <filters> Section

...
<subsystem xmlns="urn:jboss:domain:undertow:3.0">
   ...
   <filters>
      ...
      <filter name="response-time" 
              module="com.novaordis.playground.wildfly.undertow.customfilter:1" 
              class-name="com.novaordis.playground.wildfly.undertow.customfilter.ResponseTimeHandler"/>
    </filters>
</subsystem>
...

Module Version

If the module has been deployed in the "main" slot, no version information is required when configuring the filter. Otherwise, the version information can be specified after the module name and the ":" separator: module="com.novaordis.playground.wildfly.undertow.customfilter:1.0".

Specify Optional Filter Parameters

The configuration syntax allows for optional parameters, which are declared as follows:

      <filter name="..." ...>
         <param name="param1" value="value1">
         ...
     </filter>

Declare a Reference to the Filter for a Specific Host

...
<subsystem xmlns="urn:jboss:domain:undertow:3.0">
   <server name="...">
      ...
      <host name="...">
           <location .../>
           ...
           <filter-ref name="response-time"/>
           ...
      </host>
   </server>
   ...
</subsystem>
...