package com.cloudhopper.jetty; /* * #%L * ch-jetty * %% * Copyright (C) 2012 - 2013 Cloudhopper by Twitter * %% * 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. * #L% */ import com.cloudhopper.commons.util.CountingRejectedExecutionHandler; import com.cloudhopper.commons.util.NamingThreadFactory; import com.cloudhopper.commons.util.StringUtil; import java.lang.management.ManagementFactory; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.management.MBeanServer; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.ConnectorStatistics; 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.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author joelauer (twitter: @jjlauer or <a href="http://twitter.com/jjlauer" target=window>http://twitter.com/jjlauer</a>) */ public class JettyHttpServerFactory { private static final Logger logger = LoggerFactory.getLogger(JettyHttpServerFactory.class); static public JettyHttpServer create(HttpServerConfiguration configuration) throws Exception { // validate the arguments if (configuration == null) { throw new NullPointerException("configuration cannot be null"); } // are there any connectors configured? if (!configuration.hasAtLeastOneConnector()) { throw new Exception("At least one connector or sslConnector must be configured for an HttpServer"); } // create thread pool for max control logger.info("Creating threadPool with minThreads [{}] and maxThreads [{}]...", configuration.getMinThreads(), configuration.getMaxThreads()); // create connection queue based on what user picked for max queue size ThreadPoolExecutor executor = new ThreadPoolExecutor(configuration.getMinThreads(), configuration.getMaxThreads(), configuration.getThreadKeepAliveTimeout(), TimeUnit.MILLISECONDS, (configuration.getMaxQueueSize() < 0 ? new LinkedBlockingQueue<Runnable>() : (configuration.getMaxQueueSize() == 0 ? new SynchronousQueue<Runnable>() : new ArrayBlockingQueue<Runnable>(configuration.getMaxQueueSize()))), new NamingThreadFactory(configuration.getName() + "ThreadPool"), new CountingRejectedExecutionHandler()); JettyExecutorThreadPool threadPool = new JettyExecutorThreadPool(executor); // create a new jetty server instance Server server = new Server(threadPool); // enable jmx? logger.info("Creating jmx for domain [{}]...", configuration.getJmxDomain()); MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); MBeanContainer mBeanContainer = new MBeanContainer(mBeanServer); mBeanContainer.setDomain(configuration.getJmxDomain()); server.addBean(mBeanContainer); // create a scheduler? always necessary? server.addBean(new ScheduledExecutorScheduler()); // add cleartext connectors for (HttpConnectorConfiguration connConfig : configuration.getConnectors()) { logger.info("Creating NIO connector on port [{}]...", connConfig.getPort()); // use config defaults HttpConfiguration config = new HttpConfiguration(); config.setSendServerVersion(true); ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(config)); if (connConfig.getHost() != null) { connector.setHost(connConfig.getHost()); } connector.setPort(connConfig.getPort().intValue()); connector.setIdleTimeout(connConfig.getMaxIdleTime()); connector.setReuseAddress(connConfig.isReuseAddress()); if (connConfig.isStatsEnabled()) connector.addBean(new ConnectorStatistics()); server.addConnector(connector); } // add SSL connectors for (HttpSslConnectorConfiguration connConfig : configuration.getSslConnectors()) { logger.trace("Creating NIO SSL connector on port [{}]...", connConfig.getPort()); // NIO-based SSL connector requires a factory at constructor time SslContextFactory factory = new SslContextFactory(); // the keystore file MUST be set if (connConfig.getKeystoreFile() == null) { throw new Exception("An HTTP SSL connector must have its keystoreFile set"); } logger.info("Configuring NIO SSL connector on port [{}] with keystoreFile [{}]", connConfig.getPort(), connConfig.getKeystoreFile()); factory.setKeyStorePath(connConfig.getKeystoreFile()); factory.setKeyStorePassword(connConfig.getKeystorePassword()); factory.setKeyManagerPassword(connConfig.getKeystorePassword()); // the truststore is either specific or the same as keystore if (connConfig.getTruststoreFile() == null) { factory.setTrustStorePath(factory.getKeyStorePath()); } else { factory.setTrustStorePath(connConfig.getTruststoreFile()); } if (connConfig.getTruststorePassword() == null) { factory.setTrustStorePassword(connConfig.getKeystorePassword()); } else { factory.setTrustStorePassword(connConfig.getTruststorePassword()); } if (!StringUtil.isEmpty(connConfig.getCertAlias())) { factory.setCertAlias(connConfig.getCertAlias()); } // jetty had/has a bug that prints out an error over and over if this is not explicitly set factory.setNeedClientAuth(false); // SSLv3 BUG: https://www.openssl.org/~bodo/ssl-poodle.pdf // This also overrides the Jetty SslContextFactory defaults to remove SSLv2Hello factory.setExcludeProtocols("SSL", "SSLv2", "SSLv3"); // Backwards compatibility because SSLv2Hello is disabled by default in Java >=7 factory.setIncludeProtocols("TLSv1", "TLSv1.1", "TLSv1.2", "SSLv2Hello"); // ? from example http://www.eclipse.org/jetty/documentation/current/embedding-jetty.html factory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA", "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"); // SSL HTTP Configuration. Use config defaults. HttpConfiguration config = new HttpConfiguration(); config.addCustomizer(new SecureRequestCustomizer()); // SSL Connector ServerConnector connector = new ServerConnector(server, new SslConnectionFactory(factory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(config)); if (connConfig.getHost() != null) { connector.setHost(connConfig.getHost()); } connector.setPort(connConfig.getPort().intValue()); connector.setIdleTimeout(connConfig.getMaxIdleTime()); connector.setReuseAddress(connConfig.isReuseAddress()); if (connConfig.isStatsEnabled()) connector.addBean(new ConnectorStatistics()); server.addConnector(connector); } // prep server to handle multiple contexts, potentially with sessions ContextHandlerCollection contexts = new ContextHandlerCollection(); HandlerCollection handlers = new HandlerCollection(); handlers.addHandler(contexts); server.setHandler(handlers); // at this point, servlets will be added to "contexts" and resources // such as files will be added as a resource handler ServletContextHandler rootServletContext = new ServletContextHandler(contexts, "/", (configuration.isSessionsEnabled().booleanValue() ? ServletContextHandler.SESSIONS : ServletContextHandler.NO_SESSIONS)); rootServletContext.setClassLoader(Thread.currentThread().getContextClassLoader()); // add a statistics handler -- responsible for generating statistics //StatisticsHandler statsHandler = new StatisticsHandler(); //rootContext.addHandler(statsHandler); // server won't accept new connections, but will finish existing ones if (configuration.getGracefulShutdownTime() != null) { server.setStopTimeout(configuration.getGracefulShutdownTime()); logger.debug("{} has graceful shutdown period of {}ms", configuration.getName(), configuration.getGracefulShutdownTime()); } // turn off - this registers jetty's internal shutdown hook, which if // enabled will shut down jetty before we tell it to stop if (!configuration.isJettyAutoShutdownDisabled()) server.setStopAtShutdown(true); JettyHttpServer httpd = new JettyHttpServer(configuration, server, handlers, contexts, rootServletContext); // any post-configs if (configuration.getResourceBaseDirectory() != null) { httpd.addResourceBase(configuration.getResourceBaseDirectory()); } return httpd; } }