Chapter 10. Undertow
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 4.0 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 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 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 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 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 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: Using WEB-INF/jboss-web.xml file In the WEB-INF/undertow-handlers.conf 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 ; public MyHttpHandler(HttpHandler ) { this. = ; } public void handleRequest(HttpServerExchange exchange) throws Exception { // do something .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 ; private String myParam; public MyHttpHandler(HttpHandler ) { this. = ; } public void setMyParam(String myParam) { this.myParam = myParam; } public void handleRequest(HttpServerExchange exchange) throws Exception { // do something, use myParam .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. 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 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; } }; } } 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 . 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. Important 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 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 the HttpAuthenticationFactory 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 named org.wildfly.security.http.HttpServerAuthenticationMechanismFactory should be added under META-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: Additional resources For more information see modules and dependencies . Using a Custom HTTP Mechanism Add a custom module. Add an http-authentication-factory to tie the mechanism factory to a security-domain that will be used for the authentication. Update the application-security-domain resource to use the new http-authentication-factory . Note When 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. The application-security-domain resource can now be updated to use the new http-authentication-factory . 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
public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.isInIoThread()) { exchange.dispatch(this); return; } //handler code } allowed-methods(methods='GET') path('/my-path') -> allowed-methods(methods='GET') path('/a') -> redirect('/b') <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> <jboss-web> <http-handler> <class-name>org.jboss.example.MyHttpHandler</class-name> </http-handler> </jboss-web> 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); } } <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> 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); } } myHttpHandler(myParam='foobar') 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; } }; } } org.jboss.example.MyHandlerBuilder module add --name=org.wildfly.security.examples.custom-http --resources=/path/to/custom-http-mechanism.jar --dependencies=org.wildfly.security.elytron,javax.api /subsystem=elytron/service-loader-http-server-mechanism-factory=custom-factory:add(module=org.wildfly.security.examples.custom-http) /subsystem=elytron/http-authentication-factory=custom-mechanism:add(http-server-mechanism-factory=custom-factory,security-domain=ApplicationDomain,mechanism-configurations=[{mechanism-name=custom-mechanism}]) /subsystem=undertow/application-security-domain=other:add(http-authentication-factory=application-http-authentication) /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) reload