/* * Copyright 2014, The Sporting Exchange Limited * * 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 com.betfair.cougar.transport.jetty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.betfair.cougar.util.KeyStoreManagement; import org.apache.commons.lang.StringUtils; import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.ConnectorStatistics; import org.eclipse.jetty.server.ForwardedRequestCustomizer; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LowResourceMonitor; 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.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.springframework.core.io.Resource; import javax.management.MBeanServer; import javax.management.ObjectName; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; /** * This class encapsulate configuration and lifecycle of a Jetty server and its connectors */ public class JettyServerWrapper { private static final Logger LOGGER = LoggerFactory.getLogger(JettyServerWrapper.class); private Server jettyServer; private MBeanServer mbeanServer; private QueuedThreadPool threadPool; private int httpPort; private boolean httpReuseAddress; private int httpMaxIdle; private int httpAcceptors; private int httpAcceptQueueSize; private int httpSelectors; private boolean httpsAllowRenegotiate; private boolean httpForwarded; private boolean httpsForwarded; private int requestHeaderSize; private int responseHeaderSize; private int responseBufferSize; private int httpsPort; private boolean httpsReuseAddress; private int httpsMaxIdle; private int httpsAcceptors; private int httpsAcceptQueueSize; private int httpsSelectors; private boolean httpsWantClientAuth; private boolean httpsNeedClientAuth; private Resource httpsKeystore; private String httpsKeystoreType; private String httpsKeyPassword; private String httpsCertAlias; private Resource httpsTruststore; private String httpsTruststoreType; private String httpsTrustPassword; private int minThreads; private int maxThreads; private ServerConnector httpConnector; private HttpConfiguration httpConfiguration; private ServerConnector httpsConnector; private HttpConfiguration httpsConfiguration; private int maxFormContentSize; private int lowResourcesIdleTime; private int lowResourcesMaxTime; private int lowResourcesPeriod; private boolean lowResourcesMonitorThreads; private int lowResourcesMaxConnections; private long lowResourcesMaxMemory; public void initialiseConnectors() throws Exception { threadPool = new QueuedThreadPool(); threadPool.setMaxThreads(maxThreads); threadPool.setMinThreads(minThreads); threadPool.setName("JettyThread"); jettyServer = new Server(threadPool); jettyServer.setStopAtShutdown(true); MBeanContainer container = new MBeanContainer(mbeanServer); jettyServer.addBean(container); LowResourceMonitor lowResourcesMonitor = new LowResourceMonitor(jettyServer); lowResourcesMonitor.setPeriod(lowResourcesPeriod); lowResourcesMonitor.setLowResourcesIdleTimeout(lowResourcesIdleTime); lowResourcesMonitor.setMonitorThreads(lowResourcesMonitorThreads); lowResourcesMonitor.setMaxConnections(lowResourcesMaxConnections); lowResourcesMonitor.setMaxMemory(lowResourcesMaxMemory); lowResourcesMonitor.setMaxLowResourcesTime(lowResourcesMaxTime); jettyServer.addBean(lowResourcesMonitor); // US24803 - Needed for preventing Hashtable key collision DoS CVE-2012-2739 jettyServer.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize", maxFormContentSize); List<Connector> connectors = new ArrayList<Connector>(); if (httpPort != -1) { httpConfiguration = createHttpConfiguration(); setBufferSizes(httpConfiguration); if (httpForwarded) { httpConfiguration.addCustomizer(new ForwardedRequestCustomizer()); } httpConnector = createHttpConnector(jettyServer, httpConfiguration, httpAcceptors, httpSelectors); httpConnector.setPort(httpPort); httpConnector.setReuseAddress(httpReuseAddress); httpConnector.setIdleTimeout(httpMaxIdle); httpConnector.setAcceptQueueSize(httpAcceptQueueSize); httpConnector.addBean(new ConnectorStatistics()); connectors.add(httpConnector); } if (httpsPort != -1) { SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory.setKeyStorePath(httpsKeystore.getFile().getCanonicalPath()); sslContextFactory.setKeyStoreType(httpsKeystoreType); sslContextFactory.setKeyStorePassword(httpsKeyPassword); if (StringUtils.isNotBlank(httpsCertAlias)) { sslContextFactory.setCertAlias(httpsCertAlias); } sslContextFactory.setKeyManagerPassword(httpsKeyPassword); // if you need it then you defo want it sslContextFactory.setWantClientAuth(httpsNeedClientAuth || httpsWantClientAuth); sslContextFactory.setNeedClientAuth(httpsNeedClientAuth); sslContextFactory.setRenegotiationAllowed(httpsAllowRenegotiate); httpsConfiguration = createHttpConfiguration(); setBufferSizes(httpsConfiguration); if (httpsForwarded) { httpsConfiguration.addCustomizer(new ForwardedRequestCustomizer()); } httpsConnector = createHttpsConnector(jettyServer, httpsConfiguration, httpsAcceptors, httpsSelectors, sslContextFactory); httpsConnector.setPort(httpsPort); httpsConnector.setReuseAddress(httpsReuseAddress); httpsConnector.setIdleTimeout(httpsMaxIdle); httpsConnector.setAcceptQueueSize(httpsAcceptQueueSize); httpsConnector.addBean(new ConnectorStatistics()); mbeanServer.registerMBean(getKeystoreCertificateChains(), new ObjectName("CoUGAR.https:name=keyStore")); // truststore is not required if we don't want client auth if (httpsWantClientAuth) { sslContextFactory.setTrustStorePath(httpsTruststore.getFile().getCanonicalPath()); sslContextFactory.setTrustStoreType(httpsTruststoreType); sslContextFactory.setTrustStorePassword(httpsTrustPassword); mbeanServer.registerMBean(getTruststoreCertificateChains(), new ObjectName("CoUGAR.https:name=trustStore")); } connectors.add(httpsConnector); } if (connectors.size() == 0) { throw new IllegalStateException("HTTP transport requires at least one port enabled to function correctly."); } jettyServer.setConnectors(connectors.toArray(new Connector[connectors.size()])); } public void start() throws Exception { jettyServer.start(); } public void startThreadPool() throws Exception { threadPool.start(); } public void stop() throws Exception { jettyServer.stop(); } public boolean isRunning() { if (jettyServer == null) { return false; } return jettyServer.isRunning(); } private HttpConfiguration createHttpConfiguration() { HttpConfiguration httpConfiguration = new HttpConfiguration(); httpConfiguration.setSendServerVersion(true); httpConfiguration.setSendDateHeader(true); return httpConfiguration; } /** * Sets the request and header buffer sizex if they are not zero */ private void setBufferSizes(HttpConfiguration buffers) { if (requestHeaderSize > 0) { LOGGER.info("Request header size set to {} for {}", requestHeaderSize, buffers.getClass().getCanonicalName()); buffers.setRequestHeaderSize(requestHeaderSize); } if (responseBufferSize > 0) { LOGGER.info("Response buffer size set to {} for {}", responseBufferSize, buffers.getClass().getCanonicalName()); buffers.setOutputBufferSize(responseBufferSize); } if (responseHeaderSize > 0) { LOGGER.info("Response header size set to {} for {}", responseHeaderSize, buffers.getClass().getCanonicalName()); buffers.setResponseHeaderSize(responseHeaderSize); } } protected ServerConnector createHttpConnector(Server server, HttpConfiguration httpConfiguration, int httpAcceptors, int httpSelectors) { return new ServerConnector(server, null, null, null, httpAcceptors, httpSelectors, new HttpConnectionFactory(httpConfiguration)); } protected ServerConnector createHttpsConnector(Server server, HttpConfiguration httpConfiguration, int httpsAcceptors, int httpsSelectors, SslContextFactory sslContextFactory) { httpConfiguration.addCustomizer(new SecureRequestCustomizer()); return new ServerConnector(server, null, null, null, httpsAcceptors, httpsSelectors, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(httpConfiguration)); } private KeyStoreManagement getKeystoreCertificateChains() throws Exception { return KeyStoreManagement.getKeyStoreManagement(httpsKeystoreType, httpsKeystore, httpsKeyPassword); } private KeyStoreManagement getTruststoreCertificateChains() throws Exception { return KeyStoreManagement.getKeyStoreManagement(httpsTruststoreType, httpsTruststore, httpsTrustPassword); } public boolean addBean(Object o) { return jettyServer.addBean(o); } public void setHandler(Handler handler) { jettyServer.setHandler(handler); } public boolean isHttpEnabled() { return httpPort != -1; } public boolean isHttpsEnabled() { return httpsPort != -1; } public int getRequestHeaderSize() { return requestHeaderSize; } public int getResponseBufferSize() { return responseBufferSize; } public int getResponseHeaderSize() { return responseHeaderSize; } public int getHttpAcceptors() { return httpAcceptors; } public int getHttpsAcceptors() { return httpsAcceptors; } public boolean isHttpForwarded() { return httpForwarded; } public void setMbeanServer(MBeanServer mbeanServer) { this.mbeanServer = mbeanServer; } public void setHttpPort(int httpPort) { this.httpPort = httpPort; } public int getHttpPort() { return httpPort; } public void setHttpReuseAddress(boolean httpReuseAddress) { this.httpReuseAddress = httpReuseAddress; } public void setHttpMaxIdle(int httpMaxIdle) { this.httpMaxIdle = httpMaxIdle; } public void setHttpAcceptors(int httpAcceptors) { this.httpAcceptors = httpAcceptors; } public void setHttpSelectors(int httpSelectors) { this.httpSelectors = httpSelectors; } public int getHttpSelectors() { return httpSelectors; } public void setHttpForwarded(boolean httpForwarded) { this.httpForwarded = httpForwarded; } public boolean isHttpsForwarded() { return httpsForwarded; } public void setRequestHeaderSize(int requestHeaderSize) { this.requestHeaderSize = requestHeaderSize; } public void setResponseHeaderSize(int responseHeaderSize) { this.responseHeaderSize = responseHeaderSize; } public void setResponseBufferSize(int responseBufferSize) { this.responseBufferSize = responseBufferSize; } public void setHttpsPort(int httpsPort) { this.httpsPort = httpsPort; } public int getHttpsPort() { return httpsPort; } public void setHttpsReuseAddress(boolean httpsReuseAddress) { this.httpsReuseAddress = httpsReuseAddress; } public void setHttpsMaxIdle(int httpsMaxIdle) { this.httpsMaxIdle = httpsMaxIdle; } public void setHttpsAcceptors(int httpsAcceptors) { this.httpsAcceptors = httpsAcceptors; } public void setHttpsSelectors(int httpsSelectors) { this.httpsSelectors = httpsSelectors; } public int getHttpsSelectors() { return httpsSelectors; } public void setHttpsWantClientAuth(boolean httpsWantClientAuth) { this.httpsWantClientAuth = httpsWantClientAuth; } public void setHttpsNeedClientAuth(boolean httpsNeedClientAuth) { this.httpsNeedClientAuth = httpsNeedClientAuth; } public void setHttpsKeystore(Resource httpsKeystore) { this.httpsKeystore = httpsKeystore; } public void setHttpsKeystoreType(String httpsKeystoreType) { this.httpsKeystoreType = httpsKeystoreType; } public void setHttpsKeyPassword(String httpsKeyPassword) { this.httpsKeyPassword = httpsKeyPassword; } public void setHttpsTruststore(Resource httpsTruststore) { this.httpsTruststore = httpsTruststore; } public void setHttpsTruststoreType(String httpsTruststoreType) { this.httpsTruststoreType = httpsTruststoreType; } public void setHttpsTrustPassword(String httpsTrustPassword) { this.httpsTrustPassword = httpsTrustPassword; } public void setMinThreads(int minThreads) { this.minThreads = minThreads; } public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; } public boolean isHttpsAllowRenegotiate() { return httpsAllowRenegotiate; } public void setHttpsAllowRenegotiate(boolean httpsAllowRenegotiate) { this.httpsAllowRenegotiate = httpsAllowRenegotiate; } public Server getJettyServer() { return jettyServer; } /* Some testing helpers */ public boolean isHttpConnectorCreated() { return httpConnector != null; } public boolean isHttpsConnectorCreated() { return httpsConnector != null; } public void setThreadPoolName(String s) { threadPool.setName(s); } public int getMaxFormContentSize() { return maxFormContentSize; } /** * Used to set the Jetty Server attribute "org.eclipse.jetty.server.Request.maxFormContentSize". * @param maxFormContentSize */ public void setMaxFormContentSize(int maxFormContentSize) { this.maxFormContentSize = maxFormContentSize; } public void setHttpsForwarded(boolean httpsForwarded) { this.httpsForwarded = httpsForwarded; } public void setLowResourcesIdleTime(int lowResourcesIdleTime) { this.lowResourcesIdleTime = lowResourcesIdleTime; } public void setLowResourcesMaxTime(int lowResourcesMaxTime) { this.lowResourcesMaxTime = lowResourcesMaxTime; } public void setLowResourcesPeriod(int lowResourcesPeriod) { this.lowResourcesPeriod = lowResourcesPeriod; } public void setLowResourcesMaxConnections(int lowResourcesMaxConnections) { this.lowResourcesMaxConnections = lowResourcesMaxConnections; } public void setLowResourcesMaxMemory(long lowResourcesMaxMemory) { this.lowResourcesMaxMemory = lowResourcesMaxMemory; } public int getHttpAcceptQueueSize() { return httpAcceptQueueSize; } public void setHttpAcceptQueueSize(int httpAcceptQueueSize) { this.httpAcceptQueueSize = httpAcceptQueueSize; } public int getHttpsAcceptQueueSize() { return httpsAcceptQueueSize; } public void setHttpsAcceptQueueSize(int httpsAcceptQueueSize) { this.httpsAcceptQueueSize = httpsAcceptQueueSize; } public void setHttpsCertAlias(String httpsCertAlias) { this.httpsCertAlias = httpsCertAlias; } public long getLowResourcesMaxMemory() { return lowResourcesMaxMemory; } public int getLowResourcesMaxConnections() { return lowResourcesMaxConnections; } public int getLowResourcesPeriod() { return lowResourcesPeriod; } public int getLowResourcesMaxTime() { return lowResourcesMaxTime; } public int getLowResourcesIdleTime() { return lowResourcesIdleTime; } public boolean isLowResourcesMonitorThreads() { return lowResourcesMonitorThreads; } public void setLowResourcesMonitorThreads(boolean lowResourcesMonitorThreads) { this.lowResourcesMonitorThreads = lowResourcesMonitorThreads; } }