/*
* Copyright 2016 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.
*/
package com.thoughtworks.go.server;
import com.thoughtworks.go.util.ArrayUtil;
import com.thoughtworks.go.util.FileUtil;
import com.thoughtworks.go.util.ReflectionUtil;
import com.thoughtworks.go.util.SystemEnvironment;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.*;
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.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.ArgumentCaptor;
import javax.net.ssl.SSLSocketFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
public class Jetty9ServerTest {
private Jetty9Server jetty9Server;
private Server server;
private SystemEnvironment systemEnvironment;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private SSLSocketFactory sslSocketFactory;
private File configDir;
@Before
public void setUp() throws Exception {
server = mock(Server.class);
systemEnvironment = mock(SystemEnvironment.class);
when(systemEnvironment.getServerPort()).thenReturn(1234);
when(systemEnvironment.keystore()).thenReturn(temporaryFolder.newFolder());
when(systemEnvironment.truststore()).thenReturn(temporaryFolder.newFolder());
when(systemEnvironment.getWebappContextPath()).thenReturn("context");
when(systemEnvironment.getCruiseWar()).thenReturn("cruise.war");
when(systemEnvironment.getParentLoaderPriority()).thenReturn(true);
when(systemEnvironment.useCompressedJs()).thenReturn(true);
when(systemEnvironment.get(SystemEnvironment.RESPONSE_BUFFER_SIZE)).thenReturn(1000);
when(systemEnvironment.get(SystemEnvironment.IDLE_TIMEOUT)).thenReturn(2000);
when(systemEnvironment.configDir()).thenReturn(configDir = temporaryFolder.newFile());
when(systemEnvironment.get(SystemEnvironment.GO_SSL_CONFIG_ALLOW)).thenReturn(true);
when(systemEnvironment.get(SystemEnvironment.GO_SSL_RENEGOTIATION_ALLOWED)).thenReturn(true);
when(systemEnvironment.getJettyConfigFile()).thenReturn(new File("foo"));
sslSocketFactory = mock(SSLSocketFactory.class);
when(sslSocketFactory.getSupportedCipherSuites()).thenReturn(new String[]{});
jetty9Server = new Jetty9Server(systemEnvironment, "pwd", sslSocketFactory, server);
ReflectionUtil.setStaticField(Jetty9Server.class, "JETTY_XML_LOCATION_IN_JAR", "config");
}
@Test
public void shouldAddMBeanContainerAsEventListener() throws Exception {
ArgumentCaptor<MBeanContainer> captor = ArgumentCaptor.forClass(MBeanContainer.class);
jetty9Server.configure();
verify(server).addEventListener(captor.capture());
MBeanContainer mBeanContainer = captor.getValue();
assertThat(mBeanContainer.getMBeanServer(), is(not(nullValue())));
}
@Test
public void shouldAddHttpSocketConnector() throws Exception {
ArgumentCaptor<Connector> captor = ArgumentCaptor.forClass(Connector.class);
jetty9Server.configure();
verify(server, times(2)).addConnector(captor.capture());
List<Connector> connectors = captor.getAllValues();
Connector plainConnector = connectors.get(0);
assertThat(plainConnector instanceof ServerConnector, is(true));
ServerConnector connector = (ServerConnector) plainConnector;
assertThat(connector.getServer(), is(server));
assertThat(connector.getConnectionFactories().size(), is(1));
ConnectionFactory connectionFactory = connector.getConnectionFactories().iterator().next();
assertThat(connectionFactory instanceof HttpConnectionFactory, is(true));
}
@Test
public void shouldAddSSLSocketConnector() throws Exception {
ArgumentCaptor<Connector> captor = ArgumentCaptor.forClass(Connector.class);
jetty9Server.configure();
verify(server, times(2)).addConnector(captor.capture());
List<Connector> connectors = captor.getAllValues();
Connector sslConnector = connectors.get(1);
assertThat(sslConnector instanceof ServerConnector, is(true));
ServerConnector connector = (ServerConnector) sslConnector;
assertThat(connector.getServer(), is(server));
assertThat(connector.getConnectionFactories().size(), is(2));
Iterator<ConnectionFactory> iterator = connector.getConnectionFactories().iterator();
ConnectionFactory first = iterator.next();
ConnectionFactory second = iterator.next();
assertThat(first instanceof SslConnectionFactory, is(true));
SslConnectionFactory sslConnectionFactory = (SslConnectionFactory) first;
assertThat(sslConnectionFactory.getProtocol(), is("SSL-HTTP/1.1"));
assertThat(second instanceof HttpConnectionFactory, is(true));
}
@Test
public void shouldAddWelcomeRequestHandler() throws Exception {
ArgumentCaptor<HandlerCollection> captor = ArgumentCaptor.forClass(HandlerCollection.class);
jetty9Server.configure();
verify(server, times(1)).setHandler(captor.capture());
HandlerCollection handlerCollection = captor.getValue();
assertThat(handlerCollection.getHandlers().length, is(3));
Handler handler = handlerCollection.getHandlers()[0];
assertThat(handler instanceof Jetty9Server.GoServerWelcomeFileHandler, is(true));
Jetty9Server.GoServerWelcomeFileHandler welcomeFileHandler = (Jetty9Server.GoServerWelcomeFileHandler) handler;
assertThat(welcomeFileHandler.getContextPath(), is("/"));
}
@Test
public void shouldAddDefaultHeadersForRootContext() throws Exception {
ArgumentCaptor<HandlerCollection> captor = ArgumentCaptor.forClass(HandlerCollection.class);
jetty9Server.configure();
verify(server, times(1)).setHandler(captor.capture());
HandlerCollection handlerCollection = captor.getValue();
Jetty9Server.GoServerWelcomeFileHandler handler = (Jetty9Server.GoServerWelcomeFileHandler)handlerCollection.getHandlers()[0];
Handler rootPathHandler = handler.getHandler();
HttpServletResponse response = mock(HttpServletResponse.class);
when(response.getWriter()).thenReturn(mock(PrintWriter.class));
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getPathInfo()).thenReturn("/");
rootPathHandler.handle("/", mock(Request.class), request, response);
verify(response).setHeader("X-XSS-Protection", "1; mode=block");
verify(response).setHeader("X-Content-Type-Options", "nosniff");
verify(response).setHeader("X-Frame-Options", "SAMEORIGIN");
verify(response).setHeader("X-UA-Compatible", "chrome=1");
}
@Test
public void shouldSkipDefaultHeadersIfContextPathIsGoRootPath() throws Exception {
ArgumentCaptor<HandlerCollection> captor = ArgumentCaptor.forClass(HandlerCollection.class);
jetty9Server.configure();
verify(server, times(1)).setHandler(captor.capture());
HandlerCollection handlerCollection = captor.getValue();
Jetty9Server.GoServerWelcomeFileHandler handler = (Jetty9Server.GoServerWelcomeFileHandler)handlerCollection.getHandlers()[0];
Handler rootPathHandler = handler.getHandler();
HttpServletResponse response = mock(HttpServletResponse.class);
when(response.getWriter()).thenReturn(mock(PrintWriter.class));
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getPathInfo()).thenReturn("/go");
rootPathHandler.handle("/go", mock(Request.class), request, response);
verify(response, never()).setHeader("X-XSS-Protection", "1; mode=block");
verify(response, never()).setHeader("X-Content-Type-Options", "nosniff");
verify(response, never()).setHeader("X-Frame-Options", "SAMEORIGIN");
verify(response, never()).setHeader("X-UA-Compatible", "chrome=1");
}
@Test
public void shouldSkipDefaultHeadersIfContextPathIsAnyOtherUrlWithinGo() throws Exception {
ArgumentCaptor<HandlerCollection> captor = ArgumentCaptor.forClass(HandlerCollection.class);
jetty9Server.configure();
verify(server, times(1)).setHandler(captor.capture());
HandlerCollection handlerCollection = captor.getValue();
Jetty9Server.GoServerWelcomeFileHandler handler = (Jetty9Server.GoServerWelcomeFileHandler)handlerCollection.getHandlers()[0];
Handler rootPathHandler = handler.getHandler();
HttpServletResponse response = mock(HttpServletResponse.class);
when(response.getWriter()).thenReturn(mock(PrintWriter.class));
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getPathInfo()).thenReturn("/go/pipelines");
rootPathHandler.handle("/go/pipelines", mock(Request.class), request, response);
verify(response, never()).setHeader("X-XSS-Protection", "1; mode=block");
verify(response, never()).setHeader("X-Content-Type-Options", "nosniff");
verify(response, never()).setHeader("X-Frame-Options", "SAMEORIGIN");
verify(response, never()).setHeader("X-UA-Compatible", "chrome=1");
}
@Test
public void shouldAddResourceHandlerForAssets() throws Exception {
ArgumentCaptor<HandlerCollection> captor = ArgumentCaptor.forClass(HandlerCollection.class);
jetty9Server.configure();
verify(server, times(1)).setHandler(captor.capture());
HandlerCollection handlerCollection = captor.getValue();
assertThat(handlerCollection.getHandlers().length, is(3));
Handler handler = handlerCollection.getHandlers()[1];
assertThat(handler instanceof AssetsContextHandler, is(true));
AssetsContextHandler assetsContextHandler = (AssetsContextHandler) handler;
assertThat(assetsContextHandler.getContextPath(), is("context/assets"));
}
@Test
public void shouldAddWebAppContextHandler() throws Exception {
ArgumentCaptor<HandlerCollection> captor = ArgumentCaptor.forClass(HandlerCollection.class);
jetty9Server.configure();
verify(server, times(1)).setHandler(captor.capture());
HandlerCollection handlerCollection = captor.getValue();
assertThat(handlerCollection.getHandlers().length, is(3));
Handler handler = handlerCollection.getHandlers()[2];
assertThat(handler instanceof WebAppContext, is(true));
WebAppContext webAppContext = (WebAppContext) handler;
List<String> configClasses = ArrayUtil.asList(webAppContext.getConfigurationClasses());
assertThat(configClasses.contains(WebInfConfiguration.class.getCanonicalName()), is(true));
assertThat(configClasses.contains(WebXmlConfiguration.class.getCanonicalName()), is(true));
assertThat(configClasses.contains(JettyWebXmlConfiguration.class.getCanonicalName()), is(true));
assertThat(webAppContext.getContextPath(), is("context"));
assertThat(webAppContext.getWar(), is("cruise.war"));
assertThat(webAppContext.isParentLoaderPriority(), is(true));
assertThat(webAppContext.getDefaultsDescriptor(), is("jar:file:cruise.war!/WEB-INF/webdefault.xml"));
}
@Test
public void shouldSetStopAtShutdown() throws Exception {
jetty9Server.configure();
verify(server).setStopAtShutdown(true);
}
@Test
public void shouldAddExtraJarsIntoClassPath() throws Exception {
jetty9Server.configure();
jetty9Server.addExtraJarsToClasspath("test-addons/some-addon-dir/addon-1.JAR,test-addons/some-addon-dir/addon-2.jar");
assertThat(getWebAppContext(jetty9Server).getExtraClasspath(), is("test-addons/some-addon-dir/addon-1.JAR,test-addons/some-addon-dir/addon-2.jar," + configDir));
}
@Test
public void shouldSetInitParams() throws Exception {
jetty9Server.configure();
jetty9Server.setInitParameter("name", "value");
assertThat(getWebAppContext(jetty9Server).getInitParameter("name"), CoreMatchers.is("value"));
}
@Test
public void shouldReplaceJettyXmlIfItDoesNotContainCorrespondingJettyVersionNumber() throws IOException {
File jettyXml = temporaryFolder.newFile("jetty.xml");
when(systemEnvironment.getJettyConfigFile()).thenReturn(jettyXml);
String originalContent = "jetty-v6.2.3\nsome other local changes";
FileUtil.writeContentToFile(originalContent, jettyXml);
jetty9Server.replaceJettyXmlIfItBelongsToADifferentVersion(systemEnvironment.getJettyConfigFile());
assertThat(FileUtil.readContentFromFile(systemEnvironment.getJettyConfigFile()), is(FileUtil.readContentFromFile(new File(getClass().getResource("config/jetty.xml").getPath()))));
}
@Test
public void shouldNotReplaceJettyXmlIfItAlreadyContainsCorrespondingVersionNumber() throws IOException {
File jettyXml = temporaryFolder.newFile("jetty.xml");
when(systemEnvironment.getJettyConfigFile()).thenReturn(jettyXml);
String originalContent = "jetty-v9.2.3\nsome other local changes";
FileUtil.writeContentToFile(originalContent, jettyXml);
jetty9Server.replaceJettyXmlIfItBelongsToADifferentVersion(systemEnvironment.getJettyConfigFile());
assertThat(FileUtil.readContentFromFile(systemEnvironment.getJettyConfigFile()), is(originalContent));
}
@Test
public void shouldSetErrorHandlerForServer() throws Exception {
jetty9Server.configure();
verify(server).addBean(any(JettyCustomErrorPageHandler.class));
}
@Test
public void shouldSetErrorHandlerForWebAppContext() throws Exception {
ArgumentCaptor<HandlerCollection> captor = ArgumentCaptor.forClass(HandlerCollection.class);
jetty9Server.configure();
verify(server, times(1)).setHandler(captor.capture());
HandlerCollection handlerCollection = captor.getValue();
assertThat(handlerCollection.getHandlers().length, is(3));
Handler handler = handlerCollection.getHandlers()[2];
assertThat(handler instanceof WebAppContext, is(true));
WebAppContext webAppContext = (WebAppContext) handler;
assertThat(webAppContext.getErrorHandler() instanceof JettyCustomErrorPageHandler, is(true));
}
private WebAppContext getWebAppContext(Jetty9Server server) {
return (WebAppContext) ReflectionUtil.getField(server, "webAppContext");
}
}