package com.kryptnostic.rhizome.core; import java.io.IOException; import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; 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.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.webapp.WebAppContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.core.io.ClassPathResource; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.google.common.base.Optional; import com.kryptnostic.rhizome.configuration.amazon.AmazonLaunchConfiguration; import com.kryptnostic.rhizome.configuration.jetty.ConnectorConfiguration; import com.kryptnostic.rhizome.configuration.jetty.ContextConfiguration; import com.kryptnostic.rhizome.configuration.jetty.GzipConfiguration; import com.kryptnostic.rhizome.configuration.jetty.JettyConfiguration; import com.kryptnostic.rhizome.configuration.service.ConfigurationService; public class JettyLoam implements Loam { private static final Logger logger = LoggerFactory.getLogger( JettyLoam.class ); protected final JettyConfiguration config; private final Server server; protected final Optional<AmazonLaunchConfiguration> maybeAmazonLaunchConfiguration; protected JettyLoam() throws JsonParseException, JsonMappingException, IOException { this( ConfigurationService.StaticLoader.loadConfiguration( JettyConfiguration.class ) ); } public JettyLoam( JettyConfiguration config ) throws IOException { this( config, Optional.absent() ); } protected JettyLoam( JettyConfiguration config, Optional<AmazonLaunchConfiguration> maybeAmazonLaunchConfiguration ) throws IOException { this.config = config; this.maybeAmazonLaunchConfiguration = maybeAmazonLaunchConfiguration; WebAppContext context = new WebAppContext(); if ( config.getContextConfiguration().isPresent() ) { ContextConfiguration contextConfig = config.getContextConfiguration().get(); context.setContextPath( contextConfig.getPath() ); context.setResourceBase( contextConfig.getResourceBase() ); context.setParentLoaderPriority( contextConfig.isParentLoaderPriority() ); } /* * Work around for https://bugs.eclipse.org/bugs/show_bug.cgi?id=404176 Probably need to report new bug as Jetty * picks up the SpringContextInitializer, but cannot find Spring WebApplicationInitializer types, without the * configuration hack. */ JettyAnnotationConfigurationHack configurationHack = new JettyAnnotationConfigurationHack(); if ( config.isSecurityEnabled() ) { JettyAnnotationConfigurationHack.registerInitializer( RhizomeSecurity.class.getName() ); } context.setConfigurations( new org.eclipse.jetty.webapp.Configuration[] { configurationHack } ); // TODO: Make loaded servlet classes configurable // context.addServlet( JspServlet.class, "*.jsp" ); QueuedThreadPool threadPool = new QueuedThreadPool( config.getMaxThreads(), Math.min( config.getMaxThreads(), 100 ), 60000, new BlockingArrayQueue<>( 6000 )); // TODO: Make max threads configurable ( queued vs concurrent thread pool needs to be configured ) server = new Server(threadPool); if ( config.getWebConnectorConfiguration().isPresent() ) { configureEndpoint( config.getWebConnectorConfiguration().get() ); } if ( config.getServiceConnectorConfiguration().isPresent() ) { configureEndpoint( config.getServiceConnectorConfiguration().get() ); } Handler handler = context; Optional<GzipConfiguration> gzipConfig = config.getGzipConfiguration(); if ( gzipConfig.isPresent() && gzipConfig.get().isGzipEnabled() ) { GzipHandler gzipHandler = new GzipHandler(); DefaultHandler defaultHandler = new DefaultHandler(); HandlerList handlerList = new HandlerList(); handlerList.setHandlers( new Handler[] { context, defaultHandler } ); gzipHandler.addIncludedMimeTypes( gzipConfig.get().getGzipContentTypes().toArray( new String[ 0 ] ) ); gzipHandler.addIncludedMethods( gzipConfig.get().getGzipMethods().toArray( new String[ 0 ] ) ); gzipHandler.setMinGzipSize( 0 ); gzipHandler.setHandler( handlerList ); handler = gzipHandler; } server.setHandler( handler ); } public JettyLoam( Class<? extends JettyConfiguration> clazz ) throws IOException { this( ConfigurationService.StaticLoader.loadConfiguration( clazz ) ); } public static void main( String[] args ) throws Exception { JettyLoam server = new JettyLoam(); server.start(); server.join(); } protected void configureEndpoint( ConnectorConfiguration configuration ) throws IOException { HttpConfiguration http_config = new HttpConfiguration(); if ( !configuration.requireSSL() ) { ServerConnector http = new ServerConnector( server, new HttpConnectionFactory( http_config ) ); http.setPort( configuration.getHttpPort() ); server.addConnector( http ); } if ( ( configuration.requireSSL() || configuration.useSSL() ) && config.getTruststoreConfiguration().isPresent() && config.getKeystoreConfiguration().isPresent() ) { http_config.setSecureScheme( "https" ); http_config.setSecurePort( configuration.getHttpsPort() ); SslContextFactory contextFactory = new SslContextFactory(); configureSslStores( contextFactory ); String certAlias = configuration.getCertificateAlias().or( "" ); if ( StringUtils.isNotBlank( certAlias ) ) { contextFactory.setCertAlias( certAlias ); } contextFactory.setKeyManagerPassword( config.getKeyManagerPassword().get() ); contextFactory.setWantClientAuth( configuration.wantClientAuth() ); // contextFactory.setNeedClientAuth( configuration.needClientAuth() ); HttpConfiguration https_config = new HttpConfiguration( http_config ); https_config.addCustomizer( new SecureRequestCustomizer() ); SslConnectionFactory connectionFactory = new SslConnectionFactory( contextFactory, "http/1.1" ); ServerConnector ssl = new ServerConnector( server, connectionFactory, new HttpConnectionFactory( https_config ) ); // Jetty needs this twice, straight for the Jetty samples ssl.setPort( configuration.getHttpsPort() ); server.addConnector( ssl ); } else if ( ( configuration.requireSSL() || configuration.useSSL() ) && ( !config.getTruststoreConfiguration().isPresent() || !config.getKeystoreConfiguration().isPresent() ) ) { logger.warn( "SSL Configuration is incomplete." ); } } protected void configureSslStores( SslContextFactory contextFactory ) throws IOException { contextFactory.setTrustStorePath( getFromClasspath( config.getTruststoreConfiguration().get() .getStorePath() ) ); contextFactory.setTrustStorePassword( config.getTruststoreConfiguration().get().getStorePassword() ); contextFactory .setKeyStorePath( getFromClasspath( config.getKeystoreConfiguration().get().getStorePath() ) ); contextFactory.setKeyStorePassword( config.getKeystoreConfiguration().get().getStorePassword() ); } private String getFromClasspath( String path ) throws IOException { return new ClassPathResource( path ).getURL().toString(); } void initializeSslContextFactory() { /* No-Op */ } public Server getServer() { return server; } @Override public synchronized void start() throws Exception { if ( !server.isRunning() ) { server.start(); } } @Override public synchronized void stop() throws Exception { if ( server.isRunning() ) { server.stop(); } } @Override public synchronized void join() throws BeansException, InterruptedException { server.join(); } }