/*
* Copyright 2016 Hammock and its contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ws.ament.hammock.web.undertow;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.PathHandler;
import io.undertow.server.handlers.resource.ClassPathResourceManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.*;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import ws.ament.hammock.HammockRuntime;
import ws.ament.hammock.web.api.ServletDescriptor;
import ws.ament.hammock.web.base.AbstractWebServer;
import ws.ament.hammock.web.spi.WebServerConfiguration;
import ws.ament.hammock.web.undertow.websocket.UndertowWebSocketExtension;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.net.ssl.*;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import static io.undertow.Handlers.path;
import static io.undertow.servlet.Servlets.filter;
import static io.undertow.servlet.Servlets.listener;
@ApplicationScoped
public class UndertowWebServer extends AbstractWebServer {
@Inject
private Function<ServletDescriptor, ServletInfo> mapper;
@Inject
private UndertowWebSocketExtension extension;
@Inject
private WebServerConfiguration webServerConfiguration;
@Inject
private HammockRuntime hammockRuntime;
private Set<ServletInfo> servlets = new HashSet<>();
private Undertow undertow;
@Override
public void addServlet(ServletDescriptor servletDescriptor) {
servlets.add(mapper.apply(servletDescriptor));
}
@Override
public void start() {
DeploymentInfo di = new DeploymentInfo()
.setContextPath("/")
.setDeploymentName("Undertow")
.setResourceManager(new ClassPathResourceManager(getClass().getClassLoader()))
.setClassLoader(ClassLoader.getSystemClassLoader());
super.getListeners().forEach(c ->di.addListener(listener(c)));
Collection<Class<?>> endpoints = extension.getEndpointClasses();
if(!endpoints.isEmpty()) {
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
endpoints.forEach(webSocketDeploymentInfo::addEndpoint);
di.addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, webSocketDeploymentInfo);
}
getServletContextAttributes().forEach(di::addServletContextAttribute);
servlets.forEach(di::addServlet);
getFilterDescriptors().forEach(filterDescriptor -> {
FilterInfo filterInfo = filter(filterDescriptor.displayName(), filterDescriptor.getClazz()).setAsyncSupported(filterDescriptor.asyncSupported());
if(filterDescriptor.initParams() != null) {
for (WebInitParam param : filterDescriptor.initParams()) {
filterInfo.addInitParam(param.name(), param.value());
}
}
di.addFilter(filterInfo);
for(String url : filterDescriptor.urlPatterns()) {
for(DispatcherType dispatcherType : filterDescriptor.dispatcherTypes()) {
di.addFilterUrlMapping(filterDescriptor.displayName(), url, dispatcherType);
}
}
});
getInitParams().forEach(di::addInitParameter);
DeploymentManager deploymentManager = Servlets.defaultContainer().addDeployment(di);
deploymentManager.deploy();
try {
HttpHandler servletHandler = deploymentManager.start();
PathHandler path = path(Handlers.redirect("/"))
.addPrefixPath("/", servletHandler);
Builder undertowBuilder = Undertow.builder()
.addHttpListener(webServerConfiguration.getPort(), webServerConfiguration.getAddress())
.setHandler(path);
if (hammockRuntime.isSecuredConfigured()){
KeyManager[] keyManagers = loadKeyManager();
TrustManager[] trustManagers = loadTrustManager();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
undertowBuilder = undertowBuilder.addHttpsListener(webServerConfiguration.getSecuredPort(), webServerConfiguration.getAddress(), sslContext);
}
this.undertow = undertowBuilder.build();
this.undertow.start();
} catch (ServletException | GeneralSecurityException | IOException e) {
throw new RuntimeException("Unable to start server", e);
}
}
private KeyManager[] loadKeyManager() throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(loadKeystore(webServerConfiguration.getKeystorePath(),
webServerConfiguration.getKeystorePassword(),
webServerConfiguration.getKeystoreType()),
webServerConfiguration.getKeystorePassword().toCharArray());
return keyManagerFactory.getKeyManagers();
}
private TrustManager[] loadTrustManager() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(loadKeystore(webServerConfiguration.getTruststorePath(),
webServerConfiguration.getTruststorePassword(),
webServerConfiguration.getTruststoreType()));
return trustManagerFactory.getTrustManagers();
}
private KeyStore loadKeystore(String name, String password, String type) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
final InputStream stream = UndertowWebServer.class.getResourceAsStream(name);
if (stream == null){
throw new RuntimeException("Unable to start server, path " + name + " doesn't exists");
}
try(InputStream is = stream) {
KeyStore loadedKeystore = KeyStore.getInstance(type);
loadedKeystore.load(is, password.toCharArray());
return loadedKeystore;
}
}
@Override
@PreDestroy
public void stop() {
if(this.undertow != null) {
this.undertow.stop();
this.undertow = null;
}
}
}