Create beginnings of spring-boot-tomcat module
This commit is contained in:
committed by
Phillip Webb
parent
0337830615
commit
349f296d26
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.tomcat;
|
||||
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||
import org.apache.coyote.http2.Http2Protocol;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.web.server.Compression;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CompressionConnectorCustomizer}
|
||||
*
|
||||
* @author Rudy Adams
|
||||
*/
|
||||
class CompressionConnectorCustomizerTests {
|
||||
|
||||
private static final int MIN_SIZE = 100;
|
||||
|
||||
private final String[] mimeTypes = { "text/html", "text/xml", "text/xhtml" };
|
||||
|
||||
private final String[] excludedUserAgents = { "SomeUserAgent", "AnotherUserAgent" };
|
||||
|
||||
private Compression compression;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.compression = new Compression();
|
||||
this.compression.setEnabled(true);
|
||||
this.compression.setMinResponseSize(DataSize.ofBytes(MIN_SIZE));
|
||||
this.compression.setMimeTypes(this.mimeTypes);
|
||||
this.compression.setExcludedUserAgents(this.excludedUserAgents);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCustomizeCompression() {
|
||||
CompressionConnectorCustomizer compressionConnectorCustomizer = new CompressionConnectorCustomizer(
|
||||
this.compression);
|
||||
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
|
||||
Http2Protocol upgradeProtocol = new Http2Protocol();
|
||||
upgradeProtocol.setHttp11Protocol((AbstractHttp11Protocol<?>) connector.getProtocolHandler());
|
||||
connector.addUpgradeProtocol(upgradeProtocol);
|
||||
compressionConnectorCustomizer.customize(connector);
|
||||
AbstractHttp11Protocol<?> abstractHttp11Protocol = (AbstractHttp11Protocol<?>) connector.getProtocolHandler();
|
||||
compressionOn(abstractHttp11Protocol.getCompression());
|
||||
minSize(abstractHttp11Protocol.getCompressionMinSize());
|
||||
mimeType(abstractHttp11Protocol.getCompressibleMimeTypes());
|
||||
excludedUserAgents(abstractHttp11Protocol.getNoCompressionUserAgents());
|
||||
}
|
||||
|
||||
private void compressionOn(String compression) {
|
||||
assertThat(compression).isEqualTo("on");
|
||||
}
|
||||
|
||||
private void minSize(int minSize) {
|
||||
assertThat(minSize).isEqualTo(MIN_SIZE);
|
||||
}
|
||||
|
||||
private void mimeType(String[] mimeTypes) {
|
||||
assertThat(mimeTypes).isEqualTo(this.mimeTypes);
|
||||
}
|
||||
|
||||
private void excludedUserAgents(String combinedUserAgents) {
|
||||
assertThat(combinedUserAgents).isEqualTo("SomeUserAgent,AnotherUserAgent");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.tomcat;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.tomcat.util.net.SSLHostConfig;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
|
||||
import org.springframework.boot.testsupport.ssl.MockPkcs11Security;
|
||||
import org.springframework.boot.testsupport.ssl.MockPkcs11SecurityProvider;
|
||||
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
||||
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.WebServerSslBundle;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link SslConnectorCustomizer}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
* @author Cyril Dangerville
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
@DirtiesUrlFactories
|
||||
@MockPkcs11Security
|
||||
class SslConnectorCustomizerTests {
|
||||
|
||||
private final Log logger = LogFactory.getLog(SslConnectorCustomizerTests.class);
|
||||
|
||||
private Tomcat tomcat;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.tomcat = new Tomcat();
|
||||
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
|
||||
connector.setPort(0);
|
||||
this.tomcat.setConnector(connector);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void stop() throws Exception {
|
||||
System.clearProperty("javax.net.ssl.trustStorePassword");
|
||||
this.tomcat.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslCiphersConfiguration() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||
this.tomcat.start();
|
||||
SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs();
|
||||
assertThat(sslHostConfigs[0].getCiphers()).isEqualTo("ALPHA:BRAVO:CHARLIE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslEnabledMultipleProtocolsConfiguration() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" });
|
||||
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||
this.tomcat.start();
|
||||
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
|
||||
assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
|
||||
assertThat(sslHostConfig.getEnabledProtocols()).containsExactlyInAnyOrder("TLSv1.1", "TLSv1.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslEnabledProtocolsConfiguration() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setEnabledProtocols(new String[] { "TLSv1.2" });
|
||||
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||
this.tomcat.start();
|
||||
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
|
||||
assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
|
||||
assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||
Ssl.ClientAuth.NONE);
|
||||
customizer.customize(WebServerSslBundle.get(new Ssl()), Collections.emptyMap());
|
||||
}).withMessageContaining("SSL is enabled but no trust material is configured");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void customizeWhenSslIsEnabledWithPkcs11AndKeyStoreThrowsException() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyPassword("password");
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||
ssl.getClientAuth());
|
||||
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||
}).withMessageContaining("must be empty or null for PKCS11 hardware key stores");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenSslIsEnabledWithPkcs11AndKeyStoreProvider() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStorePassword("1234");
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, this.tomcat.getConnector(),
|
||||
ssl.getClientAuth());
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.tomcat;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.catalina.Service;
|
||||
import org.apache.catalina.connector.Connector;
|
||||
|
||||
/**
|
||||
* Helper class to provide public access to package-private methods for testing purposes.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public final class TomcatAccess {
|
||||
|
||||
private TomcatAccess() {
|
||||
|
||||
}
|
||||
|
||||
public static Map<Service, Connector[]> getServiceConnectors(TomcatWebServer tomcatWebServer) {
|
||||
return tomcatWebServer.getServiceConnectors();
|
||||
}
|
||||
|
||||
public static String getStartedLogMessage(TomcatWebServer tomcatWebServer) {
|
||||
return tomcatWebServer.getStartedLogMessage();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.tomcat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.loader.ParallelWebappClassLoader;
|
||||
import org.apache.catalina.webresources.StandardRoot;
|
||||
import org.apache.catalina.webresources.WarResourceSet;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link TomcatEmbeddedWebappClassLoader}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class TomcatEmbeddedWebappClassLoaderTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
@Test
|
||||
void getResourceFindsResourceFromParentClassLoader() throws Exception {
|
||||
File war = createWar();
|
||||
withWebappClassLoader(war, (classLoader) -> assertThat(classLoader.getResource("test.txt"))
|
||||
.isEqualTo(new URL(webInfClassesUrlString(war) + "test.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourcesOnlyFindsResourcesFromParentClassLoader() throws Exception {
|
||||
File warFile = createWar();
|
||||
withWebappClassLoader(warFile, (classLoader) -> {
|
||||
List<URL> urls = new ArrayList<>();
|
||||
CollectionUtils.toIterator(classLoader.getResources("test.txt")).forEachRemaining(urls::add);
|
||||
assertThat(urls).containsExactly(new URL(webInfClassesUrlString(warFile) + "test.txt"));
|
||||
});
|
||||
}
|
||||
|
||||
private void withWebappClassLoader(File war, ClassLoaderConsumer consumer) throws Exception {
|
||||
URLClassLoader parent = new URLClassLoader(new URL[] { new URL(webInfClassesUrlString(war)) }, null);
|
||||
try (ParallelWebappClassLoader classLoader = new TomcatEmbeddedWebappClassLoader(parent)) {
|
||||
StandardContext context = new StandardContext();
|
||||
context.setName("test");
|
||||
StandardRoot resources = new StandardRoot();
|
||||
resources.setContext(context);
|
||||
resources.addJarResources(new WarResourceSet(resources, "/", war.getAbsolutePath()));
|
||||
resources.start();
|
||||
classLoader.setResources(resources);
|
||||
classLoader.start();
|
||||
try {
|
||||
consumer.accept(classLoader);
|
||||
}
|
||||
finally {
|
||||
classLoader.stop();
|
||||
classLoader.close();
|
||||
resources.stop();
|
||||
}
|
||||
}
|
||||
parent.close();
|
||||
}
|
||||
|
||||
private String webInfClassesUrlString(File war) {
|
||||
return "jar:file:" + war.getAbsolutePath() + "!/WEB-INF/classes/";
|
||||
}
|
||||
|
||||
private File createWar() throws IOException {
|
||||
File warFile = new File(this.tempDir, "test.war");
|
||||
try (JarOutputStream warOut = new JarOutputStream(new FileOutputStream(warFile))) {
|
||||
createEntries(warOut, "WEB-INF/", "WEB-INF/classes/", "WEB-INF/classes/test.txt");
|
||||
}
|
||||
return warFile;
|
||||
}
|
||||
|
||||
private void createEntries(JarOutputStream out, String... names) throws IOException {
|
||||
for (String name : names) {
|
||||
out.putNextEntry(new ZipEntry(name));
|
||||
out.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
interface ClassLoaderConsumer {
|
||||
|
||||
void accept(ClassLoader classLoader) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.tomcat.reactive;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.LifecycleEvent;
|
||||
import org.apache.catalina.LifecycleListener;
|
||||
import org.apache.catalina.Service;
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.catalina.core.AprLifecycleListener;
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.valves.RemoteIpValve;
|
||||
import org.apache.coyote.ProtocolHandler;
|
||||
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.boot.tomcat.TomcatAccess;
|
||||
import org.springframework.boot.tomcat.TomcatConnectorCustomizer;
|
||||
import org.springframework.boot.tomcat.TomcatContextCustomizer;
|
||||
import org.springframework.boot.tomcat.TomcatProtocolHandlerCustomizer;
|
||||
import org.springframework.boot.tomcat.TomcatWebServer;
|
||||
import org.springframework.boot.web.server.PortInUseException;
|
||||
import org.springframework.boot.web.server.Shutdown;
|
||||
import org.springframework.boot.web.server.WebServerException;
|
||||
import org.springframework.boot.web.server.reactive.AbstractReactiveWebServerFactoryTests;
|
||||
import org.springframework.boot.web.server.reactive.ConfigurableReactiveWebServerFactory;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.assertArg;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link TomcatReactiveWebServerFactory}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Madhura Bhave
|
||||
* @author HaiTao Zhang
|
||||
*/
|
||||
class TomcatReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests {
|
||||
|
||||
@Override
|
||||
protected TomcatReactiveWebServerFactory getFactory() {
|
||||
return new TomcatReactiveWebServerFactory(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatCustomizers() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
TomcatContextCustomizer[] customizers = new TomcatContextCustomizer[4];
|
||||
Arrays.setAll(customizers, (i) -> mock(TomcatContextCustomizer.class));
|
||||
factory.setContextCustomizers(Arrays.asList(customizers[0], customizers[1]));
|
||||
factory.addContextCustomizers(customizers[2], customizers[3]);
|
||||
this.webServer = factory.getWebServer(mock(HttpHandler.class));
|
||||
InOrder ordered = inOrder((Object[]) customizers);
|
||||
for (TomcatContextCustomizer customizer : customizers) {
|
||||
then(customizer).should(ordered).customize(any(Context.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void contextIsAddedToHostBeforeCustomizersAreCalled() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
TomcatContextCustomizer customizer = mock(TomcatContextCustomizer.class);
|
||||
factory.addContextCustomizers(customizer);
|
||||
this.webServer = factory.getWebServer(mock(HttpHandler.class));
|
||||
then(customizer).should().customize(assertArg((context) -> assertThat(context.getParent()).isNotNull()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultTomcatListeners() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
assertThat(factory.getContextLifecycleListeners()).isEmpty();
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class));
|
||||
this.webServer = tomcatWebServer;
|
||||
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aprShouldBeOptIn() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
factory.setUseApr(true);
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class));
|
||||
this.webServer = tomcatWebServer;
|
||||
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).singleElement()
|
||||
.isInstanceOf(AprLifecycleListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatListeners() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
LifecycleListener[] listeners = new LifecycleListener[4];
|
||||
Arrays.setAll(listeners, (i) -> mock(LifecycleListener.class));
|
||||
factory.setContextLifecycleListeners(Arrays.asList(listeners[0], listeners[1]));
|
||||
factory.addContextLifecycleListeners(listeners[2], listeners[3]);
|
||||
this.webServer = factory.getWebServer(mock(HttpHandler.class));
|
||||
InOrder ordered = inOrder((Object[]) listeners);
|
||||
for (LifecycleListener listener : listeners) {
|
||||
then(listener).should(ordered).lifecycleEvent(any(LifecycleEvent.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void setNullConnectorCustomizersShouldThrowException() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> factory.setConnectorCustomizers(null))
|
||||
.withMessageContaining("'connectorCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNullAddConnectorCustomizersShouldThrowException() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> factory.addConnectorCustomizers((TomcatConnectorCustomizer[]) null))
|
||||
.withMessageContaining("'connectorCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setNullProtocolHandlerCustomizersShouldThrowException() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> factory.setProtocolHandlerCustomizers(null))
|
||||
.withMessageContaining("'protocolHandlerCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNullProtocolHandlerCustomizersShouldThrowException() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> factory.addProtocolHandlerCustomizers((TomcatProtocolHandlerCustomizer[]) null))
|
||||
.withMessageContaining("'protocolHandlerCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatConnectorCustomizersShouldBeInvoked() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
HttpHandler handler = mock(HttpHandler.class);
|
||||
TomcatConnectorCustomizer[] customizers = new TomcatConnectorCustomizer[4];
|
||||
Arrays.setAll(customizers, (i) -> mock(TomcatConnectorCustomizer.class));
|
||||
factory.setConnectorCustomizers(Arrays.asList(customizers[0], customizers[1]));
|
||||
factory.addConnectorCustomizers(customizers[2], customizers[3]);
|
||||
this.webServer = factory.getWebServer(handler);
|
||||
InOrder ordered = inOrder((Object[]) customizers);
|
||||
for (TomcatConnectorCustomizer customizer : customizers) {
|
||||
then(customizer).should(ordered).customize(any(Connector.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
void tomcatProtocolHandlerCustomizersShouldBeInvoked() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
HttpHandler handler = mock(HttpHandler.class);
|
||||
TomcatProtocolHandlerCustomizer<AbstractHttp11Protocol<?>>[] customizers = new TomcatProtocolHandlerCustomizer[4];
|
||||
Arrays.setAll(customizers, (i) -> mock(TomcatProtocolHandlerCustomizer.class));
|
||||
factory.setProtocolHandlerCustomizers(Arrays.asList(customizers[0], customizers[1]));
|
||||
factory.addProtocolHandlerCustomizers(customizers[2], customizers[3]);
|
||||
this.webServer = factory.getWebServer(handler);
|
||||
InOrder ordered = inOrder((Object[]) customizers);
|
||||
for (TomcatProtocolHandlerCustomizer customizer : customizers) {
|
||||
then(customizer).should(ordered).customize(any(ProtocolHandler.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatAdditionalConnectors() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
Connector[] connectors = new Connector[4];
|
||||
Arrays.setAll(connectors, (i) -> new Connector());
|
||||
factory.addAdditionalConnectors(connectors);
|
||||
this.webServer = factory.getWebServer(mock(HttpHandler.class));
|
||||
Map<Service, Connector[]> connectorsByService = TomcatAccess
|
||||
.getServiceConnectors((TomcatWebServer) this.webServer);
|
||||
assertThat(connectorsByService.values().iterator().next()).hasSize(connectors.length + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNullAdditionalConnectorsThrows() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> factory.addAdditionalConnectors((Connector[]) null))
|
||||
.withMessageContaining("'connectors' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void useForwardedHeaders() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
RemoteIpValve valve = new RemoteIpValve();
|
||||
valve.setProtocolHeader("X-Forwarded-Proto");
|
||||
factory.addEngineValves(valve);
|
||||
assertForwardHeaderIsUsed(factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void referenceClearingIsDisabled() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer(mock(HttpHandler.class));
|
||||
this.webServer.start();
|
||||
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();
|
||||
StandardContext context = (StandardContext) tomcat.getHost().findChildren()[0];
|
||||
assertThat(context.getClearReferencesRmiTargets()).isFalse();
|
||||
assertThat(context.getClearReferencesThreadLocals()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void portClashOfPrimaryConnectorResultsInPortInUseException() throws Exception {
|
||||
doWithBlockedPort((port) -> assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
factory.setPort(port);
|
||||
this.webServer = factory.getWebServer(mock(HttpHandler.class));
|
||||
this.webServer.start();
|
||||
}).satisfies((ex) -> handleExceptionCausedByBlockedPortOnPrimaryConnector(ex, port)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertThatSslWithInvalidAliasCallFails(ThrowingCallable call) {
|
||||
assertThatExceptionOfType(WebServerException.class).isThrownBy(call);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServerIsShuttingDownGracefullyThenNewConnectionsCannotBeMade() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
factory.setShutdown(Shutdown.GRACEFUL);
|
||||
BlockingHandler blockingHandler = new BlockingHandler();
|
||||
this.webServer = factory.getWebServer(blockingHandler);
|
||||
this.webServer.start();
|
||||
WebClient webClient = getWebClient(this.webServer.getPort()).build();
|
||||
this.webServer.shutDownGracefully((result) -> {
|
||||
});
|
||||
Awaitility.await().atMost(Duration.ofSeconds(30)).until(() -> {
|
||||
blockingHandler.stopBlocking();
|
||||
try {
|
||||
webClient.get().retrieve().toBodilessEntity().block();
|
||||
return false;
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
return ex.getCause() instanceof ConnectException;
|
||||
}
|
||||
});
|
||||
this.webServer.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGetTomcatWebServerIsOverriddenThenWebServerCreationCanBeCustomized() {
|
||||
AtomicReference<TomcatWebServer> webServerReference = new AtomicReference<>();
|
||||
TomcatWebServer webServer = (TomcatWebServer) new TomcatReactiveWebServerFactory() {
|
||||
|
||||
@Override
|
||||
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
|
||||
webServerReference.set(new TomcatWebServer(tomcat));
|
||||
return webServerReference.get();
|
||||
}
|
||||
|
||||
}.getWebServer(new EchoHandler());
|
||||
assertThat(webServerReference).hasValue(webServer);
|
||||
}
|
||||
|
||||
private void handleExceptionCausedByBlockedPortOnPrimaryConnector(RuntimeException ex, int blockedPort) {
|
||||
assertThat(ex).isInstanceOf(PortInUseException.class);
|
||||
assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String startedLogMessage() {
|
||||
return TomcatAccess.getStartedLogMessage((TomcatWebServer) this.webServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConnector(int port, ConfigurableReactiveWebServerFactory factory) {
|
||||
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
|
||||
connector.setPort(port);
|
||||
((TomcatReactiveWebServerFactory) factory).addAdditionalConnectors(connector);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.tomcat.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link TldPatterns}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class TldPatternsTests {
|
||||
|
||||
@Test
|
||||
void tomcatSkipAlignsWithTomcatDefaults() throws IOException {
|
||||
assertThat(TldPatterns.TOMCAT_SKIP).containsExactlyInAnyOrderElementsOf(getTomcatDefaultJarsToSkip());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatScanAlignsWithTomcatDefaults() throws IOException {
|
||||
assertThat(TldPatterns.TOMCAT_SCAN).containsExactlyInAnyOrderElementsOf(getTomcatDefaultJarsToScan());
|
||||
}
|
||||
|
||||
private Set<String> getTomcatDefaultJarsToSkip() throws IOException {
|
||||
return getTomcatDefault("tomcat.util.scan.StandardJarScanFilter.jarsToSkip");
|
||||
}
|
||||
|
||||
private Set<String> getTomcatDefaultJarsToScan() throws IOException {
|
||||
return getTomcatDefault("tomcat.util.scan.StandardJarScanFilter.jarsToScan");
|
||||
}
|
||||
|
||||
private Set<String> getTomcatDefault(String key) throws IOException {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
try (InputStream inputStream = classLoader.getResource("catalina.properties").openStream()) {
|
||||
Properties properties = new Properties();
|
||||
properties.load(inputStream);
|
||||
String jarsToSkip = properties.getProperty(key);
|
||||
return StringUtils.commaDelimitedListToSet(jarsToSkip);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,776 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.tomcat.servlet;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import jakarta.servlet.MultipartConfigElement;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRegistration.Dynamic;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.catalina.Container;
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.LifecycleEvent;
|
||||
import org.apache.catalina.LifecycleListener;
|
||||
import org.apache.catalina.LifecycleState;
|
||||
import org.apache.catalina.Service;
|
||||
import org.apache.catalina.Valve;
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.catalina.core.AprLifecycleListener;
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.core.StandardWrapper;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.util.CharsetMapper;
|
||||
import org.apache.catalina.valves.RemoteIpValve;
|
||||
import org.apache.coyote.ProtocolHandler;
|
||||
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||
import org.apache.coyote.http11.Http11Nio2Protocol;
|
||||
import org.apache.hc.client5.http.HttpHostConnectException;
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
|
||||
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.NoHttpResponseException;
|
||||
import org.apache.hc.core5.ssl.SSLContextBuilder;
|
||||
import org.apache.jasper.servlet.JspServlet;
|
||||
import org.apache.tomcat.JarScanFilter;
|
||||
import org.apache.tomcat.JarScanType;
|
||||
import org.apache.tomcat.util.scan.StandardJarScanFilter;
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
|
||||
import org.springframework.boot.testsupport.system.CapturedOutput;
|
||||
import org.springframework.boot.tomcat.ConnectorStartFailedException;
|
||||
import org.springframework.boot.tomcat.TomcatAccess;
|
||||
import org.springframework.boot.tomcat.TomcatConnectorCustomizer;
|
||||
import org.springframework.boot.tomcat.TomcatContextCustomizer;
|
||||
import org.springframework.boot.tomcat.TomcatEmbeddedContext;
|
||||
import org.springframework.boot.tomcat.TomcatProtocolHandlerCustomizer;
|
||||
import org.springframework.boot.tomcat.TomcatWebServer;
|
||||
import org.springframework.boot.web.server.PortInUseException;
|
||||
import org.springframework.boot.web.server.Shutdown;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.WebServerException;
|
||||
import org.springframework.boot.web.server.servlet.AbstractServletWebServerFactoryTests;
|
||||
import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.assertArg;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link TomcatServletWebServerFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactoryTests {
|
||||
|
||||
@Override
|
||||
protected TomcatServletWebServerFactory getFactory() {
|
||||
return new TomcatServletWebServerFactory(0);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void restoreTccl() {
|
||||
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
|
||||
}
|
||||
|
||||
// JMX MBean names clash if you get more than one Engine with the same name...
|
||||
@Test
|
||||
void tomcatEngineNames() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer();
|
||||
factory.setPort(0);
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
|
||||
// Make sure that the names are different
|
||||
String firstName = ((TomcatWebServer) this.webServer).getTomcat().getEngine().getName();
|
||||
String secondName = tomcatWebServer.getTomcat().getEngine().getName();
|
||||
assertThat(firstName).as("Tomcat engines must have different names").isNotEqualTo(secondName);
|
||||
tomcatWebServer.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultTomcatListeners() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
assertThat(factory.getContextLifecycleListeners()).isEmpty();
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
|
||||
this.webServer = tomcatWebServer;
|
||||
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aprShouldBeOptIn() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.setUseApr(true);
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
|
||||
this.webServer = tomcatWebServer;
|
||||
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).singleElement()
|
||||
.isInstanceOf(AprLifecycleListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatListeners() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
LifecycleListener[] listeners = new LifecycleListener[4];
|
||||
Arrays.setAll(listeners, (i) -> mock(LifecycleListener.class));
|
||||
factory.setContextLifecycleListeners(Arrays.asList(listeners[0], listeners[1]));
|
||||
factory.addContextLifecycleListeners(listeners[2], listeners[3]);
|
||||
this.webServer = factory.getWebServer();
|
||||
InOrder ordered = inOrder((Object[]) listeners);
|
||||
for (LifecycleListener listener : listeners) {
|
||||
then(listener).should(ordered).lifecycleEvent(any(LifecycleEvent.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatCustomizers() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
TomcatContextCustomizer[] customizers = new TomcatContextCustomizer[4];
|
||||
Arrays.setAll(customizers, (i) -> mock(TomcatContextCustomizer.class));
|
||||
factory.setContextCustomizers(Arrays.asList(customizers[0], customizers[1]));
|
||||
factory.addContextCustomizers(customizers[2], customizers[3]);
|
||||
this.webServer = factory.getWebServer();
|
||||
InOrder ordered = inOrder((Object[]) customizers);
|
||||
for (TomcatContextCustomizer customizer : customizers) {
|
||||
then(customizer).should(ordered).customize(any(Context.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void contextIsAddedToHostBeforeCustomizersAreCalled() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
TomcatContextCustomizer customizer = mock(TomcatContextCustomizer.class);
|
||||
factory.addContextCustomizers(customizer);
|
||||
this.webServer = factory.getWebServer();
|
||||
then(customizer).should().customize(assertArg((context) -> assertThat(context.getParent()).isNotNull()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatConnectorCustomizers() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
TomcatConnectorCustomizer[] customizers = new TomcatConnectorCustomizer[4];
|
||||
Arrays.setAll(customizers, (i) -> mock(TomcatConnectorCustomizer.class));
|
||||
factory.setConnectorCustomizers(Arrays.asList(customizers[0], customizers[1]));
|
||||
factory.addConnectorCustomizers(customizers[2], customizers[3]);
|
||||
this.webServer = factory.getWebServer();
|
||||
InOrder ordered = inOrder((Object[]) customizers);
|
||||
for (TomcatConnectorCustomizer customizer : customizers) {
|
||||
then(customizer).should(ordered).customize(any(Connector.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
void tomcatProtocolHandlerCustomizersShouldBeInvoked() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
TomcatProtocolHandlerCustomizer<AbstractHttp11Protocol<?>>[] customizers = new TomcatProtocolHandlerCustomizer[4];
|
||||
Arrays.setAll(customizers, (i) -> mock(TomcatProtocolHandlerCustomizer.class));
|
||||
factory.setProtocolHandlerCustomizers(Arrays.asList(customizers[0], customizers[1]));
|
||||
factory.addProtocolHandlerCustomizers(customizers[2], customizers[3]);
|
||||
this.webServer = factory.getWebServer();
|
||||
InOrder ordered = inOrder((Object[]) customizers);
|
||||
for (TomcatProtocolHandlerCustomizer customizer : customizers) {
|
||||
then(customizer).should(ordered).customize(any(ProtocolHandler.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatProtocolHandlerCanBeCustomized() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
TomcatProtocolHandlerCustomizer<AbstractHttp11Protocol<?>> customizer = (protocolHandler) -> protocolHandler
|
||||
.setProcessorCache(250);
|
||||
factory.addProtocolHandlerCustomizers(customizer);
|
||||
Tomcat tomcat = getTomcat(factory);
|
||||
Connector connector = TomcatAccess.getServiceConnectors((TomcatWebServer) this.webServer)
|
||||
.get(tomcat.getService())[0];
|
||||
AbstractHttp11Protocol<?> protocolHandler = (AbstractHttp11Protocol<?>) connector.getProtocolHandler();
|
||||
assertThat(protocolHandler.getProcessorCache()).isEqualTo(250);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatAdditionalConnectors() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
Connector[] connectors = new Connector[4];
|
||||
Arrays.setAll(connectors, (i) -> {
|
||||
Connector connector = new Connector();
|
||||
connector.setPort(0);
|
||||
return connector;
|
||||
});
|
||||
factory.addAdditionalConnectors(connectors);
|
||||
this.webServer = factory.getWebServer();
|
||||
Map<Service, Connector[]> connectorsByService = new HashMap<>(
|
||||
TomcatAccess.getServiceConnectors((TomcatWebServer) this.webServer));
|
||||
assertThat(connectorsByService.values().iterator().next()).hasSize(connectors.length + 1);
|
||||
this.webServer.start();
|
||||
this.webServer.stop();
|
||||
connectorsByService.forEach((service, serviceConnectors) -> {
|
||||
for (Connector connector : serviceConnectors) {
|
||||
assertThat(connector.getProtocolHandler()).extracting("endpoint.serverSock").isNull();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNullAdditionalConnectorThrows() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> factory.addAdditionalConnectors((Connector[]) null))
|
||||
.withMessageContaining("'connectors' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionTimeout() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.getSettings().getSession().setTimeout(Duration.ofSeconds(10));
|
||||
assertTimeout(factory, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionTimeoutInMinutes() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.getSettings().getSession().setTimeout(Duration.ofMinutes(1));
|
||||
assertTimeout(factory, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noSessionTimeout() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.getSettings().getSession().setTimeout(null);
|
||||
assertTimeout(factory, -1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void valve() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
Valve valve = mock(Valve.class);
|
||||
factory.addContextValves(valve);
|
||||
this.webServer = factory.getWebServer();
|
||||
then(valve).should().setNext(any(Valve.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setNullTomcatContextCustomizersThrows() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> factory.setContextCustomizers(null))
|
||||
.withMessageContaining("'contextCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNullContextCustomizersThrows() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> factory.addContextCustomizers((TomcatContextCustomizer[]) null))
|
||||
.withMessageContaining("'contextCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setNullTomcatConnectorCustomizersThrows() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> factory.setConnectorCustomizers(null))
|
||||
.withMessageContaining("'connectorCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNullConnectorCustomizersThrows() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> factory.addConnectorCustomizers((TomcatConnectorCustomizer[]) null))
|
||||
.withMessageContaining("'connectorCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setNullTomcatProtocolHandlerCustomizersThrows() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> factory.setProtocolHandlerCustomizers(null))
|
||||
.withMessageContaining("'protocolHandlerCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNullTomcatProtocolHandlerCustomizersThrows() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> factory.addProtocolHandlerCustomizers((TomcatProtocolHandlerCustomizer[]) null))
|
||||
.withMessageContaining("'protocolHandlerCustomizers' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void uriEncoding() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.setUriEncoding(StandardCharsets.US_ASCII);
|
||||
Tomcat tomcat = getTomcat(factory);
|
||||
Connector connector = TomcatAccess.getServiceConnectors((TomcatWebServer) this.webServer)
|
||||
.get(tomcat.getService())[0];
|
||||
assertThat(connector.getURIEncoding()).isEqualTo("US-ASCII");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultUriEncoding() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
Tomcat tomcat = getTomcat(factory);
|
||||
Connector connector = TomcatAccess.getServiceConnectors((TomcatWebServer) this.webServer)
|
||||
.get(tomcat.getService())[0];
|
||||
assertThat(connector.getURIEncoding()).isEqualTo("UTF-8");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startupFailureDoesNotResultInUnstoppedThreadsBeingReported(CapturedOutput output) throws Exception {
|
||||
super.portClashOfPrimaryConnectorResultsInPortInUseException();
|
||||
assertThat(output).doesNotContain("appears to have started a thread named [main]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void destroyCalledWithoutStart() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer(exampleServletRegistration());
|
||||
this.webServer.destroy();
|
||||
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();
|
||||
assertThat(tomcat.getServer().getState()).isSameAs(LifecycleState.DESTROYED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConnector(int port, ConfigurableServletWebServerFactory factory) {
|
||||
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
|
||||
connector.setPort(port);
|
||||
((TomcatServletWebServerFactory) factory).addAdditionalConnectors(connector);
|
||||
}
|
||||
|
||||
@Test
|
||||
void useForwardHeaders() throws Exception {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.addContextValves(new RemoteIpValve());
|
||||
assertForwardHeaderIsUsed(factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableDoesNotSaveSessionFiles() throws Exception {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
// If baseDir is not set SESSIONS.ser is written to a different temp directory
|
||||
// each time. By setting it we can really ensure that data isn't saved
|
||||
factory.setBaseDirectory(this.tempDir);
|
||||
this.webServer = factory.getWebServer(sessionServletRegistration());
|
||||
this.webServer.start();
|
||||
String s1 = getResponse(getLocalUrl("/session"));
|
||||
String s2 = getResponse(getLocalUrl("/session"));
|
||||
this.webServer.stop();
|
||||
this.webServer = factory.getWebServer(sessionServletRegistration());
|
||||
this.webServer.start();
|
||||
String s3 = getResponse(getLocalUrl("/session"));
|
||||
String message = "Session error s1=" + s1 + " s2=" + s2 + " s3=" + s3;
|
||||
assertThat(s2.split(":")[0]).as(message).isEqualTo(s1.split(":")[1]);
|
||||
assertThat(s3.split(":")[0]).as(message).isNotEqualTo(s2.split(":")[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jndiLookupsCanBePerformedDuringApplicationContextRefresh() throws NamingException {
|
||||
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
|
||||
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0) {
|
||||
|
||||
@Override
|
||||
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
|
||||
tomcat.enableNaming();
|
||||
return super.getTomcatWebServer(tomcat);
|
||||
}
|
||||
|
||||
};
|
||||
// Server is created in onRefresh
|
||||
this.webServer = factory.getWebServer();
|
||||
// Lookups should now be possible
|
||||
new InitialContext().lookup("java:comp/env");
|
||||
// Called in finishRefresh, giving us an opportunity to remove the context binding
|
||||
// and avoid a leak
|
||||
this.webServer.start();
|
||||
// Lookups should no longer be possible
|
||||
assertThatExceptionOfType(NamingException.class).isThrownBy(() -> new InitialContext().lookup("java:comp/env"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultLocaleCharsetMappingsAreOverridden() throws IOException {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer();
|
||||
// override defaults, see org.apache.catalina.util.CharsetMapperDefault.properties
|
||||
Properties charsetMapperDefault = PropertiesLoaderUtils
|
||||
.loadProperties(new ClassPathResource("CharsetMapperDefault.properties", CharsetMapper.class));
|
||||
for (String language : charsetMapperDefault.stringPropertyNames()) {
|
||||
assertThat(getCharset(new Locale(language))).isEqualTo(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void tldSkipPatternsShouldBeAppliedToContextJarScanner() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.addTldSkipPatterns("foo.jar", "bar.jar");
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();
|
||||
Context context = (Context) tomcat.getHost().findChildren()[0];
|
||||
JarScanFilter jarScanFilter = context.getJarScanner().getJarScanFilter();
|
||||
assertThat(jarScanFilter.check(JarScanType.TLD, "foo.jar")).isFalse();
|
||||
assertThat(jarScanFilter.check(JarScanType.TLD, "bar.jar")).isFalse();
|
||||
assertThat(jarScanFilter.check(JarScanType.TLD, "test.jar")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void tldScanPatternsShouldBeAppliedToContextJarScanner() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();
|
||||
Context context = (Context) tomcat.getHost().findChildren()[0];
|
||||
JarScanFilter jarScanFilter = context.getJarScanner().getJarScanFilter();
|
||||
String tldScan = ((StandardJarScanFilter) jarScanFilter).getTldScan();
|
||||
assertThat(tldScan).isEqualTo("log4j-taglib*.jar,log4j-jakarta-web*.jar,log4javascript*.jar,slf4j-taglib*.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customTomcatHttpOnlyCookie() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.getSettings().getSession().getCookie().setHttpOnly(false);
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();
|
||||
Context context = (Context) tomcat.getHost().findChildren()[0];
|
||||
assertThat(context.getUseHttpOnly()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionThrownOnLoadFailureWhenFailCtxIfServletStartFailsIsTrue() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.addContextCustomizers((context) -> {
|
||||
if (context instanceof StandardContext standardContext) {
|
||||
standardContext.setFailCtxIfServletStartFails(true);
|
||||
}
|
||||
});
|
||||
this.webServer = factory
|
||||
.getWebServer((context) -> context.addServlet("failing", FailingServlet.class).setLoadOnStartup(0));
|
||||
assertThatExceptionOfType(WebServerException.class).isThrownBy(this.webServer::start);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionThrownOnLoadFailureWhenFailCtxIfServletStartFailsIsFalse() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.addContextCustomizers((context) -> {
|
||||
if (context instanceof StandardContext standardContext) {
|
||||
standardContext.setFailCtxIfServletStartFails(false);
|
||||
}
|
||||
});
|
||||
this.webServer = factory
|
||||
.getWebServer((context) -> context.addServlet("failing", FailingServlet.class).setLoadOnStartup(0));
|
||||
this.webServer.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
void referenceClearingIsDisabled() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();
|
||||
StandardContext context = (StandardContext) tomcat.getHost().findChildren()[0];
|
||||
assertThat(context.getClearReferencesRmiTargets()).isFalse();
|
||||
assertThat(context.getClearReferencesThreadLocals()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonExistentUploadDirectoryIsCreatedUponMultipartUpload() {
|
||||
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0);
|
||||
AtomicReference<ServletContext> servletContextReference = new AtomicReference<>();
|
||||
factory.addInitializers((servletContext) -> {
|
||||
servletContextReference.set(servletContext);
|
||||
Dynamic servlet = servletContext.addServlet("upload", new HttpServlet() {
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
req.getParts();
|
||||
}
|
||||
|
||||
});
|
||||
servlet.addMapping("/upload");
|
||||
servlet.setMultipartConfig(new MultipartConfigElement((String) null));
|
||||
});
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
File temp = (File) servletContextReference.get().getAttribute(ServletContext.TEMPDIR);
|
||||
FileSystemUtils.deleteRecursively(temp);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||
body.add("file", new ByteArrayResource(new byte[1024 * 1024]));
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(getLocalUrl("/upload"), requestEntity,
|
||||
String.class);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionThrownOnContextListenerDestroysServer() {
|
||||
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0) {
|
||||
|
||||
@Override
|
||||
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
|
||||
try {
|
||||
return super.getTomcatWebServer(tomcat);
|
||||
}
|
||||
finally {
|
||||
assertThat(tomcat.getServer().getState()).isEqualTo(LifecycleState.DESTROYED);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
assertThatExceptionOfType(WebServerException.class).isThrownBy(
|
||||
() -> factory.getWebServer((context) -> context.addListener(new FailingServletContextListener())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerJspServletWithDefaultLoadOnStartup() {
|
||||
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0);
|
||||
factory.addInitializers((context) -> context.addServlet("manually-registered-jsp-servlet", JspServlet.class));
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertThatSslWithInvalidAliasCallFails(ThrowingCallable call) {
|
||||
assertThatExceptionOfType(WebServerException.class).isThrownBy(call);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServerIsShuttingDownGracefullyThenNewConnectionsCannotBeMade() throws Exception {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.setShutdown(Shutdown.GRACEFUL);
|
||||
BlockingServlet blockingServlet = new BlockingServlet();
|
||||
this.webServer = factory.getWebServer((context) -> {
|
||||
Dynamic registration = context.addServlet("blockingServlet", blockingServlet);
|
||||
registration.addMapping("/blocking");
|
||||
registration.setAsyncSupported(true);
|
||||
});
|
||||
this.webServer.start();
|
||||
int port = this.webServer.getPort();
|
||||
Future<Object> request = initiateGetRequest(port, "/blocking");
|
||||
blockingServlet.awaitQueue();
|
||||
this.webServer.shutDownGracefully((result) -> {
|
||||
});
|
||||
Object unconnectableRequest = Awaitility.await()
|
||||
.until(() -> initiateGetRequest(HttpClients.createDefault(), port, "/").get(),
|
||||
(result) -> result instanceof Exception);
|
||||
assertThat(unconnectableRequest).isInstanceOf(HttpHostConnectException.class);
|
||||
blockingServlet.admitOne();
|
||||
assertThat(request.get()).isInstanceOf(HttpResponse.class);
|
||||
this.webServer.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServerIsShuttingDownARequestOnAnIdleConnectionResultsInConnectionReset() throws Exception {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.setShutdown(Shutdown.GRACEFUL);
|
||||
BlockingServlet blockingServlet = new BlockingServlet();
|
||||
this.webServer = factory.getWebServer((context) -> {
|
||||
Dynamic registration = context.addServlet("blockingServlet", blockingServlet);
|
||||
registration.addMapping("/blocking");
|
||||
registration.setAsyncSupported(true);
|
||||
});
|
||||
HttpClient httpClient = HttpClients.createMinimal();
|
||||
this.webServer.start();
|
||||
int port = this.webServer.getPort();
|
||||
Future<Object> keepAliveRequest = initiateGetRequest(httpClient, port, "/blocking");
|
||||
blockingServlet.awaitQueue();
|
||||
blockingServlet.admitOne();
|
||||
assertThat(keepAliveRequest.get()).isInstanceOf(HttpResponse.class);
|
||||
Future<Object> request = initiateGetRequest(port, "/blocking");
|
||||
blockingServlet.awaitQueue();
|
||||
this.webServer.shutDownGracefully((result) -> {
|
||||
});
|
||||
Object idleConnectionRequestResult = Awaitility.await().until(() -> {
|
||||
Future<Object> idleConnectionRequest = initiateGetRequest(httpClient, port, "/");
|
||||
Object result = idleConnectionRequest.get();
|
||||
return result;
|
||||
}, (result) -> result instanceof Exception);
|
||||
assertThat(idleConnectionRequestResult).isInstanceOfAny(SocketException.class, NoHttpResponseException.class);
|
||||
if (idleConnectionRequestResult instanceof SocketException socketException) {
|
||||
assertThat(socketException).hasMessage("Connection reset");
|
||||
}
|
||||
blockingServlet.admitOne();
|
||||
Object response = request.get();
|
||||
assertThat(response).isInstanceOf(HttpResponse.class);
|
||||
this.webServer.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources({ "1.crt", "1.key", "2.crt", "2.key" })
|
||||
void shouldUpdateSslWhenReloadingSslBundles() throws Exception {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
addTestTxtFile(factory);
|
||||
DefaultSslBundleRegistry bundles = new DefaultSslBundleRegistry("test",
|
||||
createPemSslBundle("classpath:1.crt", "classpath:1.key"));
|
||||
factory.setSslBundles(bundles);
|
||||
factory.setSsl(Ssl.forBundle("test"));
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
RememberingHostnameVerifier verifier = new RememberingHostnameVerifier();
|
||||
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
|
||||
TlsSocketStrategy tlsSocketStrategy = new DefaultClientTlsStrategy(sslContext, verifier);
|
||||
HttpComponentsClientHttpRequestFactory requestFactory = createHttpComponentsRequestFactory(tlsSocketStrategy);
|
||||
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory)).isEqualTo("test");
|
||||
assertThat(verifier.getLastPrincipal()).isEqualTo("CN=1");
|
||||
requestFactory = createHttpComponentsRequestFactory(tlsSocketStrategy);
|
||||
bundles.updateBundle("test", createPemSslBundle("classpath:2.crt", "classpath:2.key"));
|
||||
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory)).isEqualTo("test");
|
||||
assertThat(verifier.getLastPrincipal()).isEqualTo("CN=2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslWithHttp11Nio2Protocol() throws Exception {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
addTestTxtFile(factory);
|
||||
factory.setProtocol(Http11Nio2Protocol.class.getName());
|
||||
factory.setSsl(getSsl(null, "password", "classpath:test.jks"));
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
HttpComponentsClientHttpRequestFactory requestFactory = createHttpComponentsRequestFactory(
|
||||
createTrustSelfSignedTlsSocketStrategy());
|
||||
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory)).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JspServlet getJspServlet() throws ServletException {
|
||||
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();
|
||||
Container container = tomcat.getHost().findChildren()[0];
|
||||
StandardWrapper standardWrapper = (StandardWrapper) container.findChild("jsp");
|
||||
if (standardWrapper == null) {
|
||||
return null;
|
||||
}
|
||||
standardWrapper.load();
|
||||
return (JspServlet) standardWrapper.getServlet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, String> getActualMimeMappings() {
|
||||
Context context = (Context) ((TomcatWebServer) this.webServer).getTomcat().getHost().findChildren()[0];
|
||||
Map<String, String> mimeMappings = new HashMap<>();
|
||||
for (String extension : context.findMimeMappings()) {
|
||||
mimeMappings.put(extension, context.findMimeMapping(extension));
|
||||
}
|
||||
return mimeMappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Charset getCharset(Locale locale) {
|
||||
Context context = (Context) ((TomcatWebServer) this.webServer).getTomcat().getHost().findChildren()[0];
|
||||
CharsetMapper mapper = ((TomcatEmbeddedContext) context).getCharsetMapper();
|
||||
String charsetName = mapper.getCharset(locale);
|
||||
return (charsetName != null) ? Charset.forName(charsetName) : null;
|
||||
}
|
||||
|
||||
private void assertTimeout(TomcatServletWebServerFactory factory, int expected) {
|
||||
Tomcat tomcat = getTomcat(factory);
|
||||
Context context = (Context) tomcat.getHost().findChildren()[0];
|
||||
assertThat(context.getSessionTimeout()).isEqualTo(expected);
|
||||
}
|
||||
|
||||
private Tomcat getTomcat(TomcatServletWebServerFactory factory) {
|
||||
this.webServer = factory.getWebServer();
|
||||
return ((TomcatWebServer) this.webServer).getTomcat();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleExceptionCausedByBlockedPortOnPrimaryConnector(RuntimeException ex, int blockedPort) {
|
||||
assertThat(ex).isInstanceOf(PortInUseException.class);
|
||||
assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleExceptionCausedByBlockedPortOnSecondaryConnector(RuntimeException ex, int blockedPort) {
|
||||
assertThat(ex).isInstanceOf(ConnectorStartFailedException.class);
|
||||
assertThat(((ConnectorStartFailedException) ex).getPort()).isEqualTo(blockedPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String startedLogMessage() {
|
||||
return TomcatAccess.getStartedLogMessage((TomcatWebServer) this.webServer);
|
||||
}
|
||||
|
||||
private static final class RememberingHostnameVerifier implements HostnameVerifier {
|
||||
|
||||
private volatile String lastPrincipal;
|
||||
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
try {
|
||||
this.lastPrincipal = session.getPeerPrincipal().getName();
|
||||
}
|
||||
catch (SSLPeerUnverifiedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String getLastPrincipal() {
|
||||
return this.lastPrincipal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBLjCB4aADAgECAhQ25wrNnapZEkFc8kgf5NDHXKxnTzAFBgMrZXAwDDEKMAgG
|
||||
A1UEAwwBMTAgFw0yMzEwMTAwODU1MTJaGA8yMTIzMDkxNjA4NTUxMlowDDEKMAgG
|
||||
A1UEAwwBMTAqMAUGAytlcAMhAOyxNxHzcNj7xTkcjVLI09sYUGUGIvdV5s0YWXT8
|
||||
XAiwo1MwUTAdBgNVHQ4EFgQUmm23oLIu5MgdBb/snZSuE+MrRZ0wHwYDVR0jBBgw
|
||||
FoAUmm23oLIu5MgdBb/snZSuE+MrRZ0wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXAD
|
||||
QQA2KMpIyySC8u4onW2MVW1iK2dJJZbMRaNMLlQuE+ZIHQLwflYW4sH/Pp76pboc
|
||||
QhqKXcO7xH7f2tD5hE2izcUB
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIJb1A+i5bmilBD9mUbhk1oFVI6FAZQGnhduv7xV6WWEc
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,9 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBLjCB4aADAgECAhR4TMDk3qg5sKREp16lEHR3bV3M9zAFBgMrZXAwDDEKMAgG
|
||||
A1UEAwwBMjAgFw0yMzEwMTAwODU1MjBaGA8yMTIzMDkxNjA4NTUyMFowDDEKMAgG
|
||||
A1UEAwwBMjAqMAUGAytlcAMhADPft6hzyCjHCe5wSprChuuO/CuPIJ2t+l4roS1D
|
||||
43/wo1MwUTAdBgNVHQ4EFgQUfrRibAWml4Ous4kpnBIggM2xnLcwHwYDVR0jBBgw
|
||||
FoAUfrRibAWml4Ous4kpnBIggM2xnLcwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXAD
|
||||
QQC/MOclal2Cp0B3kmaLbK0M8mapclIOJa78hzBkqPA3URClAF2GmF187wHqi7qV
|
||||
+xZ+KWv26pLJR46vk8Kc6ZIO
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEICxhres2Z2lICm7/isnm+2iNR12GmgG7KK86BNDZDeIF
|
||||
-----END PRIVATE KEY-----
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user