/*************************GO-LICENSE-START*********************************
* Copyright 2015 ThoughtWorks, Inc.
*
* 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.
*************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.server;
import com.thoughtworks.go.server.config.GoSSLConfig;
import com.thoughtworks.go.server.util.GoPlainSocketConnector;
import com.thoughtworks.go.server.util.GoSslSocketConnector;
import com.thoughtworks.go.util.FileUtil;
import com.thoughtworks.go.util.GoConstants;
import com.thoughtworks.go.util.SystemEnvironment;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.xml.sax.SAXException;
import javax.management.MBeanServer;
import javax.net.ssl.SSLSocketFactory;
import javax.servlet.ServletException;
import javax.servlet.SessionCookieConfig;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.management.ManagementFactory;
import static java.text.MessageFormat.format;
public class Jetty9Server extends AppServer {
protected static String JETTY_XML_LOCATION_IN_JAR = "/defaultFiles/config";
private static final String JETTY_XML = "jetty.xml";
private static final String JETTY_VERSION = "jetty-v9.2.3";
private Server server;
private WebAppContext webAppContext;
private static final Logger LOG = Logger.getLogger(Jetty9Server.class);
private GoSSLConfig goSSLConfig;
public Jetty9Server(SystemEnvironment systemEnvironment, String password, SSLSocketFactory sslSocketFactory) {
this(systemEnvironment, password, sslSocketFactory, new Server());
}
Jetty9Server(SystemEnvironment systemEnvironment, String password, SSLSocketFactory sslSocketFactory, Server server) {
super(systemEnvironment, password, sslSocketFactory);
systemEnvironment.set(SystemEnvironment.JETTY_XML_FILE_NAME, JETTY_XML);
this.server = server;
goSSLConfig = new GoSSLConfig(sslSocketFactory, systemEnvironment);
}
@Override
public void configure() throws Exception {
server.addEventListener(mbeans());
server.addConnector(plainConnector());
server.addConnector(sslConnector());
HandlerCollection handlers = new HandlerCollection();
handlers.addHandler(welcomeFileHandler());
createWebAppContext();
addResourceHandler(handlers, webAppContext);
handlers.addHandler(webAppContext);
JettyCustomErrorPageHandler errorHandler = new JettyCustomErrorPageHandler();
webAppContext.setErrorHandler(errorHandler);
server.addBean(errorHandler);
server.setHandler(handlers);
performCustomConfiguration();
server.setStopAtShutdown(true);
}
@Override
public void start() throws Exception {
server.start();
}
@Override
public void stop() throws Exception {
server.stop();
}
@Override
public void addExtraJarsToClasspath(String extraClasspath) {
extraClasspath = new StringBuilder(extraClasspath).append(",").append(systemEnvironment.configDir().getAbsoluteFile()).toString();
webAppContext.setExtraClasspath(extraClasspath);
}
@Override
public void setCookieExpirePeriod(int cookieExpirePeriod) {
SessionCookieConfig cookieConfig = webAppContext.getSessionHandler().getSessionManager().getSessionCookieConfig();
cookieConfig.setHttpOnly(true);
cookieConfig.setMaxAge(cookieExpirePeriod);
}
@Override
public void setInitParameter(String name, String value) {
webAppContext.setInitParameter(name, value);
}
@Override
public Throwable getUnavailableException() {
return webAppContext.getUnavailableException();
}
private MBeanContainer mbeans() {
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
MBeanContainer mbeans = new MBeanContainer(platformMBeanServer);
return mbeans;
}
ContextHandler welcomeFileHandler() {
return new GoServerWelcomeFileHandler();
}
private Connector plainConnector() {
return new GoPlainSocketConnector(this, systemEnvironment).getConnector();
}
private Connector sslConnector() {
return new GoSslSocketConnector(this, password, systemEnvironment, goSSLConfig).getConnector();
}
class GoServerWelcomeFileHandler extends ContextHandler {
public GoServerWelcomeFileHandler() {
setContextPath("/");
setHandler(new Handler());
}
private class Handler extends AbstractHandler {
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if ("/go".equals(request.getPathInfo()) || request.getPathInfo().startsWith("/go/")) {
return;
}
response.setHeader("X-XSS-Protection", "1; mode=block");
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "SAMEORIGIN");
response.setHeader("X-UA-Compatible", "chrome=1");
if ("/".equals(target)) {
response.setHeader("Location", GoConstants.GO_URL_CONTEXT + "/home");
response.setStatus(301);
response.setHeader("Content-Type", "text/html");
PrintWriter writer = response.getWriter();
writer.write("redirecting..");
writer.close();
}
}
}
}
private void performCustomConfiguration() throws Exception {
File jettyConfig = systemEnvironment.getJettyConfigFile();
if (jettyConfig.exists()) {
replaceJettyXmlIfItBelongsToADifferentVersion(jettyConfig);
LOG.info("Configuring Jetty using " + jettyConfig.getAbsolutePath());
FileInputStream serverConfiguration = new FileInputStream(jettyConfig);
XmlConfiguration configuration = new XmlConfiguration(serverConfiguration);
configuration.configure(server);
} else {
String message = String.format(
"No custom jetty configuration (%s) found, using defaults.",
jettyConfig.getAbsolutePath());
LOG.info(message);
}
}
protected void replaceJettyXmlIfItBelongsToADifferentVersion(File jettyConfig) throws IOException {
if (FileUtil.readContentFromFile(jettyConfig).contains(JETTY_VERSION)) return;
replaceFileWithPackagedOne(jettyConfig);
}
private void replaceFileWithPackagedOne(File jettyConfig) {
InputStream inputStream = null;
try {
inputStream = getClass().getResourceAsStream(JETTY_XML_LOCATION_IN_JAR + "/" + jettyConfig.getName());
if (inputStream == null) {
throw new RuntimeException(format("Resource {0}/{1} does not exist in the classpath", JETTY_XML_LOCATION_IN_JAR, jettyConfig.getName()));
}
FileUtil.writeToFile(inputStream, systemEnvironment.getJettyConfigFile());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
private void addResourceHandler(HandlerCollection handlers, WebAppContext webAppContext) throws IOException {
if (!systemEnvironment.useCompressedJs()) return;
AssetsContextHandler handler = new AssetsContextHandler(systemEnvironment);
handlers.addHandler(handler);
webAppContext.addLifeCycleListener(new AssetsContextHandlerInitializer(handler, webAppContext));
}
private String getWarFile() {
return systemEnvironment.getCruiseWar();
}
WebAppContext createWebAppContext() throws IOException, SAXException, ClassNotFoundException, UnavailableException {
webAppContext = new WebAppContext();
webAppContext.setDefaultsDescriptor(GoWebXmlConfiguration.configuration(getWarFile()));
webAppContext.setConfigurationClasses(new String[]{
WebInfConfiguration.class.getCanonicalName(),
WebXmlConfiguration.class.getCanonicalName(),
JettyWebXmlConfiguration.class.getCanonicalName()
});
webAppContext.setContextPath(systemEnvironment.getWebappContextPath());
webAppContext.setWar(getWarFile());
webAppContext.setParentLoaderPriority(systemEnvironment.getParentLoaderPriority());
return webAppContext;
}
public Server getServer() {
return server;
}
}