All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.rwh.utils.jetty.JettyServer Maven / Gradle / Ivy

Go to download

Utility library for running an embedded Jetty with service provider based initialization of Spring and Jersey

The newest version!
package de.rwh.utils.jetty;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConfiguration.Customizer;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.rwh.utils.crypto.CertificateChecker;
import de.rwh.utils.crypto.CertificateCheckerImpl;
import de.rwh.utils.crypto.CertificateHelper;
import de.rwh.utils.crypto.io.CertificateReader;

public class JettyServer extends Server
{
	private static final String PROPERTY_JETTY_HOST = "jetty.host";
	private static final String PROPERTY_JETTY_HOST_DEFAULT = "localhost";
	private static final String PROPERTY_JETTY_PORT = "jetty.port";
	private static final String PROPERTY_JETTY_PORT_HTTP_DEFAULT = "8080";
	private static final String PROPERTY_JETTY_PORT_HTTPS_DEFAULT = "8443";

	private static final String PROPERTY_JETTY_TRUSTSTORE_PEM = "jetty.truststore.pem";
	private static final String PROPERTY_JETTY_KEYSTORE_P12 = "jetty.keystore.p12";
	private static final String PROPERTY_JETTY_KEYSTORE_PASSWORD = "jetty.keystore.password";
	private static final String PROPERTY_JETTY_NEEDCLIENTAUTH = "jetty.needclientauth";
	private static final String PROPERTY_JETTY_NEEDCLIENTAUTH_DEFAULT = "false";
	private static final String PROPERTY_JETTY_CLIENT_CERT_HEADER = "jetty.clientcertheader";
	private static final String PROPERTY_JETTY_CLIENT_CERT_HEADER_DEFAULT = "X-ClientCert";

	private static final Logger logger = LoggerFactory.getLogger(JettyServer.class);

	public static Function httpsConnector(HttpConfiguration httpConfiguration,
			Properties properties)
	{
		try
		{
			String httpsHost = properties.getProperty(PROPERTY_JETTY_HOST, PROPERTY_JETTY_HOST_DEFAULT);
			int httpsPort = Integer
					.parseInt(properties.getProperty(PROPERTY_JETTY_PORT, PROPERTY_JETTY_PORT_HTTPS_DEFAULT));

			Path trustStorePath = Paths.get(properties.getProperty(PROPERTY_JETTY_TRUSTSTORE_PEM));
			Path keyStorePath = Paths.get(properties.getProperty(PROPERTY_JETTY_KEYSTORE_P12));

			char[] keyStorePassword = toCharArray(properties.getProperty(PROPERTY_JETTY_KEYSTORE_PASSWORD));
			boolean needClientAuth = Boolean.parseBoolean(
					properties.getProperty(PROPERTY_JETTY_NEEDCLIENTAUTH, PROPERTY_JETTY_NEEDCLIENTAUTH_DEFAULT));

			KeyStore trustStore = CertificateReader.allFromCer(trustStorePath);
			KeyStore keyStore = CertificateReader.fromPkcs12(keyStorePath, keyStorePassword);

			checkServerCert(trustStore, keyStore);

			return httpsConnector(httpConfiguration, httpsHost, httpsPort, trustStore, keyStore, keyStorePassword,
					needClientAuth);
		}
		catch (NumberFormatException | NoSuchAlgorithmException | CertificateException | KeyStoreException
				| IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	private static char[] toCharArray(String password)
	{
		return password == null ? null : password.toCharArray();
	}

	private static void checkServerCert(KeyStore trustStore, KeyStore keyStore) throws KeyStoreException
	{
		CertificateChecker cc = new CertificateCheckerImpl(
				Executors.newScheduledThreadPool(1, r -> new Thread(r, "LoggerScheduledExecutorServiceJetty")));

		for (Enumeration aliases = keyStore.aliases(); aliases.hasMoreElements();)
		{
			String alias = aliases.nextElement();
			Certificate certificate = keyStore.getCertificate(alias);
			if (certificate instanceof X509Certificate)
				cc.checkServerCertificateAndScheduleWarning(trustStore, (X509Certificate) certificate);
		}
	}

	private static void logCertificateConfig(int httpsPort, KeyStore trustStore, KeyStore keyStore)
	{
		if (!logger.isDebugEnabled())
			return;

		try
		{
			logger.debug("Using TrustStore for https connector {} with: {}", httpsPort,
					CertificateHelper.listCertificateSubjectNames(trustStore));
			logger.debug("Using KeyStore for https connector {} with: {}", httpsPort,
					CertificateHelper.listCertificateSubjectNames(keyStore));
		}
		catch (KeyStoreException e)
		{
			logger.warn("Error while printing TrustStore/KeyStore config", e);
		}
	}

	public static Function httpsConnector(HttpConfiguration httpConfiguration,
			String httpsHost, int httpsPort, KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword,
			boolean needClientAuth)
	{
		return server ->
		{
			logCertificateConfig(httpsPort, trustStore, keyStore);

			SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
			sslContextFactory.setTrustStore(trustStore);
			sslContextFactory.setKeyStore(keyStore);
			sslContextFactory.setKeyStorePassword(String.valueOf(keyStorePassword));
			sslContextFactory.setNeedClientAuth(needClientAuth);

			SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory,
					HttpVersion.HTTP_1_1.asString());

			HttpConnectionFactory connectionFactory = new HttpConnectionFactory(httpConfiguration);

			ServerConnector tlsConnector = new ServerConnector(server, sslConnectionFactory, connectionFactory);
			tlsConnector.setHost(httpsHost);
			tlsConnector.setPort(httpsPort);

			return tlsConnector;
		};
	}

	public static Function httpConnector(HttpConfiguration httpConfiguration,
			Properties properties)
	{
		String httpHost = properties.getProperty(PROPERTY_JETTY_HOST, PROPERTY_JETTY_HOST_DEFAULT);
		int httpPort = Integer.parseInt(properties.getProperty(PROPERTY_JETTY_PORT, PROPERTY_JETTY_PORT_HTTP_DEFAULT));

		return httpConnector(httpConfiguration, httpHost, httpPort);
	}

	public static Function httpConnector(HttpConfiguration httpConfiguration, String httpHost,
			int httpPort)
	{
		return server ->
		{
			HttpConnectionFactory connectionFactory = new HttpConnectionFactory(httpConfiguration);

			ServerConnector connector = new ServerConnector(server, connectionFactory);
			connector.setHost(httpHost);
			connector.setPort(httpPort);

			return connector;
		};
	}

	public static HttpConfiguration httpConfiguration()
	{
		return httpConfiguration(null);
	}

	public static HttpConfiguration httpConfiguration(Customizer customizer)
	{
		HttpConfiguration configuration = new HttpConfiguration();
		configuration.setSendServerVersion(false);
		configuration.setSendXPoweredBy(false);
		configuration.setSendDateHeader(false);

		if (customizer != null)
			configuration.addCustomizer(customizer);

		return configuration;
	}

	public static SecureRequestCustomizer secureRequestCustomizer(Properties properties)
	{
		return new SecureRequestCustomizer();
	}

	public static ForwardedSecureRequestCustomizer forwardedSecureRequestCustomizer(Properties properties)
	{
		String clientCertHeaderName = properties.getProperty(PROPERTY_JETTY_CLIENT_CERT_HEADER,
				PROPERTY_JETTY_CLIENT_CERT_HEADER_DEFAULT);
		return new ForwardedSecureRequestCustomizer(clientCertHeaderName);
	}

	public static Stream webInfJars(Predicate filter)
	{
		return classPathEntries().filter(e -> e.endsWith(".jar")).filter(filter);
	}

	public static Stream webInfClassesDirs(Predicate filter)
	{
		return classPathEntries().filter(e -> e.contains("classes") || e.contains("test-classes")).filter(filter);
	}

	public static Stream classPathEntries()
	{
		return Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator));
	}

	public static String pathStringFromURI(String uriString)
	{
		try
		{
			URI uri = new URI(uriString);
			return uri.getScheme() == null ? Paths.get(uriString).toString() : Paths.get(uri).toString();
		}
		catch (URISyntaxException e)
		{
			throw new RuntimeException(e);
		}
	}

	public static ErrorHandler defaultErrorHandler()
	{
		return new ErrorHandler();
	}

	public static ErrorHandler statusCodeOnlyErrorHandler()
	{
		return new ErrorHandler()
		{
			protected void writeErrorPage(javax.servlet.http.HttpServletRequest request, java.io.Writer writer,
					int code, String message, boolean showStacks) throws IOException
			{
			}
		};
	}

	private final Context servletContext;
	private final WebAppContext webAppContext;

	@SafeVarargs
	public JettyServer(Function connector, ErrorHandler errorHandler, String contextPath,
			List> initializers, Properties initParameter, Stream webInfClassesDirs,
			Stream webInfJars, Class... additionalFilters)
	{
		this(Collections.singletonList(connector), errorHandler, contextPath, initializers, initParameter,
				webInfClassesDirs, webInfJars, additionalFilters);
	}

	@SafeVarargs
	public JettyServer(List> connectors, ErrorHandler errorHandler,
			String contextPath, List> initializers, Properties initParameter, Stream webInfClassesDirs,
			Stream webInfJars, Class... additionalFilters)
	{
		WebAppContext context = new WebAppContext();
		context.setLogUrlOnStart(true);
		context.setThrowUnavailableOnStartupException(true);

		if (initParameter != null)
		{
			initParameter.forEach((k, v) -> context.setInitParameter(Objects.toString(k), Objects.toString(v)));
			logger.debug("InitParams: {}", context.getInitParams());
		}

		context.setContextPath(contextPath);
		context.setAttribute(AnnotationConfiguration.SERVLET_CONTAINER_INITIALIZER_ORDER,
				initializers.stream().map(c -> c.getName()).collect(Collectors.joining(", ")) + ", *");
		context.setConfigurations(new Configuration[] { new AnnotationConfiguration() });
		context.setAttribute(WebInfConfiguration.WEBINF_JAR_PATTERN, "");

		webInfJars.map(e -> Paths.get(e)).filter(p ->
		{
			boolean readable = Files.isReadable(p);
			if (!readable)
				logger.warn("Classpath entry '{}' not readable", p);
			return readable;
		}).map(PathResource::new).forEach(r -> context.getMetaData().addWebInfJar(r));

		context.getMetaData().setWebInfClassesDirs(webInfClassesDirs.map(e -> Paths.get(e)).filter(Files::isReadable)
				.map(PathResource::new).collect(Collectors.toList()));

		logger.info("Web inf classes: dirs {}", context.getMetaData().getWebInfClassesDirs());
		logger.info("Web inf classes: jars {}", context.getMetaData().getWebInfJars());

		for (Class f : additionalFilters)
		{
			logger.info("Adding filter: {}", f.getName());
			context.addFilter(f, "/*", EnumSet.allOf(DispatcherType.class));
		}

		connectors.forEach(c -> addConnector(c.apply(this)));

		setHandler(context);
		setStopAtShutdown(true);

		addBean(errorHandler);
		context.setErrorHandler(errorHandler);

		servletContext = context.getServletContext();
		webAppContext = context;
	}

	public Context getServletContext()
	{
		return servletContext;
	}

	public WebAppContext getWebAppContext()
	{
		return webAppContext;
	}

	public static void start(JettyServer server)
	{
		try
		{
			server.start();
			server.join();
		}
		catch (Throwable e)
		{
			e.printStackTrace();
			try
			{
				server.stop();
				System.exit(1);
			}
			catch (Exception e1)
			{
				e1.printStackTrace();
				System.exit(2);
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy