/*
* Copyright 2012-2017 the original author or authors.
*
* 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 org.springframework.boot.web.embedded.jetty;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.jasper.servlet.JspServlet;
import org.eclipse.jetty.server.Handler;
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.HandlerWrapper;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactoryTests;
import org.springframework.http.HttpHeaders;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JettyServletWebServerFactory}.
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
* @author Henri Kerola
*/
public class JettyServletWebServerFactoryTests
extends AbstractServletWebServerFactoryTests {
@Override
protected JettyServletWebServerFactory getFactory() {
return new JettyServletWebServerFactory(0);
}
@Test
public void jettyConfigurations() throws Exception {
JettyServletWebServerFactory factory = getFactory();
Configuration[] configurations = new Configuration[4];
for (int i = 0; i < configurations.length; i++) {
configurations[i] = mock(Configuration.class);
}
factory.setConfigurations(Arrays.asList(configurations[0], configurations[1]));
factory.addConfigurations(configurations[2], configurations[3]);
this.webServer = factory.getWebServer();
InOrder ordered = inOrder((Object[]) configurations);
for (Configuration configuration : configurations) {
ordered.verify(configuration).configure(any(WebAppContext.class));
}
}
@Test
public void jettyCustomizations() throws Exception {
JettyServletWebServerFactory factory = getFactory();
JettyServerCustomizer[] configurations = new JettyServerCustomizer[4];
for (int i = 0; i < configurations.length; i++) {
configurations[i] = mock(JettyServerCustomizer.class);
}
factory.setServerCustomizers(Arrays.asList(configurations[0], configurations[1]));
factory.addServerCustomizers(configurations[2], configurations[3]);
this.webServer = factory.getWebServer();
InOrder ordered = inOrder((Object[]) configurations);
for (JettyServerCustomizer configuration : configurations) {
ordered.verify(configuration).customize(any(Server.class));
}
}
@Test
public void sessionTimeout() throws Exception {
JettyServletWebServerFactory factory = getFactory();
factory.setSessionTimeout(10);
assertTimeout(factory, 10);
}
@Test
public void sessionTimeoutInMins() throws Exception {
JettyServletWebServerFactory factory = getFactory();
factory.setSessionTimeout(1, TimeUnit.MINUTES);
assertTimeout(factory, 60);
}
@Test
public void sslCiphersConfiguration() throws Exception {
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
JettyServletWebServerFactory factory = getFactory();
factory.setSsl(ssl);
this.webServer = factory.getWebServer();
this.webServer.start();
JettyWebServer jettyWebServer = (JettyWebServer) this.webServer;
ServerConnector connector = (ServerConnector) jettyWebServer.getServer()
.getConnectors()[0];
SslConnectionFactory connectionFactory = connector
.getConnectionFactory(SslConnectionFactory.class);
assertThat(connectionFactory.getSslContextFactory().getIncludeCipherSuites())
.containsExactly("ALPHA", "BRAVO", "CHARLIE");
assertThat(connectionFactory.getSslContextFactory().getExcludeCipherSuites())
.isEmpty();
}
@Test
public void stopCalledWithoutStart() throws Exception {
JettyServletWebServerFactory factory = getFactory();
this.webServer = factory.getWebServer(exampleServletRegistration());
this.webServer.stop();
Server server = ((JettyWebServer) this.webServer).getServer();
assertThat(server.isStopped()).isTrue();
}
@Override
protected void addConnector(int port, AbstractServletWebServerFactory factory) {
((JettyServletWebServerFactory) factory)
.addServerCustomizers(new JettyServerCustomizer() {
@Override
public void customize(Server server) {
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
}
});
}
@Test
public void sslEnabledMultiProtocolsConfiguration() throws Exception {
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" });
JettyServletWebServerFactory factory = getFactory();
factory.setSsl(ssl);
this.webServer = factory.getWebServer();
this.webServer.start();
JettyWebServer jettyWebServer = (JettyWebServer) this.webServer;
ServerConnector connector = (ServerConnector) jettyWebServer.getServer()
.getConnectors()[0];
SslConnectionFactory connectionFactory = connector
.getConnectionFactory(SslConnectionFactory.class);
assertThat(connectionFactory.getSslContextFactory().getIncludeProtocols())
.isEqualTo(new String[] { "TLSv1.1", "TLSv1.2" });
}
@Test
public void sslEnabledProtocolsConfiguration() throws Exception {
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
ssl.setEnabledProtocols(new String[] { "TLSv1.1" });
JettyServletWebServerFactory factory = getFactory();
factory.setSsl(ssl);
this.webServer = factory.getWebServer();
this.webServer.start();
JettyWebServer jettyWebServer = (JettyWebServer) this.webServer;
ServerConnector connector = (ServerConnector) jettyWebServer.getServer()
.getConnectors()[0];
SslConnectionFactory connectionFactory = connector
.getConnectionFactory(SslConnectionFactory.class);
assertThat(connectionFactory.getSslContextFactory().getIncludeProtocols())
.isEqualTo(new String[] { "TLSv1.1" });
}
private void assertTimeout(JettyServletWebServerFactory factory, int expected) {
this.webServer = factory.getWebServer();
JettyWebServer jettyWebServer = (JettyWebServer) this.webServer;
Handler[] handlers = jettyWebServer.getServer()
.getChildHandlersByClass(WebAppContext.class);
WebAppContext webAppContext = (WebAppContext) handlers[0];
int actual = webAppContext.getSessionHandler().getMaxInactiveInterval();
assertThat(actual).isEqualTo(expected);
}
@Test
public void wrappedHandlers() throws Exception {
JettyServletWebServerFactory factory = getFactory();
factory.setServerCustomizers(Arrays.asList(new JettyServerCustomizer() {
@Override
public void customize(Server server) {
Handler handler = server.getHandler();
HandlerWrapper wrapper = new HandlerWrapper();
wrapper.setHandler(handler);
HandlerCollection collection = new HandlerCollection();
collection.addHandler(wrapper);
server.setHandler(collection);
}
}));
this.webServer = factory.getWebServer(exampleServletRegistration());
this.webServer.start();
assertThat(getResponse(getLocalUrl("/hello"))).isEqualTo("Hello World");
}
@Test
public void basicSslClasspathKeyStore() throws Exception {
testBasicSslWithKeyStore("classpath:test.jks");
}
@Test
public void useForwardHeaders() throws Exception {
JettyServletWebServerFactory factory = getFactory();
factory.setUseForwardHeaders(true);
assertForwardHeaderIsUsed(factory);
}
@Test
public void defaultThreadPool() throws Exception {
JettyServletWebServerFactory factory = getFactory();
factory.setThreadPool(null);
assertThat(factory.getThreadPool()).isNull();
this.webServer = factory.getWebServer();
assertThat(((JettyWebServer) this.webServer).getServer().getThreadPool())
.isNotNull();
}
@Test
public void customThreadPool() throws Exception {
JettyServletWebServerFactory factory = getFactory();
ThreadPool threadPool = mock(ThreadPool.class);
factory.setThreadPool(threadPool);
this.webServer = factory.getWebServer();
assertThat(((JettyWebServer) this.webServer).getServer().getThreadPool())
.isSameAs(threadPool);
}
@Override
@SuppressWarnings("serial")
// Workaround for Jetty issue - https://bugs.eclipse.org/bugs/show_bug.cgi?id=470646
protected String setUpFactoryForCompression(final int contentSize, String[] mimeTypes,
String[] excludedUserAgents) throws Exception {
char[] chars = new char[contentSize];
Arrays.fill(chars, 'F');
final String testContent = new String(chars);
AbstractServletWebServerFactory factory = getFactory();
Compression compression = new Compression();
compression.setEnabled(true);
if (mimeTypes != null) {
compression.setMimeTypes(mimeTypes);
}
if (excludedUserAgents != null) {
compression.setExcludedUserAgents(excludedUserAgents);
}
factory.setCompression(compression);
this.webServer = factory
.getWebServer(new ServletRegistrationBean<HttpServlet>(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentLength(contentSize);
resp.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain");
resp.getWriter().print(testContent);
}
}, "/test.txt"));
this.webServer.start();
return testContent;
}
@Override
protected JspServlet getJspServlet() throws Exception {
WebAppContext context = (WebAppContext) ((JettyWebServer) this.webServer)
.getServer().getHandler();
ServletHolder holder = context.getServletHandler().getServlet("jsp");
if (holder == null) {
return null;
}
holder.start();
return (JspServlet) holder.getServlet();
}
@Override
protected Map<String, String> getActualMimeMappings() {
WebAppContext context = (WebAppContext) ((JettyWebServer) this.webServer)
.getServer().getHandler();
return context.getMimeTypes().getMimeMap();
}
@Override
protected Charset getCharset(Locale locale) {
WebAppContext context = (WebAppContext) ((JettyWebServer) this.webServer)
.getServer().getHandler();
String charsetName = context.getLocaleEncoding(locale);
return (charsetName != null) ? Charset.forName(charsetName) : null;
}
@Override
protected void handleExceptionCausedByBlockedPort(RuntimeException ex,
int blockedPort) {
assertThat(ex).isInstanceOf(PortInUseException.class);
assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort);
}
}