blob: ce92760165398853dc673f3e0600ea2e20e3e4d7 [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 org.apache.cloudstack;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.Properties;
import com.cloud.utils.Pair;
import com.cloud.utils.server.ServerProperties;
import org.apache.commons.daemon.Daemon;
import org.apache.commons.daemon.DaemonContext;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.RequestLog;
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.HandlerCollection;
import org.eclipse.jetty.server.handler.MovedContextHandler;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.webapp.WebAppContext;
import org.apache.log4j.Logger;
import com.cloud.utils.PropertiesUtil;
import org.apache.commons.lang3.StringUtils;
/***
* The ServerDaemon class implements the embedded server, it can be started either
* using JSVC or directly from the JAR along with additional jars not shaded in the uber-jar.
* Configuration parameters are read from server.properties file available on the classpath.
*/
public class ServerDaemon implements Daemon {
private static final Logger LOG = Logger.getLogger(ServerDaemon.class);
private static final String WEB_XML = "META-INF/webapp/WEB-INF/web.xml";
/////////////////////////////////////////////////////
/////////////// Server Properties ///////////////////
/////////////////////////////////////////////////////
private static final String BIND_INTERFACE = "bind.interface";
private static final String CONTEXT_PATH = "context.path";
private static final String SESSION_TIMEOUT = "session.timeout";
private static final String HTTP_ENABLE = "http.enable";
private static final String HTTP_PORT = "http.port";
private static final String HTTPS_ENABLE = "https.enable";
private static final String HTTPS_PORT = "https.port";
private static final String KEYSTORE_FILE = "https.keystore";
private static final String KEYSTORE_PASSWORD = "https.keystore.password";
private static final String WEBAPP_DIR = "webapp.dir";
private static final String ACCESS_LOG = "access.log";
////////////////////////////////////////////////////////
/////////////// Server Configuration ///////////////////
////////////////////////////////////////////////////////
private Server server;
private boolean httpEnable = true;
private int httpPort = 8080;
private int httpsPort = 8443;
private int sessionTimeout = 30;
private boolean httpsEnable = false;
private String accessLogFile = "access.log";
private String bindInterface = null;
private String contextPath = "/client";
private String keystoreFile;
private String keystorePassword;
private String webAppLocation;
//////////////////////////////////////////////////
/////////////// Public methods ///////////////////
//////////////////////////////////////////////////
public static void main(final String... anArgs) throws Exception {
final ServerDaemon daemon = new ServerDaemon();
daemon.init(null);
daemon.start();
}
@Override
public void init(final DaemonContext context) {
final File confFile = PropertiesUtil.findConfigFile("server.properties");
if (confFile == null) {
LOG.warn(String.format("Server configuration file not found. Initializing server daemon on %s, with http.enable=%s, http.port=%s, https.enable=%s, https.port=%s, context.path=%s",
bindInterface, httpEnable, httpPort, httpsEnable, httpsPort, contextPath));
return;
}
LOG.info("Server configuration file found: " + confFile.getAbsolutePath());
try {
InputStream is = new FileInputStream(confFile);
final Properties properties = ServerProperties.getServerProperties(is);
if (properties == null) {
return;
}
setBindInterface(properties.getProperty(BIND_INTERFACE, null));
setContextPath(properties.getProperty(CONTEXT_PATH, "/client"));
setHttpEnable(Boolean.valueOf(properties.getProperty(HTTP_ENABLE, "true")));
setHttpPort(Integer.valueOf(properties.getProperty(HTTP_PORT, "8080")));
setHttpsEnable(Boolean.valueOf(properties.getProperty(HTTPS_ENABLE, "false")));
setHttpsPort(Integer.valueOf(properties.getProperty(HTTPS_PORT, "8443")));
setKeystoreFile(properties.getProperty(KEYSTORE_FILE));
setKeystorePassword(properties.getProperty(KEYSTORE_PASSWORD));
setWebAppLocation(properties.getProperty(WEBAPP_DIR));
setAccessLogFile(properties.getProperty(ACCESS_LOG, "access.log"));
setSessionTimeout(Integer.valueOf(properties.getProperty(SESSION_TIMEOUT, "30")));
} catch (final IOException e) {
LOG.warn("Failed to read configuration from server.properties file", e);
} finally {
// make sure that at least HTTP is enabled if both of them are set to false (misconfiguration)
if (!httpEnable && !httpsEnable) {
setHttpEnable(true);
LOG.warn("Server configuration malformed, neither http nor https is enabled, http will be enabled.");
}
}
LOG.info(String.format("Initializing server daemon on %s, with http.enable=%s, http.port=%s, https.enable=%s, https.port=%s, context.path=%s",
bindInterface, httpEnable, httpPort, httpsEnable, httpsPort, contextPath));
}
@Override
public void start() throws Exception {
// Thread pool
final QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setMinThreads(10);
threadPool.setMaxThreads(500);
// Jetty Server
server = new Server(threadPool);
// Setup Scheduler
server.addBean(new ScheduledExecutorScheduler());
// Setup JMX
final MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbeanContainer);
// HTTP config
final HttpConfiguration httpConfig = new HttpConfiguration();
// it would be nice to make this dynamic but we take care of this ourselves for now: httpConfig.addCustomizer( new ForwardedRequestCustomizer() );
httpConfig.setSecureScheme("https");
httpConfig.setSecurePort(httpsPort);
httpConfig.setOutputBufferSize(32768);
httpConfig.setRequestHeaderSize(8192);
httpConfig.setResponseHeaderSize(8192);
httpConfig.setSendServerVersion(false);
httpConfig.setSendDateHeader(false);
// HTTP Connector
createHttpConnector(httpConfig);
// Setup handlers
Pair<SessionHandler,HandlerCollection> pair = createHandlers();
server.setHandler(pair.second());
// Extra config options
server.setStopAtShutdown(true);
// HTTPS Connector
createHttpsConnector(httpConfig);
server.start();
// Must set the session timeout after the server has started
pair.first().setMaxInactiveInterval(sessionTimeout * 60);
server.join();
}
@Override
public void stop() throws Exception {
server.stop();
}
@Override
public void destroy() {
server.destroy();
}
///////////////////////////////////////////////////
/////////////// Private methods ///////////////////
///////////////////////////////////////////////////
private void createHttpConnector(final HttpConfiguration httpConfig) {
if (httpEnable) {
final ServerConnector httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
httpConnector.setPort(httpPort);
httpConnector.setHost(bindInterface);
httpConnector.setIdleTimeout(30000);
server.addConnector(httpConnector);
}
}
private void createHttpsConnector(final HttpConfiguration httpConfig) {
// Configure SSL
if (httpsEnable && StringUtils.isNotEmpty(keystoreFile) && new File(keystoreFile).exists()) {
// SSL Context
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
// Define keystore path and passwords
sslContextFactory.setKeyStorePath(keystoreFile);
sslContextFactory.setKeyStorePassword(keystorePassword);
sslContextFactory.setKeyManagerPassword(keystorePassword);
// HTTPS config
final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
// HTTPS Connector
final ServerConnector sslConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory, "http/1.1"),
new HttpConnectionFactory(httpsConfig));
sslConnector.setPort(httpsPort);
sslConnector.setHost(bindInterface);
server.addConnector(sslConnector);
}
}
private Pair<SessionHandler,HandlerCollection> createHandlers() {
final WebAppContext webApp = new WebAppContext();
webApp.setContextPath(contextPath);
webApp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
// GZIP handler
final GzipHandler gzipHandler = new GzipHandler();
gzipHandler.addIncludedMimeTypes("text/html", "text/xml", "text/css", "text/plain", "text/javascript", "application/javascript", "application/json", "application/xml");
gzipHandler.setIncludedMethods("GET", "POST");
gzipHandler.setCompressionLevel(9);
gzipHandler.setHandler(webApp);
if (StringUtils.isEmpty(webAppLocation)) {
webApp.setWar(getShadedWarUrl());
} else {
webApp.setWar(webAppLocation);
}
// Request log handler
final RequestLogHandler log = new RequestLogHandler();
log.setRequestLog(createRequestLog());
// Redirect root context handler_war
MovedContextHandler rootRedirect = new MovedContextHandler();
rootRedirect.setContextPath("/");
rootRedirect.setNewContextURL(contextPath);
rootRedirect.setPermanent(true);
// Put rootRedirect at the end!
return new Pair<>(webApp.getSessionHandler(), new HandlerCollection(log, gzipHandler, rootRedirect));
}
private RequestLog createRequestLog() {
final NCSARequestLog log = new NCSARequestLog();
final File logPath = new File(accessLogFile);
final File parentFile = logPath.getParentFile();
if (parentFile != null) {
parentFile.mkdirs();
}
log.setFilename(logPath.getPath());
log.setAppend(true);
log.setLogTimeZone("GMT");
log.setLogLatency(true);
return log;
}
private URL getResource(String aResource) {
return Thread.currentThread().getContextClassLoader().getResource(aResource);
}
private String getShadedWarUrl() {
final String urlStr = getResource(WEB_XML).toString();
return urlStr.substring(0, urlStr.length() - 15);
}
///////////////////////////////////////////
/////////////// Setters ///////////////////
///////////////////////////////////////////
public void setBindInterface(String bindInterface) {
this.bindInterface = bindInterface;
}
public void setHttpPort(int httpPort) {
this.httpPort = httpPort;
}
public void setHttpEnable(boolean httpEnable) {
this.httpEnable = httpEnable;
}
public void setHttpsPort(int httpsPort) {
this.httpsPort = httpsPort;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public void setHttpsEnable(boolean httpsEnable) {
this.httpsEnable = httpsEnable;
}
public void setKeystoreFile(String keystoreFile) {
this.keystoreFile = keystoreFile;
}
public void setKeystorePassword(String keystorePassword) {
this.keystorePassword = keystorePassword;
}
public void setAccessLogFile(String accessLogFile) {
this.accessLogFile = accessLogFile;
}
public void setWebAppLocation(String webAppLocation) {
this.webAppLocation = webAppLocation;
}
public void setSessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
}