Undertow Web Server Tutorial
Jakob Jenkov |
Undertow web server is an open source minimalistic Java web server sponsored by Red Hat. Undertow is used inside the JBoss Wildfly application server. Undertow provides both blocking and non-blocking APIs based on Java NIO, and has a compositional design that enables you create your own custom web server by combining special purpose request handlers along with your own custom request handlers.
Undertow is designed to be embeddable, meaning you can create and start an Undertow web server from inside your own Java application. You could actually start multiple Undertow web servers from inside a single Java application.
By minimalistic I mean that it is possible to compile your customized Java application with the embedded Undertow web server - including its JAR file dependencies - into a single standalone fat jar as small as 5-6 MB.
I have used Undertow for several years to run several websites, and it has worked quite well so far.
Undertow Website
You can find Undertow at the Undertow website, here:
Undertow Examples GitHub Repository
I have published many of the Undertow examples from this tutorial in a GitHub repository so you can see the code in its entirety. Each example tends to be small and isolated, using a minimum of external classes - to keep the code simple for you to read. Here is the Undertow examples GitHub repository:
https://github.com/jjenkov/undertow-examples
Undertow Maven Dependencies
To use Undertow in your Java project you must include the Undertow Maven dependencies in your Maven pom.xml file. Here is how the core Undertow Maven dependency looks:
<dependency> <groupId>io.undertow</groupId> <artifactId>undertow-core</artifactId> <version>2.1.0.Final</version> </dependency>
Undertow also has a few extra JAR files you can use, if you need the extra functionality they provide. These Undertow JAR files are:
<dependency> <groupId>io.undertow</groupId> <artifactId>undertow-servlet</artifactId> <version>2.1.0.Final</version> </dependency>
<dependency> <groupId>io.undertow</groupId> <artifactId>undertow-websockets-jsr</artifactId> <version>2.1.0.Final</version> </dependency>
Undertow Overview
When you use Undertow you create an Undertow server. This server listens on a TCP port for incoming HTTP requests. All incoming HTTP requests are forwarded to a Handler which you set on the Undertow server. How this looks is shown in the next section.
Simple Undertow Application
Here is a simple application that creates an Undertow web server and starts it up:
import io.undertow.Undertow; import java.io.IOException; public class UndertowHttpServer { public static void main(String[] args) throws IOException { System.out.println("Building Undertow server"); WebHandler webHandler = new WebHandler(); Undertow.Builder builder = Undertow.builder(); Undertow undertow = builder .addHttpListener(80, "localhost") .setHandler(webHandler) .build(); System.out.println("Undertow started"); undertow.start(); } }
Notice the Undertow.Builder class that is used to build the Undertow server instance. Also notice the WebHandler which is the component that receives all the incoming HTTP requests. See the next section for an example of a Handler implementation.
Simple Undertow Handler
Here is a simple Undertow handler which receives a request and sends a simple, hardcoded response back:
import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; public class WebHandler implements HttpHandler { @Override public void handleRequest(HttpServerExchange httpServerExchange) throws Exception { httpServerExchange .getResponseSender() .send("<!DOCTYPE html<>html<>body<>h1<Hello World from Undertow>/h1<>/body<>/html>"); } }HTTP Request URI
HTTP Request URI
You can access the HTTP Request URI via the HttpServerExchange
object passed as parameter
to the HttpHandler
's handleRequest()
method. Here is an example of accessing
the HTTP Request URI:
String uri = httpServerExchange.getRequestURI();
HTTP Request Headers
You can access the HTTP request headers such as the Accept header via the HttpServerExchange
object passed as parameter to the HttpHandler
's handleRequest()
method.
Here is an example of accessing the Accept HTTP request header:
import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderValues; public class HttpHeadersHandler implements HttpHandler { @Override public void handleRequest(HttpServerExchange httpServerExchange) throws Exception { //obtain header values for the Accept HTTP header HeaderValues acceptHeader = httpServerExchange.getRequestHeaders().get("Accept"); System.out.println(""); System.out.println("URI: " + httpServerExchange.getRequestURI()); System.out.println("Accept header values: " + acceptHeader.size()); //print each value out to System.out for(int i=0; i < acceptHeader.size(); i++) { System.out.println("" + i + " : " + acceptHeader.get(i)); } httpServerExchange .getResponseSender() .send("<!DOCTYPE html><html><body><h1>HTTP Accept Headers Printed to System.out</h1></body></html>"); } }
String uri = httpServerExchange.getRequestURI();
HTTP Response Headers
It is possible to set the HTTP response headers too, via the HttpServerExchange
object
passed as parameter to the HttpHandler
's handleRequest()
method.
Here is an example of setting the Content-Type
HTTP response header:
String mimeType = "text/html"; httpServerExchange .getResponseHeaders() .put(HttpString.tryFromString("Content-Type"), mimeType);
HTTP 404 Not Found Handler
It is possible to send an HTTP 404 URI Not Found error code back to the browser, in case your Undertow service cannot find any content matching the incoming URI + parameters. Here is a NotFound404Handler that shows how simple it is to send back an HTTP 404 code:
import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; public class NotFound404Handler implements HttpHandler { @Override public void handleRequest(HttpServerExchange httpServerExchange) throws Exception { httpServerExchange.setStatusCode(404); httpServerExchange.getResponseSender() .send("URI not found: " + httpServerExchange.getRequestURI()); } }
Routing Undertow Handler
As mentioned earlier, the Undertow takes a simple Handler instance which receives all the incoming HTTP requests to the Undertow server. In order to route the incoming requests to different Handler instances based on the URI of the HTTP request, you have to implement a routing Handler yourself.
Here is a simple example of an Undertow Handler which looks at the URI of the incoming HTTP request, and routes the HTTP request to a Handler matching that URI, by looking up the Handler by URI in a Map:
import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import java.util.HashMap; import java.util.Map; public class RoutingHandler implements HttpHandler { private Map<String, HttpHandler> handlerMap = new HashMap<>(); public void addHandler(String uri, HttpHandler handler) { this.handlerMap.put(uri, handler); } @Override public void handleRequest(HttpServerExchange httpServerExchange) throws Exception { String uri = httpServerExchange.getRequestURI(); HttpHandler handler = this.handlerMap.get(uri); if(handler != null) { handler.handleRequest(httpServerExchange); } // else - send back an HTTP 404. See a later example. } }
File Handler
It is of course possible to send back static content from an Undertow handler. Most websites have some amount of static content typically stored in files. Here is an Undertow file handler capable of translating the request URI to a file path in the local file system, load the corresponding file and return it to the browser.
import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import java.io.File; import java.nio.ByteBuffer; public class FileHandler implements HttpHandler { private String baseDirPath = null; public FileHandler(String baseDirPath) { this.baseDirPath = baseDirPath; } @Override public void handleRequest(HttpServerExchange httpServerExchange) throws Exception { String fileUri = httpServerExchange.getRequestURI(); String mimetype = MimeTypeDetector.detectMimeType(fileUri); String filePath = (this.baseDirPath + fileUri).replace('/', File.separatorChar); try{ byte[] bytes = FileUtil.loadFile(filePath); //Perhaps allocating a new ByteBuffer for every request is not optimal. // A single instance could be reused, although it then has to be big enough to hold all files. ByteBuffer buffer = ByteBuffer.allocate(bytes.length); buffer.clear(); buffer.put(bytes); buffer.flip(); httpServerExchange.getResponseHeaders().put(Headers.CONTENT_TYPE, mimetype); httpServerExchange.getResponseSender().send(buffer); httpServerExchange.endExchange(); } catch(Throwable t) { t.printStackTrace(); } } }
Tweet | |
Jakob Jenkov |