Virtual hosting is a method which allows a single server (or multiple) to listen on a single port (most often the default port 80
) and serve different content based on the host / domain name. This is is commonly used in load balancers or web servers such as NGINX or apache to serve several websites from a single server. In its simplest form virtual hosting is a pair of a port and hostname that map to a handler.
Virtual Hosting in Undertow
One of the most common virtual hosting use cases is redirecting a non www domain to a www domain. Creating custom HttpHandler's in Undertow is easy. Undertow also has a built in virtual hosting handler called NameVirtualHostHandler
.
Routes
A simple hello world route and a route to redirect to the given hostname. We are not just redirect blindly to the new host we are capturing the full path and any query params as well. This is made easy using some OkHttp
classes.
// Simple hello world
public static void hello(HttpServerExchange exchange) {
Exchange.body().sendText(exchange, "Hello World");
}
/*
* Using our utility to get the current url redirect
* to the given host keeping paths and query strings.
*/
public static HttpHandler redirectToHost(String host) {
return exchange -> {
HttpUrl url = Exchange.urls().currentUrl(exchange);
Exchange.redirect().permanent(exchange, url.newBuilder().host(host).build().toString());
};
}
private static final HttpHandler ROUTES = new RoutingHandler()
.get("/hello", RedirectServer::hello)
.setFallbackHandler(RoutingHandlers::notFoundHandler)
;
Get Current URL
Here is our simple util method to grab the current HttpUrl
. We use OkHttp's
library here because they have a very nice api for working with urls.
default HttpUrl currentUrl(HttpServerExchange exchange) {
HttpUrl.Builder urlBuilder = HttpUrl.parse(exchange.getRequestURL()).newBuilder();
if (!"".equals(exchange.getQueryString())) {
urlBuilder = urlBuilder.encodedQuery(exchange.getQueryString());
}
return urlBuilder.build();
}
Adding Handlers to NameVirtualHostHandler
Here we add two virtual hosting handlers. One on localhost which should redirect to www.localhost just as an example (This will work in some browsers but not with cURL
). The second handler is our actual web content.
public static void main(String[] args) {
NameVirtualHostHandler handler = Handlers.virtualHost()
.addHost("localhost", VirtualHostServer.redirectToHost("www.localhost"))
.addHost("www.localhost", ROUTES);
SimpleServer server = SimpleServer.simpleServer(handler);
server.start();
}
Testing it out
curl -v -L localhost:8080
* Rebuilt URL to: localhost:8080/
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Connection: keep-alive
< Location: http://www.localhost:8080/
< Content-Length: 0
< Date: Thu, 20 Apr 2017 12:05:02 GMT
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://www.localhost:8080/'
* Could not resolve host: www.localhost
* Closing connection 1
curl: (6) Could not resolve host: www.localhost
Now cURL
won't resolve www.localhost unless we add it to our hosts file however Chrome and some other browsers will. This is just an example and would generally use a real domain instead of localhost. Let's also make sure we preserve the path and query params.
curl -v -L localhost:8080/hello?123
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /hello?123 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Connection: keep-alive
< Location: http://www.localhost:8080/hello?123
< Content-Length: 0
< Date: Thu, 20 Apr 2017 12:07:16 GMT
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://www.localhost:8080/hello?123'
* Could not resolve host: www.localhost
* Closing connection 1
curl: (6) Could not resolve host: www.localhost