Experiences with migrating from JBoss AS 7 to WildFly 8.1

24 June 2014, by: Arjan Tijms

This month the first major update of WildFly 8 (the base for a future JBoss EAP 7) was released; WildFly 8.1.0.Final.

I tried to get zeef.com running on it, which currently runs on JBoss AS 7. Zeef.com is a relatively new Java EE 6 based web application that was started about a year ago. As such there is not yet that much legacy code present in it.

This article is about the issues I encountered during this initial migration.

In broad lines the issues fell into the following categories:

    Datasource
  • JASPIC
  • Tomcat/Undertow differences
  • Valves

Datasource

The first issue I run into was a failed deployment. WildFly spitted out page upon page of unintelligible mumble jumble. Upon a closer look there was something about our datasource in the middle of all of this. Apparently WildFly was trying to tell me that the datasource couldn’t be found. For zeef.com we define our datasource in application.xml (the Java EE standard way) and switch between stages uses a delegating switcheable datasource.

Moving the datasource definition to ejb-jar.xml solved the problem. Now WildFly 8.1 does support the definition of datasources in application.xml as demonstrated by this test (although a small workaround is needed). It might be an issue with loading the SwitchableXADataSource from the EAR level, but I didn’t investigate this further.

JASPIC

The next problem concerned an amount of issues related to JASPIC, the Java EE standard authentication API. JASPIC is an important but troublesome spec. It’s suspected that its TCK is very light, as (preview) implementations that don’t actually work (yet) have been certified.

The WildFly team however has done a fair amount of work to make sure its JASPIC implementation is reasonably correct, among others by using an external set of tests designed to verify that JASPIC does the most basic things right (like actually authenticating). Unfortunately one case slipped through, and that mainly concerned the behavior of HttpServletRequest#authenticate.

Specifically the following issues occured:

  • authenticate() does nothing and closes response (UNDERTOW-263)
  • authenticate() closes response when no authentication happened (UNDERTOW-259)
  • HttpServletRequest#logout doesn’t fully clear security context (WFLY-4602)
  • NullPointerExceptions right after request processing (WFLY-3514)
  • NullPointerExceptions for requests after a session is created (WFLY-3518

(update: UNDERTOW-259 and WFLY-3514 are fixed in WildFly 8.2, UNDERTOW-263, WFLY-4602 and WFLY-3518 are fixed in WildFly 9.0)

The first two issues could be worked around by installing an HttpServletRequestWrapper, e.g. via an Undertow Handler as follows:

public class JaspicHandler implements HttpHandler {
 
	private Field authenticationStateField; 
	private HttpHandler next;
 
	public JaspicHandler(HttpHandler next) {
		this.next = next;
		try {
			authenticationStateField = SecurityContextImpl.class.getDeclaredField("authenticationState");
			authenticationStateField.setAccessible(true);
		} catch (NoSuchFieldException | SecurityException e) {
			throw new RuntimeException(e);
		}
	}
 
	@Override
	public void handleRequest(final HttpServerExchange exchange) throws Exception {
 
		ServletRequestContext context =	exchange.getAttachment(ATTACHMENT_KEY);
		if (context != null) {
			ServletRequest request = context.getServletRequest();
 
			HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper((HttpServletRequest) request) {
				@Override
				public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
					if (response.isCommitted()) {
						throw MESSAGES.responseAlreadyCommited();
					}
 
					SecurityContext securityContext = exchange.getSecurityContext();
					securityContext.setAuthenticationRequired();
 
					if (securityContext instanceof SecurityContextImpl) {
						SecurityContextImpl securityContextImpl = (SecurityContextImpl) securityContext;
						try {
							// Perform the actual reset of the authentication state
							authenticationStateField.set(securityContextImpl, authenticationStateField.get(new SecurityContextImpl(null, null)));
						} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
							throw new RuntimeException(e);
						}
					}
 
					if (securityContext.authenticate()) {
						if (securityContext.isAuthenticated()) {
							return true;
						} else {
							throw MESSAGES.authenticationFailed();
						}
					} else {
						// Just return false. The original method for some reason closes the stream here.
						 // see https://issues.jboss.org/browse/UNDERTOW-259
						return false;
					}
				}
			};
 
			context.setServletRequest(wrapper);
		}
 
		next.handleRequest(exchange);
	}
 
}

And then register this as an innerHandler:

public class UndertowHandlerExtension implements ServletExtension {
    @Override
    public void handleDeployment(final DeploymentInfo deploymentInfo, final ServletContext servletContext) {
        deploymentInfo.addInnerHandlerChainWrapper(handler -> new JaspicHandler(handler)); 
    }
}

The handler extension itself has to be registered by putting its fully qualified class name in /META-INF/services/io.undertow.servlet.ServletExtension.

For the other two issues JASPIAuthenticationMechanism had to be patched by inserting a simple guard around obtaining the so-called “cached account”:

cachedAccount = authSession == null? null : authSession.getAccount();

and by inserting another guard in secureResponse to check if a wrapper hadn’t been installed before:

ServletRequest request = exchange.getAttachment(ATTACHMENT_KEY).getServletRequest();
 
if (!TRUE.equals(request.getAttribute("JASPIAuthenticationMechanism.secureResponse.installed"))) {
    request.setAttribute("JASPIAuthenticationMechanism.secureResponse.installed", TRUE);
    // original code
}

(the fix for this last issue was committed rather fast by WildFly developers and fixes it in a better way)

Tomcat/Undertow differences

The next problems were about (small) differences between Tomcat/JBossWeb that JBoss previously used and the new Undertow that’s used in WildFly 8.

The first of those issues (UNDERTOW-348) are about what HttpServletRequest#getRequestURI and HttpServletRequest#getServletPath return when a welcome file is requested. Tomcat will return the requested location for getRequestURI and the welcome file resource for getServletPath, while Undertow will return the welcome file resource for both calls.

E.g. with a welcome file declaration in web.xml as follows:

<welcome-file-list>
    <welcome-file>index</welcome-file>
</welcome-file-list>

and when requesting the context root of an application (e.g. http://localhost:8080 for a root deployment), the results are as follows:

getRequestURI getServletPath
Tomcat/JBossWeb / /welcome
Undertow /welcome /welcome

The information about the requested resource can be used to redirect the user to the root (“/”) when for some reason the welcome file resource is directly requested. With such a redirect the website will always display a ‘clean’ URL in the address bar. With the way Undertow does things there’s no way to distinguish a request to “/” from a request to “/welcome” and thus no opportunity to redirect. If a redirect was already in place based on the Tomcat/JBossWeb behavior an endless redirect loop will be the result.

A workaround here is to create another Undertow handler as follows:

public class RequestURIHandler implements HttpHandler {
 
    private HttpHandler next;
 
    public RequestURIHandler(HttpHandler next) {
        this.next = next;
    }
 
    @Override
    public void handleRequest(final HttpServerExchange exchange) throws Exception {
 
        String requestURI = exchange.getRequestURI();
 
        next.handleRequest(exchange);
 
        exchange.setRequestURI(requestURI);
    }
}

This handler too has to be registered like we did for the JaspicHandler, but this time as an initialHandler.

(update: The behavior of welcome files was changed back to how it was in WildFly 9.0)

Another difference (likely a bug) between JBossWeb and Undertow is that for a root deployment the former will write the JSESSIONID cookie with the path set to “/”. Undertow however leaves the path empty.

An empty path is however interpreted by the browser as being the requested URI, meaning that the JSESSIONID cookie is set on each path where the user happens to do something that creates a session. It doesn’t require much imagination to understand this causes chaos in an application.

As it appears the fact that APIs for getting the context root often return the empty string for the root deployment (instead of “/”), but the path name for other deployments (e.g. “/foo”) is the culprit here. Undertow is not the first one to fall for this. Mojarra once had an identical bug with respect to setting a cookie for The Flash.

We can workaround this issue by setting the path explicitly in web.xml as follows:

<session-config>
    <cookie-config>
        <path>/</path>
        <http-only>true</http-only>
        <secure/>
    </cookie-config>
    <tracking-mode>COOKIE</tracking-mode>
</session-config>

(update: Cookies for a root deployment are fixed in WildFly 8.2)

Valves

Tomcat (and thus JBossWeb) has a low level mechanism called a Valve, which is a kind of Filter like element but at a much lower level and with access to some of the internal server APIs.

Ideally an application wouldn’t have to resort to using these, but sometimes there’s no choice. For instance a Filter can not change the outcome of the Servlet pipeline. It’s already fully established when the Filter is called. A Filter can redirect or forward, but these mechanisms have various side-effects. There’s also no mechanism in Servlet to intercept ALL cookies being written. By wrapping the HttpServletResponse you can catch a lot, but not these emitted by lower level components like the server generated JSESSIONID and cookies set by e.g. a SAM.

For zeef.com we had to resort to use a few of these. As Valves are highly specific to Tomcat it’s only logical that Undertow can’t support them. It does have another construct called HttpHandler, which we already used above for some workarounds.

Those handlers are quite powerful. There are ones that are called before the request pipeline is set up (initial handlers) and ones that execute during this pipeline (inner and outer handlers).

One of the things where we used a Valve for in Tomcat/JBossWeb is to do universal cookie rewriting. Unfortunately Undertow doesn’t have a way to rewrite a cookie right away. There is an opportunity to have a kind of listener called right before the response is being written, but it’s a bit non-obvious.

Via a handler and some reflective code we can do this more directly, as shown by the following code:

public class CookieRewriteHandler implements HttpHandler {
 
    private Field responseCookiesField;
    private final HttpHandler next;
 
    public CookieRewriteHandler(HttpHandler next) {
 
        this.next = next;
 
        try {
            responseCookiesField = HttpServerExchange.class.getDeclaredField("responseCookies");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new RuntimeException(e);
        }
        responseCookiesField.setAccessible(true);
    }
 
    @Override
    public void handleRequest(final HttpServerExchange exchange) throws Exception {
        // Overwrite the map used to store cookies internally so we can intercept
        // the cookies being written.
 
        // Alternatively: there's a wrapper handler called just before the response
        // is being written. We could use that to iterate over the cookie map.
        responseCookiesField.set(exchange, new HashMap<String, Cookie>() {
 
            private static final long serialVersionUID = 1L;
 
            @Override
            public Cookie put(String key, Cookie value) {
                // *****************************
                // rewrite cookie here as needed
                // *****************************
                return super.put(key, value);
            }
 
        });
 
        next.handleRequest(exchange);
    }
}

Of course using reflection to hack into the internals of a server is rarely a good idea and this handler is at risk of breaking with every minor update to Undertow.

Conclusion

During the initial attempt to get our application running on WildFly I certainly encountered a fair number of issues. It certainly wasn’t the case of just deploying the app and having everything working.

Of course we do have to realize that the Java EE standard datasource and authentication mechanism are unfortunately still not used that much as most vendors keep documenting their proprietary mechanisms first and foremost, which likely causes users to use those most, which on its turn may cause these standard ways to get less testing hours.

The Tomcat/WildFly differences initially looked major, but are after all just small bugs.

The Valves issue is debatable. It’s a major change in the product JBoss, but most Java EE applications should maybe not have used them in the first place. Purely from the point of view of the Java EE spec it doesn’t matter that Red Hat changed this, but looking from the point of view of JBoss itself it does matter and people have little choice but to rewrite their code if they want to upgrade.

Finally we have to realize that WildFly from a certain point of view is like an open beta. It has freely downloadable binaries, but the product is early in its lifecycle and there’s no commercial support available for it yet. When the WildFly branch transitions to JBoss EAP 7 many bugs that the community discovers now will undoubtedly have been fixed and commercial support will be available then. In a way this is the price we pay for a free product such as this. As David Blevins wrote “Open Source Isn’t Free”. Users “pay” by testing the software and producing bug reports and perhaps patches, which IMHO is a pretty good deal.

At any length there’s the eternal tradeoff to be made: adopt the new Java EE 7 spec early with WildFly 8 but be prepared to run into some issues, or wait a good deal longer for JBoss EAP 7 but then have a much more stable product to begin with. This is of course a choice everyone has to make for themselves.

Arjan Tijms

2 comments to “Experiences with migrating from JBoss AS 7 to WildFly 8.1”

  1. Dimitris Andreadis says:

    Excellent article, thanks Arjan!

    Your input will definitely help to iron out those issues

  2. Ntanh says:

    Helpful for me. Thanks alot

Type your comment below:

Time limit is exhausted. Please reload CAPTCHA.

css.php best counter