Create spring-boot-web-server module
This commit is contained in:
committed by
Phillip Webb
parent
0cf76bf43b
commit
96bee8e034
@@ -0,0 +1,822 @@
|
||||
/*
|
||||
* 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.web.server.reactive;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.StringRequestContent;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
import reactor.netty.NettyPipeline;
|
||||
import reactor.netty.http.Http11SslContextSpec;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.tcp.SslProvider.GenericSslContextSpec;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.boot.testsupport.classpath.resources.ResourcePath;
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
|
||||
import org.springframework.boot.web.server.Compression;
|
||||
import org.springframework.boot.web.server.GracefulShutdownResult;
|
||||
import org.springframework.boot.web.server.Http2;
|
||||
import org.springframework.boot.web.server.Shutdown;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
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.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientRequestException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Base for testing classes that extends {@link AbstractReactiveWebServerFactory}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
public abstract class AbstractReactiveWebServerFactoryTests {
|
||||
|
||||
protected WebServer webServer;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (this.webServer != null) {
|
||||
try {
|
||||
this.webServer.stop();
|
||||
try {
|
||||
this.webServer.destroy();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ConfigurableReactiveWebServerFactory getFactory();
|
||||
|
||||
@Test
|
||||
void specificPort() throws Exception {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
int specificPort = doWithRetry(() -> {
|
||||
factory.setPort(0);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
return this.webServer.getPort();
|
||||
});
|
||||
Mono<String> result = getWebClient(this.webServer.getPort()).build()
|
||||
.post()
|
||||
.uri("/test")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.body(BodyInserters.fromValue("Hello World"))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
assertThat(result.block(Duration.ofSeconds(30))).isEqualTo("Hello World");
|
||||
assertThat(this.webServer.getPort()).isEqualTo(specificPort);
|
||||
}
|
||||
|
||||
@Test
|
||||
protected void restartAfterStop() throws Exception {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
int port = this.webServer.getPort();
|
||||
assertThat(getResponse(port, "/test")).isEqualTo("Hello World");
|
||||
this.webServer.stop();
|
||||
assertThatException().isThrownBy(() -> getResponse(port, "/test"));
|
||||
this.webServer.start();
|
||||
assertThat(getResponse(this.webServer.getPort(), "/test")).isEqualTo("Hello World");
|
||||
}
|
||||
|
||||
private String getResponse(int port, String uri) {
|
||||
WebClient webClient = getWebClient(port).build();
|
||||
Mono<String> result = webClient.post()
|
||||
.uri(uri)
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.body(BodyInserters.fromValue("Hello World"))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
return result.block(Duration.ofSeconds(30));
|
||||
}
|
||||
|
||||
@Test
|
||||
void portIsMinusOneWhenConnectionIsClosed() {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
assertThat(this.webServer.getPort()).isGreaterThan(0);
|
||||
this.webServer.destroy();
|
||||
assertThat(this.webServer.getPort()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void basicSslFromClassPath() {
|
||||
testBasicSslWithKeyStore("classpath:test.jks", "password");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void basicSslFromFileSystem(@ResourcePath("test.jks") String keyStore) {
|
||||
testBasicSslWithKeyStore(keyStore, "password");
|
||||
|
||||
}
|
||||
|
||||
protected final void testBasicSslWithKeyStore(String keyStore, String keyPassword) {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore(keyStore);
|
||||
ssl.setKeyPassword(keyPassword);
|
||||
ssl.setKeyStorePassword("secret");
|
||||
factory.setSsl(ssl);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
ReactorClientHttpConnector connector = buildTrustAllSslConnector();
|
||||
WebClient client = WebClient.builder()
|
||||
.baseUrl("https://localhost:" + this.webServer.getPort())
|
||||
.clientConnector(connector)
|
||||
.build();
|
||||
Mono<String> result = client.post()
|
||||
.uri("/test")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.body(BodyInserters.fromValue("Hello World"))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
assertThat(result.block(Duration.ofSeconds(30))).isEqualTo("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslWithValidAlias() {
|
||||
String keyStore = "classpath:test.jks";
|
||||
String keyPassword = "password";
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore(keyStore);
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setKeyPassword(keyPassword);
|
||||
ssl.setKeyAlias("test-alias");
|
||||
factory.setSsl(ssl);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
ReactorClientHttpConnector connector = buildTrustAllSslConnector();
|
||||
WebClient client = WebClient.builder()
|
||||
.baseUrl("https://localhost:" + this.webServer.getPort())
|
||||
.clientConnector(connector)
|
||||
.build();
|
||||
|
||||
Mono<String> result = client.post()
|
||||
.uri("/test")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.body(BodyInserters.fromValue("Hello World"))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
|
||||
StepVerifier.create(result).expectNext("Hello World").expectComplete().verify(Duration.ofSeconds(30));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslWithInvalidAliasFailsDuringStartup() {
|
||||
String keyStore = "classpath:test.jks";
|
||||
String keyPassword = "password";
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore(keyStore);
|
||||
ssl.setKeyPassword(keyPassword);
|
||||
ssl.setKeyAlias("test-alias-404");
|
||||
factory.setSsl(ssl);
|
||||
assertThatSslWithInvalidAliasCallFails(() -> factory.getWebServer(new EchoHandler()).start());
|
||||
}
|
||||
|
||||
protected void assertThatSslWithInvalidAliasCallFails(ThrowingCallable call) {
|
||||
assertThatException().isThrownBy(call)
|
||||
.withStackTraceContaining("Keystore does not contain alias 'test-alias-404'");
|
||||
}
|
||||
|
||||
protected ReactorClientHttpConnector buildTrustAllSslConnector() {
|
||||
GenericSslContextSpec<?> sslContextSpec = Http11SslContextSpec.forClient()
|
||||
.configure((builder) -> builder.sslProvider(SslProvider.JDK)
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE));
|
||||
HttpClient client = HttpClient.create().wiretap(true).secure((spec) -> spec.sslContext(sslContextSpec));
|
||||
return new ReactorClientHttpConnector(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslWantsClientAuthenticationSucceedsWithClientCertificate() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(Ssl.ClientAuth.WANT);
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
testClientAuthSuccess(ssl, buildTrustAllSslWithClientKeyConnector("test.jks", "password"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslWantsClientAuthenticationSucceedsWithoutClientCertificate() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(Ssl.ClientAuth.WANT);
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
testClientAuthSuccess(ssl, buildTrustAllSslConnector());
|
||||
}
|
||||
|
||||
protected ReactorClientHttpConnector buildTrustAllSslWithClientKeyConnector(String keyStore,
|
||||
String keyStorePassword) throws Exception {
|
||||
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
try (InputStream stream = new ClassPathResource(keyStore).getInputStream()) {
|
||||
clientKeyStore.load(stream, "secret".toCharArray());
|
||||
}
|
||||
KeyManagerFactory clientKeyManagerFactory = KeyManagerFactory
|
||||
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
clientKeyManagerFactory.init(clientKeyStore, keyStorePassword.toCharArray());
|
||||
|
||||
GenericSslContextSpec<?> sslContextSpec = Http11SslContextSpec.forClient()
|
||||
.configure((builder) -> builder.sslProvider(SslProvider.JDK)
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.keyManager(clientKeyManagerFactory));
|
||||
HttpClient client = HttpClient.create().wiretap(true).secure((spec) -> spec.sslContext(sslContextSpec));
|
||||
return new ReactorClientHttpConnector(client);
|
||||
}
|
||||
|
||||
protected void testClientAuthSuccess(Ssl sslConfiguration, ReactorClientHttpConnector clientConnector) {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
factory.setSsl(sslConfiguration);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
WebClient client = WebClient.builder()
|
||||
.baseUrl("https://localhost:" + this.webServer.getPort())
|
||||
.clientConnector(clientConnector)
|
||||
.build();
|
||||
Mono<String> result = client.post()
|
||||
.uri("/test")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.body(BodyInserters.fromValue("Hello World"))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
assertThat(result.block(Duration.ofSeconds(30))).isEqualTo("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslNeedsClientAuthenticationSucceedsWithClientCertificate() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(Ssl.ClientAuth.NEED);
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
testClientAuthSuccess(ssl, buildTrustAllSslWithClientKeyConnector("test.jks", "password"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void sslNeedsClientAuthenticationFailsWithoutClientCertificate() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(Ssl.ClientAuth.NEED);
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
testClientAuthFailure(ssl, buildTrustAllSslConnector());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources({ "test-cert.pem", "test-key.pem", "test.p12" })
|
||||
void sslWithPemCertificates() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(Ssl.ClientAuth.NEED);
|
||||
ssl.setCertificate("classpath:test-cert.pem");
|
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem");
|
||||
ssl.setTrustCertificate("classpath:test-cert.pem");
|
||||
testClientAuthSuccess(ssl, buildTrustAllSslWithClientKeyConnector("test.p12", "secret"));
|
||||
}
|
||||
|
||||
protected void testClientAuthFailure(Ssl sslConfiguration, ReactorClientHttpConnector clientConnector) {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
factory.setSsl(sslConfiguration);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
WebClient client = WebClient.builder()
|
||||
.baseUrl("https://localhost:" + this.webServer.getPort())
|
||||
.clientConnector(clientConnector)
|
||||
.build();
|
||||
Mono<String> result = client.post()
|
||||
.uri("/test")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.body(BodyInserters.fromValue("Hello World"))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
StepVerifier.create(result).expectError(WebClientRequestException.class).verify(Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
protected WebClient.Builder getWebClient(int port) {
|
||||
return getWebClient(HttpClient.create().wiretap(true), port);
|
||||
}
|
||||
|
||||
protected WebClient.Builder getWebClient(HttpClient client, int port) {
|
||||
InetSocketAddress address = new InetSocketAddress(port);
|
||||
String baseUrl = "http://" + address.getHostString() + ":" + address.getPort();
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(client)).baseUrl(baseUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
protected void compressionOfResponseToGetRequest() {
|
||||
WebClient client = prepareCompressionTest();
|
||||
ResponseEntity<Void> response = client.get().retrieve().toBodilessEntity().block(Duration.ofSeconds(30));
|
||||
assertResponseIsCompressed(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
protected void compressionOfResponseToPostRequest() {
|
||||
WebClient client = prepareCompressionTest();
|
||||
ResponseEntity<Void> response = client.post().retrieve().toBodilessEntity().block(Duration.ofSeconds(30));
|
||||
assertResponseIsCompressed(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noCompressionForSmallResponse() {
|
||||
Compression compression = new Compression();
|
||||
compression.setEnabled(true);
|
||||
compression.setMinResponseSize(DataSize.ofBytes(3001));
|
||||
WebClient client = prepareCompressionTest(compression);
|
||||
ResponseEntity<Void> response = client.get().retrieve().toBodilessEntity().block(Duration.ofSeconds(30));
|
||||
assertResponseIsNotCompressed(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noCompressionForMimeType() {
|
||||
Compression compression = new Compression();
|
||||
compression.setEnabled(true);
|
||||
compression.setMimeTypes(new String[] { "application/json" });
|
||||
WebClient client = prepareCompressionTest(compression);
|
||||
ResponseEntity<Void> response = client.get().retrieve().toBodilessEntity().block(Duration.ofSeconds(30));
|
||||
assertResponseIsNotCompressed(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
protected void noCompressionForUserAgent() {
|
||||
Compression compression = new Compression();
|
||||
compression.setEnabled(true);
|
||||
compression.setExcludedUserAgents(new String[] { "testUserAgent" });
|
||||
WebClient client = prepareCompressionTest(compression);
|
||||
ResponseEntity<Void> response = client.get()
|
||||
.header("User-Agent", "testUserAgent")
|
||||
.retrieve()
|
||||
.toBodilessEntity()
|
||||
.block(Duration.ofSeconds(30));
|
||||
assertResponseIsNotCompressed(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noCompressionForResponseWithInvalidContentType() {
|
||||
Compression compression = new Compression();
|
||||
compression.setEnabled(true);
|
||||
compression.setMimeTypes(new String[] { "application/json" });
|
||||
WebClient client = prepareCompressionTest(compression, "test~plain");
|
||||
ResponseEntity<Void> response = client.get().retrieve().toBodilessEntity().block(Duration.ofSeconds(30));
|
||||
assertResponseIsNotCompressed(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenSslIsEnabledAndNoKeyStoreIsConfiguredThenServerFailsToStart() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> testBasicSslWithKeyStore(null, null))
|
||||
.withMessageContaining("SSL is enabled but no trust material is configured");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenThereAreNoInFlightRequestsShutDownGracefullyReturnsTrueBeforePeriodElapses() throws Exception {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
factory.setShutdown(Shutdown.GRACEFUL);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
AtomicReference<GracefulShutdownResult> result = new AtomicReference<>();
|
||||
this.webServer.shutDownGracefully(result::set);
|
||||
Awaitility.await().atMost(Duration.ofSeconds(30)).until(() -> GracefulShutdownResult.IDLE == result.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenARequestRemainsInFlightThenShutDownGracefullyDoesNotInvokeCallbackUntilTheRequestCompletes()
|
||||
throws Exception {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
factory.setShutdown(Shutdown.GRACEFUL);
|
||||
BlockingHandler blockingHandler = new BlockingHandler();
|
||||
this.webServer = factory.getWebServer(blockingHandler);
|
||||
this.webServer.start();
|
||||
Mono<ResponseEntity<Void>> request = getWebClient(this.webServer.getPort()).build()
|
||||
.get()
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
AtomicReference<ResponseEntity<Void>> responseReference = new AtomicReference<>();
|
||||
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||
request.subscribe((response) -> {
|
||||
responseReference.set(response);
|
||||
responseLatch.countDown();
|
||||
});
|
||||
blockingHandler.awaitQueue();
|
||||
AtomicReference<GracefulShutdownResult> result = new AtomicReference<>();
|
||||
this.webServer.shutDownGracefully(result::set);
|
||||
assertThat(responseReference.get()).isNull();
|
||||
blockingHandler.completeOne();
|
||||
assertThat(responseLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||
Awaitility.await().atMost(Duration.ofSeconds(30)).until(() -> GracefulShutdownResult.IDLE == result.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenAnInflightRequestWhenTheServerIsStoppedThenGracefulShutdownCallbackIsCalledWithRequestsActive()
|
||||
throws Exception {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
factory.setShutdown(Shutdown.GRACEFUL);
|
||||
BlockingHandler blockingHandler = new BlockingHandler();
|
||||
this.webServer = factory.getWebServer(blockingHandler);
|
||||
this.webServer.start();
|
||||
Mono<ResponseEntity<Void>> request = getWebClient(this.webServer.getPort()).build()
|
||||
.get()
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
AtomicReference<ResponseEntity<Void>> responseReference = new AtomicReference<>();
|
||||
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||
request.subscribe((response) -> {
|
||||
responseReference.set(response);
|
||||
responseLatch.countDown();
|
||||
});
|
||||
blockingHandler.awaitQueue();
|
||||
AtomicReference<GracefulShutdownResult> result = new AtomicReference<>();
|
||||
this.webServer.shutDownGracefully(result::set);
|
||||
assertThat(responseReference.get()).isNull();
|
||||
try {
|
||||
this.webServer.stop();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Continue
|
||||
}
|
||||
System.out.println("Stopped");
|
||||
Awaitility.await()
|
||||
.atMost(Duration.ofSeconds(5))
|
||||
.until(() -> GracefulShutdownResult.REQUESTS_ACTIVE == result.get());
|
||||
blockingHandler.completeOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenARequestIsActiveAfterGracefulShutdownEndsThenStopWillComplete() throws InterruptedException {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
factory.setShutdown(Shutdown.GRACEFUL);
|
||||
BlockingHandler blockingHandler = new BlockingHandler();
|
||||
this.webServer = factory.getWebServer(blockingHandler);
|
||||
this.webServer.start();
|
||||
Mono<ResponseEntity<Void>> request = getWebClient(this.webServer.getPort()).build()
|
||||
.get()
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
AtomicReference<ResponseEntity<Void>> responseReference = new AtomicReference<>();
|
||||
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||
request.subscribe((response) -> {
|
||||
responseReference.set(response);
|
||||
responseLatch.countDown();
|
||||
});
|
||||
blockingHandler.awaitQueue();
|
||||
AtomicReference<GracefulShutdownResult> result = new AtomicReference<>();
|
||||
this.webServer.shutDownGracefully(result::set);
|
||||
this.webServer.stop();
|
||||
Awaitility.await()
|
||||
.atMost(Duration.ofSeconds(30))
|
||||
.until(() -> GracefulShutdownResult.REQUESTS_ACTIVE == result.get());
|
||||
blockingHandler.completeOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenARequestIsActiveThenStopWillComplete() throws InterruptedException {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
BlockingHandler blockingHandler = new BlockingHandler();
|
||||
this.webServer = factory.getWebServer(blockingHandler);
|
||||
this.webServer.start();
|
||||
Mono<ResponseEntity<Void>> request = getWebClient(this.webServer.getPort()).build()
|
||||
.get()
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
AtomicReference<ResponseEntity<Void>> responseReference = new AtomicReference<>();
|
||||
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||
request.subscribe((response) -> {
|
||||
responseReference.set(response);
|
||||
responseLatch.countDown();
|
||||
});
|
||||
blockingHandler.awaitQueue();
|
||||
try {
|
||||
this.webServer.stop();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Continue
|
||||
}
|
||||
blockingHandler.completeOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
protected void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed() throws Exception {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
Http2 http2 = new Http2();
|
||||
http2.setEnabled(true);
|
||||
factory.setHttp2(http2);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
|
||||
try (org.eclipse.jetty.client.HttpClient client = new org.eclipse.jetty.client.HttpClient(
|
||||
new HttpClientTransportOverHTTP2(new HTTP2Client()))) {
|
||||
client.start();
|
||||
ContentResponse response = client.POST("http://localhost:" + this.webServer.getPort())
|
||||
.body(new StringRequestContent("text/plain", "Hello World"))
|
||||
.send();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(response.getContentAsString()).isEqualTo("Hello World");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
protected void whenHttp2IsEnabledAndSslIsDisabledThenHttp11CanStillBeUsed() {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
Http2 http2 = new Http2();
|
||||
http2.setEnabled(true);
|
||||
factory.setHttp2(http2);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
Mono<String> result = getWebClient(this.webServer.getPort()).build()
|
||||
.post()
|
||||
.uri("/test")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.body(BodyInserters.fromValue("Hello World"))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
assertThat(result.block(Duration.ofSeconds(30))).isEqualTo("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startedLogMessageWithSinglePort() {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
assertThat(startedLogMessage()).matches(
|
||||
"(Jetty|Netty|Tomcat|Undertow) started on port " + this.webServer.getPort() + " \\(http(/1.1)?\\)");
|
||||
}
|
||||
|
||||
@Test
|
||||
protected void startedLogMessageWithMultiplePorts() {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
addConnector(0, factory);
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on ports " + this.webServer.getPort()
|
||||
+ " \\(http(/1.1)?\\), [0-9]+ \\(http(/1.1)?\\)");
|
||||
}
|
||||
|
||||
protected WebClient prepareCompressionTest() {
|
||||
Compression compression = new Compression();
|
||||
compression.setEnabled(true);
|
||||
return prepareCompressionTest(compression);
|
||||
}
|
||||
|
||||
protected WebClient prepareCompressionTest(Compression compression) {
|
||||
return prepareCompressionTest(compression, MediaType.TEXT_PLAIN_VALUE);
|
||||
}
|
||||
|
||||
protected WebClient prepareCompressionTest(Compression compression, String responseContentType) {
|
||||
ConfigurableReactiveWebServerFactory factory = getFactory();
|
||||
factory.setCompression(compression);
|
||||
this.webServer = factory.getWebServer(new CharsHandler(3000, responseContentType));
|
||||
this.webServer.start();
|
||||
|
||||
HttpClient client = HttpClient.create()
|
||||
.wiretap(true)
|
||||
.compress(true)
|
||||
.doOnConnected((connection) -> connection.channel()
|
||||
.pipeline()
|
||||
.addBefore(NettyPipeline.HttpDecompressor, "CompressionTest", new CompressionDetectionHandler()));
|
||||
return getWebClient(client, this.webServer.getPort()).build();
|
||||
}
|
||||
|
||||
protected void assertResponseIsCompressed(ResponseEntity<Void> response) {
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getHeaders().getFirst("X-Test-Compressed")).isEqualTo("true");
|
||||
}
|
||||
|
||||
protected void assertResponseIsNotCompressed(ResponseEntity<Void> response) {
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getHeaders().headerNames()).doesNotContain("X-Test-Compressed");
|
||||
}
|
||||
|
||||
protected void assertForwardHeaderIsUsed(ConfigurableReactiveWebServerFactory factory) {
|
||||
this.webServer = factory.getWebServer(new XForwardedHandler());
|
||||
this.webServer.start();
|
||||
String body = getWebClient(this.webServer.getPort()).build()
|
||||
.get()
|
||||
.header("X-Forwarded-Proto", "https")
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block(Duration.ofSeconds(30));
|
||||
assertThat(body).isEqualTo("https");
|
||||
}
|
||||
|
||||
private <T> T doWithRetry(Callable<T> action) throws Exception {
|
||||
Exception lastFailure = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
try {
|
||||
return action.call();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
lastFailure = ex;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Action was not successful in 10 attempts", lastFailure);
|
||||
}
|
||||
|
||||
protected final void doWithBlockedPort(BlockedPortAction action) throws Exception {
|
||||
ServerSocket serverSocket = new ServerSocket();
|
||||
try (serverSocket) {
|
||||
int blockedPort = doWithRetry(() -> {
|
||||
serverSocket.bind(null);
|
||||
return serverSocket.getLocalPort();
|
||||
});
|
||||
action.run(blockedPort);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract String startedLogMessage();
|
||||
|
||||
protected abstract void addConnector(int port, ConfigurableReactiveWebServerFactory factory);
|
||||
|
||||
public interface BlockedPortAction {
|
||||
|
||||
void run(int port);
|
||||
|
||||
}
|
||||
|
||||
protected static class EchoHandler implements HttpHandler {
|
||||
|
||||
public EchoHandler() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
response.setStatusCode(HttpStatus.OK);
|
||||
return response.writeWith(request.getBody());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class BlockingHandler implements HttpHandler {
|
||||
|
||||
private final BlockingQueue<Sinks.Empty<Void>> processors = new ArrayBlockingQueue<>(10);
|
||||
|
||||
private volatile boolean blocking = true;
|
||||
|
||||
public BlockingHandler() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
if (this.blocking) {
|
||||
Sinks.Empty<Void> completion = Sinks.empty();
|
||||
this.processors.add(completion);
|
||||
return completion.asMono().then(Mono.empty());
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
public void completeOne() {
|
||||
try {
|
||||
Sinks.Empty<Void> processor = this.processors.take();
|
||||
processor.tryEmitEmpty();
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public void awaitQueue() throws InterruptedException {
|
||||
while (this.processors.isEmpty()) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopBlocking() {
|
||||
this.blocking = false;
|
||||
this.processors.forEach(Sinks.Empty::tryEmitEmpty);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CompressionDetectionHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (msg instanceof HttpResponse response) {
|
||||
boolean compressed = response.headers().contains(HttpHeaderNames.CONTENT_ENCODING, "gzip", true);
|
||||
if (compressed) {
|
||||
response.headers().set("X-Test-Compressed", "true");
|
||||
}
|
||||
}
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CharsHandler implements HttpHandler {
|
||||
|
||||
private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
|
||||
private final DataBuffer bytes;
|
||||
|
||||
private final String mediaType;
|
||||
|
||||
CharsHandler(int contentSize, String mediaType) {
|
||||
char[] chars = new char[contentSize];
|
||||
Arrays.fill(chars, 'F');
|
||||
this.bytes = factory.wrap(new String(chars).getBytes(StandardCharsets.UTF_8));
|
||||
this.mediaType = mediaType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
response.setStatusCode(HttpStatus.OK);
|
||||
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, this.mediaType);
|
||||
response.getHeaders().setContentLength(this.bytes.readableByteCount());
|
||||
return response.writeWith(Mono.just(this.bytes));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class XForwardedHandler implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
String scheme = request.getURI().getScheme();
|
||||
DataBufferFactory bufferFactory = new DefaultDataBufferFactory();
|
||||
DataBuffer buffer = bufferFactory.wrap(scheme.getBytes(StandardCharsets.UTF_8));
|
||||
return response.writeWith(Mono.just(buffer));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.web.server.reactive;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
|
||||
/**
|
||||
* A mock reactive {@link WebServer}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class MockReactiveWebServer implements WebServer {
|
||||
|
||||
private final int port;
|
||||
|
||||
private HttpHandler httpHandler;
|
||||
|
||||
private Map<String, HttpHandler> httpHandlerMap;
|
||||
|
||||
MockReactiveWebServer(HttpHandler httpHandler, int port) {
|
||||
this.httpHandler = httpHandler;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
MockReactiveWebServer(Map<String, HttpHandler> httpHandlerMap, int port) {
|
||||
this.httpHandlerMap = httpHandlerMap;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public HttpHandler getHttpHandler() {
|
||||
return this.httpHandler;
|
||||
}
|
||||
|
||||
public Map<String, HttpHandler> getHttpHandlerMap() {
|
||||
return this.httpHandlerMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.web.server.reactive;
|
||||
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
/**
|
||||
* Mock {@link ReactiveWebServerFactory}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class MockReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
|
||||
|
||||
private MockReactiveWebServer webServer;
|
||||
|
||||
@Override
|
||||
public WebServer getWebServer(HttpHandler httpHandler) {
|
||||
this.webServer = spy(new MockReactiveWebServer(httpHandler, getPort()));
|
||||
return this.webServer;
|
||||
}
|
||||
|
||||
public MockReactiveWebServer getWebServer() {
|
||||
return this.webServer;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.web.server.servlet;
|
||||
|
||||
import jakarta.servlet.ServletContextEvent;
|
||||
import jakarta.servlet.ServletContextListener;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.testsupport.classpath.ForkedClassPath;
|
||||
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Base class for tests for {@link WebServer}s driving {@link ServletContextListener}s
|
||||
* correctly.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@DirtiesUrlFactories
|
||||
public abstract class AbstractServletWebServerServletContextListenerTests {
|
||||
|
||||
private final Class<?> webServerConfiguration;
|
||||
|
||||
protected AbstractServletWebServerServletContextListenerTests(Class<?> webServerConfiguration) {
|
||||
this.webServerConfiguration = webServerConfiguration;
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForkedClassPath
|
||||
void registeredServletContextListenerBeanIsCalled() {
|
||||
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(
|
||||
ServletListenerRegistrationBeanConfiguration.class, this.webServerConfiguration);
|
||||
ServletContextListener servletContextListener = (ServletContextListener) context
|
||||
.getBean("registration", ServletListenerRegistrationBean.class)
|
||||
.getListener();
|
||||
then(servletContextListener).should().contextInitialized(any(ServletContextEvent.class));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForkedClassPath
|
||||
void servletContextListenerBeanIsCalled() {
|
||||
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(
|
||||
ServletContextListenerBeanConfiguration.class, this.webServerConfiguration);
|
||||
ServletContextListener servletContextListener = context.getBean("servletContextListener",
|
||||
ServletContextListener.class);
|
||||
then(servletContextListener).should().contextInitialized(any(ServletContextEvent.class));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ServletContextListenerBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
ServletContextListener servletContextListener() {
|
||||
return mock(ServletContextListener.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ServletListenerRegistrationBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
ServletListenerRegistrationBean<ServletContextListener> registration() {
|
||||
return new ServletListenerRegistrationBean<>(mock(ServletContextListener.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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.web.server.servlet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterRegistration;
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
import jakarta.servlet.SessionCookieConfig;
|
||||
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.boot.web.server.WebServerException;
|
||||
import org.springframework.mock.web.MockSessionCookieConfig;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* A mock servlet {@link WebServer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class MockServletWebServer implements WebServer {
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
private final Initializer[] initializers;
|
||||
|
||||
private final List<RegisteredServlet> registeredServlets = new ArrayList<>();
|
||||
|
||||
private final List<RegisteredFilter> registeredFilters = new ArrayList<>();
|
||||
|
||||
private final Map<String, FilterRegistration> filterRegistrations = new HashMap<>();
|
||||
|
||||
private final Map<String, ServletRegistration> servletRegistrations = new HashMap<>();
|
||||
|
||||
private final int port;
|
||||
|
||||
MockServletWebServer(ServletContextInitializers initializers, int port) {
|
||||
this(StreamSupport.stream(initializers.spliterator(), false)
|
||||
.map((initializer) -> (Initializer) initializer::onStartup)
|
||||
.toArray(Initializer[]::new), port);
|
||||
}
|
||||
|
||||
MockServletWebServer(Initializer[] initializers, int port) {
|
||||
this.initializers = initializers;
|
||||
this.port = port;
|
||||
initialize();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void initialize() {
|
||||
try {
|
||||
this.servletContext = mock(ServletContext.class);
|
||||
lenient().doAnswer((invocation) -> {
|
||||
RegisteredServlet registeredServlet = new RegisteredServlet(invocation.getArgument(1));
|
||||
MockServletWebServer.this.registeredServlets.add(registeredServlet);
|
||||
this.servletRegistrations.put(invocation.getArgument(0), registeredServlet.getRegistration());
|
||||
return registeredServlet.getRegistration();
|
||||
}).when(this.servletContext).addServlet(anyString(), any(Servlet.class));
|
||||
lenient().doAnswer((invocation) -> {
|
||||
RegisteredFilter registeredFilter = new RegisteredFilter(invocation.getArgument(1));
|
||||
MockServletWebServer.this.registeredFilters.add(registeredFilter);
|
||||
this.filterRegistrations.put(invocation.getArgument(0), registeredFilter.getRegistration());
|
||||
return registeredFilter.getRegistration();
|
||||
}).when(this.servletContext).addFilter(anyString(), any(Filter.class));
|
||||
final SessionCookieConfig sessionCookieConfig = new MockSessionCookieConfig();
|
||||
given(this.servletContext.getSessionCookieConfig()).willReturn(sessionCookieConfig);
|
||||
final Map<String, String> initParameters = new HashMap<>();
|
||||
lenient().doAnswer((invocation) -> {
|
||||
initParameters.put(invocation.getArgument(0), invocation.getArgument(1));
|
||||
return null;
|
||||
}).when(this.servletContext).setInitParameter(anyString(), anyString());
|
||||
given(this.servletContext.getInitParameterNames())
|
||||
.willReturn(Collections.enumeration(initParameters.keySet()));
|
||||
lenient().doAnswer((invocation) -> initParameters.get(invocation.getArgument(0)))
|
||||
.when(this.servletContext)
|
||||
.getInitParameter(anyString());
|
||||
given(this.servletContext.getAttributeNames()).willReturn(Collections.emptyEnumeration());
|
||||
lenient().when((Map<String, FilterRegistration>) this.servletContext.getFilterRegistrations())
|
||||
.thenReturn(this.filterRegistrations);
|
||||
lenient().when((Map<String, ServletRegistration>) this.servletContext.getServletRegistrations())
|
||||
.thenReturn(this.servletRegistrations);
|
||||
for (Initializer initializer : this.initializers) {
|
||||
initializer.onStartup(this.servletContext);
|
||||
}
|
||||
}
|
||||
catch (ServletException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws WebServerException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.servletContext = null;
|
||||
this.registeredServlets.clear();
|
||||
this.filterRegistrations.clear();
|
||||
this.registeredFilters.clear();
|
||||
}
|
||||
|
||||
public ServletContext getServletContext() {
|
||||
return this.servletContext;
|
||||
}
|
||||
|
||||
public Servlet[] getServlets() {
|
||||
Servlet[] servlets = new Servlet[this.registeredServlets.size()];
|
||||
Arrays.setAll(servlets, (i) -> this.registeredServlets.get(i).getServlet());
|
||||
return servlets;
|
||||
}
|
||||
|
||||
public RegisteredServlet getRegisteredServlet(int index) {
|
||||
return getRegisteredServlets().get(index);
|
||||
}
|
||||
|
||||
public List<RegisteredServlet> getRegisteredServlets() {
|
||||
return this.registeredServlets;
|
||||
}
|
||||
|
||||
public RegisteredFilter getRegisteredFilters(int index) {
|
||||
return getRegisteredFilters().get(index);
|
||||
}
|
||||
|
||||
public List<RegisteredFilter> getRegisteredFilters() {
|
||||
return this.registeredFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
/**
|
||||
* A registered servlet.
|
||||
*/
|
||||
public static class RegisteredServlet {
|
||||
|
||||
private final Servlet servlet;
|
||||
|
||||
private final ServletRegistration.Dynamic registration;
|
||||
|
||||
public RegisteredServlet(Servlet servlet) {
|
||||
this.servlet = servlet;
|
||||
this.registration = mock(ServletRegistration.Dynamic.class);
|
||||
}
|
||||
|
||||
public ServletRegistration.Dynamic getRegistration() {
|
||||
return this.registration;
|
||||
}
|
||||
|
||||
public Servlet getServlet() {
|
||||
return this.servlet;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A registered filter.
|
||||
*/
|
||||
public static class RegisteredFilter {
|
||||
|
||||
private final Filter filter;
|
||||
|
||||
private final FilterRegistration.Dynamic registration;
|
||||
|
||||
public RegisteredFilter(Filter filter) {
|
||||
this.filter = filter;
|
||||
this.registration = mock(FilterRegistration.Dynamic.class);
|
||||
}
|
||||
|
||||
public FilterRegistration.Dynamic getRegistration() {
|
||||
return this.registration;
|
||||
}
|
||||
|
||||
public Filter getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializer (usually implement by adapting {@code ServletContextInitializer}).
|
||||
*/
|
||||
@FunctionalInterface
|
||||
protected interface Initializer {
|
||||
|
||||
void onStartup(ServletContext context) throws ServletException;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.web.server.servlet;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
|
||||
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.boot.web.server.servlet.MockServletWebServer.RegisteredFilter;
|
||||
import org.springframework.boot.web.server.servlet.MockServletWebServer.RegisteredServlet;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
/**
|
||||
* Mock {@link ServletWebServerFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class MockServletWebServerFactory extends AbstractConfigurableWebServerFactory
|
||||
implements ConfigurableServletWebServerFactory {
|
||||
|
||||
private final ServletWebServerSettings settings = new ServletWebServerSettings();
|
||||
|
||||
private MockServletWebServer webServer;
|
||||
|
||||
@Override
|
||||
public WebServer getWebServer(ServletContextInitializer... initializers) {
|
||||
this.webServer = spy(
|
||||
new MockServletWebServer(ServletContextInitializers.from(this.settings, initializers), getPort()));
|
||||
return this.webServer;
|
||||
}
|
||||
|
||||
public MockServletWebServer getWebServer() {
|
||||
return this.webServer;
|
||||
}
|
||||
|
||||
public ServletContext getServletContext() {
|
||||
return (getWebServer() != null) ? getWebServer().getServletContext() : null;
|
||||
}
|
||||
|
||||
public RegisteredServlet getRegisteredServlet(int index) {
|
||||
return (getWebServer() != null) ? getWebServer().getRegisteredServlet(index) : null;
|
||||
}
|
||||
|
||||
public RegisteredFilter getRegisteredFilter(int index) {
|
||||
return (getWebServer() != null) ? getWebServer().getRegisteredFilters(index) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletWebServerSettings getSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDqzCCApOgAwIBAgIIFMqbpqvipw0wDQYJKoZIhvcNAQELBQAwbDELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEP
|
||||
MA0GA1UEChMGVk13YXJlMQ8wDQYDVQQLEwZTcHJpbmcxEjAQBgNVBAMTCWxvY2Fs
|
||||
aG9zdDAgFw0yMzA1MDUxMTI2NThaGA8yMTIzMDQxMTExMjY1OFowbDELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEP
|
||||
MA0GA1UEChMGVk13YXJlMQ8wDQYDVQQLEwZTcHJpbmcxEjAQBgNVBAMTCWxvY2Fs
|
||||
aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPwHWxoE3xjRmNdD
|
||||
+m+e/aFlr5wEGQUdWSDD613OB1w7kqO/audEp3c6HxDB3GPcEL0amJwXgY6CQMYu
|
||||
sythuZX/EZSc2HdilTBu/5T+mbdWe5JkKThpiA0RYeucQfKuB7zv4ypioa4wiR4D
|
||||
nPsZXjg95OF8pCzYEssv8wT49v+M3ohWUgfF0FPlMFCSo0YVTuzB1mhDlWKq/jhQ
|
||||
11WpTmk/dQX+l6ts6bYIcJt4uItG+a68a4FutuSjZdTAE0f5SOYRBpGH96mjLwEP
|
||||
fW8ZjzvKb9g4R2kiuoPxvCDs1Y/8V2yvKqLyn5Tx9x/DjFmOi0DRK/TgELvNceCb
|
||||
UDJmhXMCAwEAAaNPME0wHQYDVR0OBBYEFMBIGU1nwix5RS3O5hGLLoMdR1+NMCwG
|
||||
A1UdEQQlMCOCCWxvY2FsaG9zdIcQAAAAAAAAAAAAAAAAAAAAAYcEfwAAATANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAhepfJgTFvqSccsT97XdAZfvB0noQx5NSynRV8NWmeOld
|
||||
hHP6Fzj6xCxHSYvlUfmX8fVP9EOAuChgcbbuTIVJBu60rnDT21oOOnp8FvNonCV6
|
||||
gJ89sCL7wZ77dw2RKIeUFjXXEV3QJhx2wCOVmLxnJspDoKFIEVjfLyiPXKxqe/6b
|
||||
dG8zzWDZ6z+M2JNCtVoOGpljpHqMPCmbDktncv6H3dDTZ83bmLj1nbpOU587gAJ8
|
||||
fl1PiUDyPRIl2cnOJd+wCHKsyym/FL7yzk0OSEZ81I92LpGd/0b2Ld3m/bpe+C4Z
|
||||
ILzLXTnC6AhrLcDc9QN/EO+BiCL52n7EplNLtSn1LQ==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,59 @@
|
||||
Bag Attributes
|
||||
friendlyName: test-alias
|
||||
localKeyID: 54 69 6D 65 20 31 36 38 33 32 38 36 31 31 34 30 37 31
|
||||
Key Attributes: <No Attributes>
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD8B1saBN8Y0ZjX
|
||||
Q/pvnv2hZa+cBBkFHVkgw+tdzgdcO5Kjv2rnRKd3Oh8Qwdxj3BC9GpicF4GOgkDG
|
||||
LrMrYbmV/xGUnNh3YpUwbv+U/pm3VnuSZCk4aYgNEWHrnEHyrge87+MqYqGuMIke
|
||||
A5z7GV44PeThfKQs2BLLL/ME+Pb/jN6IVlIHxdBT5TBQkqNGFU7swdZoQ5Viqv44
|
||||
UNdVqU5pP3UF/perbOm2CHCbeLiLRvmuvGuBbrbko2XUwBNH+UjmEQaRh/epoy8B
|
||||
D31vGY87ym/YOEdpIrqD8bwg7NWP/Fdsryqi8p+U8fcfw4xZjotA0Sv04BC7zXHg
|
||||
m1AyZoVzAgMBAAECggEAfEqiZqANaF+BqXQIb4Dw42ZTJzWsIyYYnPySOGZRoe5t
|
||||
QJ03uwtULYv34xtANe1DQgd6SMyc46ugBzzjtprQ3ET5Jhn99U6kdcjf+dpf85dO
|
||||
hOEppP0CkDNI39nleinSfh6uIOqYgt/D143/nqQhn8oCdSOzkbwT9KnWh1bC9T7I
|
||||
vFjGfElvt1/xl88qYgrWgYLgXaencNGgiv/4/M0FNhiHEGsVC7SCu6kapC/WIQpE
|
||||
5IdV+HR+tiLoGZhXlhqorY7QC4xKC4wwafVSiFxqDOQAuK+SMD4TCEv0Aop+c+SE
|
||||
YBigVTmgVeJkjK7IkTEhKkAEFmRF5/5w+bZD9FhTNQKBgQD+4fNG1ChSU8RdizZT
|
||||
5dPlDyAxpETSCEXFFVGtPPh2j93HDWn7XugNyjn5FylTH507QlabC+5wZqltdIjK
|
||||
GRB5MIinQ9/nR2fuwGc9s+0BiSEwNOUB1MWm7wWL/JUIiKq6sTi6sJIfsYg79zco
|
||||
qxl5WE94aoINx9Utq1cdWhwJTQKBgQD9IjPksd4Jprz8zMrGLzR8k1gqHyhv24qY
|
||||
EJ7jiHKKAP6xllTUYwh1IBSL6w2j5lfZPpIkb4Jlk2KUoX6fN81pWkBC/fTBUSIB
|
||||
EHM9bL51+yKEYUbGIy/gANuRbHXsWg3sjUsFTNPN4hGTFk3w2xChCyl/f5us8Lo8
|
||||
Z633SNdpvwKBgQCGyDU9XzNzVZihXtx7wS0sE7OSjKtX5cf/UCbA1V0OVUWR3SYO
|
||||
J0HPCQFfF0BjFHSwwYPKuaR9C8zMdLNhK5/qdh/NU7czNi9fsZ7moh7SkRFbzJzN
|
||||
OxbKD9t/CzJEMQEXeF/nWTfsSpUgILqqZtAxuuFLbAcaAnJYlCKdAumQgQKBgQCK
|
||||
mqjJh68pn7gJwGUjoYNe1xtGbSsqHI9F9ovZ0MPO1v6e5M7sQJHH+Fnnxzv/y8e8
|
||||
d6tz8e73iX1IHymDKv35uuZHCGF1XOR+qrA/KQUc+vcKf21OXsP/JtkTRs1HLoRD
|
||||
S5aRf2DWcfvniyYARSNU2xTM8GWgi2ueWbMDHUp+ZwKBgA/swC+K+Jg5DEWm6Sau
|
||||
e6y+eC6S+SoXEKkI3wf7m9aKoZo0y+jh8Gas6gratlc181pSM8O3vZG0n19b493I
|
||||
apCFomMLE56zEzvyzfpsNhFhk5MBMCn0LPyzX6MiynRlGyWIj0c99fbHI3pOMufP
|
||||
WgmVLTZ8uDcSW1MbdUCwFSk5
|
||||
-----END PRIVATE KEY-----
|
||||
Bag Attributes
|
||||
friendlyName: test-alias
|
||||
localKeyID: 54 69 6D 65 20 31 36 38 33 32 38 36 31 31 34 30 37 31
|
||||
subject=C = US, ST = California, L = Palo Alto, O = VMware, OU = Spring, CN = localhost
|
||||
issuer=C = US, ST = California, L = Palo Alto, O = VMware, OU = Spring, CN = localhost
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDqzCCApOgAwIBAgIIFMqbpqvipw0wDQYJKoZIhvcNAQELBQAwbDELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEP
|
||||
MA0GA1UEChMGVk13YXJlMQ8wDQYDVQQLEwZTcHJpbmcxEjAQBgNVBAMTCWxvY2Fs
|
||||
aG9zdDAgFw0yMzA1MDUxMTI2NThaGA8yMTIzMDQxMTExMjY1OFowbDELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEP
|
||||
MA0GA1UEChMGVk13YXJlMQ8wDQYDVQQLEwZTcHJpbmcxEjAQBgNVBAMTCWxvY2Fs
|
||||
aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPwHWxoE3xjRmNdD
|
||||
+m+e/aFlr5wEGQUdWSDD613OB1w7kqO/audEp3c6HxDB3GPcEL0amJwXgY6CQMYu
|
||||
sythuZX/EZSc2HdilTBu/5T+mbdWe5JkKThpiA0RYeucQfKuB7zv4ypioa4wiR4D
|
||||
nPsZXjg95OF8pCzYEssv8wT49v+M3ohWUgfF0FPlMFCSo0YVTuzB1mhDlWKq/jhQ
|
||||
11WpTmk/dQX+l6ts6bYIcJt4uItG+a68a4FutuSjZdTAE0f5SOYRBpGH96mjLwEP
|
||||
fW8ZjzvKb9g4R2kiuoPxvCDs1Y/8V2yvKqLyn5Tx9x/DjFmOi0DRK/TgELvNceCb
|
||||
UDJmhXMCAwEAAaNPME0wHQYDVR0OBBYEFMBIGU1nwix5RS3O5hGLLoMdR1+NMCwG
|
||||
A1UdEQQlMCOCCWxvY2FsaG9zdIcQAAAAAAAAAAAAAAAAAAAAAYcEfwAAATANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAhepfJgTFvqSccsT97XdAZfvB0noQx5NSynRV8NWmeOld
|
||||
hHP6Fzj6xCxHSYvlUfmX8fVP9EOAuChgcbbuTIVJBu60rnDT21oOOnp8FvNonCV6
|
||||
gJ89sCL7wZ77dw2RKIeUFjXXEV3QJhx2wCOVmLxnJspDoKFIEVjfLyiPXKxqe/6b
|
||||
dG8zzWDZ6z+M2JNCtVoOGpljpHqMPCmbDktncv6H3dDTZ83bmLj1nbpOU587gAJ8
|
||||
fl1PiUDyPRIl2cnOJd+wCHKsyym/FL7yzk0OSEZ81I92LpGd/0b2Ld3m/bpe+C4Z
|
||||
ILzLXTnC6AhrLcDc9QN/EO+BiCL52n7EplNLtSn1LQ==
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDqzCCApOgAwIBAgIIFMqbpqvipw0wDQYJKoZIhvcNAQELBQAwbDELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEP
|
||||
MA0GA1UEChMGVk13YXJlMQ8wDQYDVQQLEwZTcHJpbmcxEjAQBgNVBAMTCWxvY2Fs
|
||||
aG9zdDAgFw0yMzA1MDUxMTI2NThaGA8yMTIzMDQxMTExMjY1OFowbDELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEP
|
||||
MA0GA1UEChMGVk13YXJlMQ8wDQYDVQQLEwZTcHJpbmcxEjAQBgNVBAMTCWxvY2Fs
|
||||
aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPwHWxoE3xjRmNdD
|
||||
+m+e/aFlr5wEGQUdWSDD613OB1w7kqO/audEp3c6HxDB3GPcEL0amJwXgY6CQMYu
|
||||
sythuZX/EZSc2HdilTBu/5T+mbdWe5JkKThpiA0RYeucQfKuB7zv4ypioa4wiR4D
|
||||
nPsZXjg95OF8pCzYEssv8wT49v+M3ohWUgfF0FPlMFCSo0YVTuzB1mhDlWKq/jhQ
|
||||
11WpTmk/dQX+l6ts6bYIcJt4uItG+a68a4FutuSjZdTAE0f5SOYRBpGH96mjLwEP
|
||||
fW8ZjzvKb9g4R2kiuoPxvCDs1Y/8V2yvKqLyn5Tx9x/DjFmOi0DRK/TgELvNceCb
|
||||
UDJmhXMCAwEAAaNPME0wHQYDVR0OBBYEFMBIGU1nwix5RS3O5hGLLoMdR1+NMCwG
|
||||
A1UdEQQlMCOCCWxvY2FsaG9zdIcQAAAAAAAAAAAAAAAAAAAAAYcEfwAAATANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAhepfJgTFvqSccsT97XdAZfvB0noQx5NSynRV8NWmeOld
|
||||
hHP6Fzj6xCxHSYvlUfmX8fVP9EOAuChgcbbuTIVJBu60rnDT21oOOnp8FvNonCV6
|
||||
gJ89sCL7wZ77dw2RKIeUFjXXEV3QJhx2wCOVmLxnJspDoKFIEVjfLyiPXKxqe/6b
|
||||
dG8zzWDZ6z+M2JNCtVoOGpljpHqMPCmbDktncv6H3dDTZ83bmLj1nbpOU587gAJ8
|
||||
fl1PiUDyPRIl2cnOJd+wCHKsyym/FL7yzk0OSEZ81I92LpGd/0b2Ld3m/bpe+C4Z
|
||||
ILzLXTnC6AhrLcDc9QN/EO+BiCL52n7EplNLtSn1LQ==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,59 @@
|
||||
Bag Attributes
|
||||
friendlyName: test-alias
|
||||
localKeyID: 54 69 6D 65 20 31 36 38 33 32 38 36 31 31 34 30 37 31
|
||||
Key Attributes: <No Attributes>
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD8B1saBN8Y0ZjX
|
||||
Q/pvnv2hZa+cBBkFHVkgw+tdzgdcO5Kjv2rnRKd3Oh8Qwdxj3BC9GpicF4GOgkDG
|
||||
LrMrYbmV/xGUnNh3YpUwbv+U/pm3VnuSZCk4aYgNEWHrnEHyrge87+MqYqGuMIke
|
||||
A5z7GV44PeThfKQs2BLLL/ME+Pb/jN6IVlIHxdBT5TBQkqNGFU7swdZoQ5Viqv44
|
||||
UNdVqU5pP3UF/perbOm2CHCbeLiLRvmuvGuBbrbko2XUwBNH+UjmEQaRh/epoy8B
|
||||
D31vGY87ym/YOEdpIrqD8bwg7NWP/Fdsryqi8p+U8fcfw4xZjotA0Sv04BC7zXHg
|
||||
m1AyZoVzAgMBAAECggEAfEqiZqANaF+BqXQIb4Dw42ZTJzWsIyYYnPySOGZRoe5t
|
||||
QJ03uwtULYv34xtANe1DQgd6SMyc46ugBzzjtprQ3ET5Jhn99U6kdcjf+dpf85dO
|
||||
hOEppP0CkDNI39nleinSfh6uIOqYgt/D143/nqQhn8oCdSOzkbwT9KnWh1bC9T7I
|
||||
vFjGfElvt1/xl88qYgrWgYLgXaencNGgiv/4/M0FNhiHEGsVC7SCu6kapC/WIQpE
|
||||
5IdV+HR+tiLoGZhXlhqorY7QC4xKC4wwafVSiFxqDOQAuK+SMD4TCEv0Aop+c+SE
|
||||
YBigVTmgVeJkjK7IkTEhKkAEFmRF5/5w+bZD9FhTNQKBgQD+4fNG1ChSU8RdizZT
|
||||
5dPlDyAxpETSCEXFFVGtPPh2j93HDWn7XugNyjn5FylTH507QlabC+5wZqltdIjK
|
||||
GRB5MIinQ9/nR2fuwGc9s+0BiSEwNOUB1MWm7wWL/JUIiKq6sTi6sJIfsYg79zco
|
||||
qxl5WE94aoINx9Utq1cdWhwJTQKBgQD9IjPksd4Jprz8zMrGLzR8k1gqHyhv24qY
|
||||
EJ7jiHKKAP6xllTUYwh1IBSL6w2j5lfZPpIkb4Jlk2KUoX6fN81pWkBC/fTBUSIB
|
||||
EHM9bL51+yKEYUbGIy/gANuRbHXsWg3sjUsFTNPN4hGTFk3w2xChCyl/f5us8Lo8
|
||||
Z633SNdpvwKBgQCGyDU9XzNzVZihXtx7wS0sE7OSjKtX5cf/UCbA1V0OVUWR3SYO
|
||||
J0HPCQFfF0BjFHSwwYPKuaR9C8zMdLNhK5/qdh/NU7czNi9fsZ7moh7SkRFbzJzN
|
||||
OxbKD9t/CzJEMQEXeF/nWTfsSpUgILqqZtAxuuFLbAcaAnJYlCKdAumQgQKBgQCK
|
||||
mqjJh68pn7gJwGUjoYNe1xtGbSsqHI9F9ovZ0MPO1v6e5M7sQJHH+Fnnxzv/y8e8
|
||||
d6tz8e73iX1IHymDKv35uuZHCGF1XOR+qrA/KQUc+vcKf21OXsP/JtkTRs1HLoRD
|
||||
S5aRf2DWcfvniyYARSNU2xTM8GWgi2ueWbMDHUp+ZwKBgA/swC+K+Jg5DEWm6Sau
|
||||
e6y+eC6S+SoXEKkI3wf7m9aKoZo0y+jh8Gas6gratlc181pSM8O3vZG0n19b493I
|
||||
apCFomMLE56zEzvyzfpsNhFhk5MBMCn0LPyzX6MiynRlGyWIj0c99fbHI3pOMufP
|
||||
WgmVLTZ8uDcSW1MbdUCwFSk5
|
||||
-----END PRIVATE KEY-----
|
||||
Bag Attributes
|
||||
friendlyName: test-alias
|
||||
localKeyID: 54 69 6D 65 20 31 36 38 33 32 38 36 31 31 34 30 37 31
|
||||
subject=C = US, ST = California, L = Palo Alto, O = VMware, OU = Spring, CN = localhost
|
||||
issuer=C = US, ST = California, L = Palo Alto, O = VMware, OU = Spring, CN = localhost
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDqzCCApOgAwIBAgIIFMqbpqvipw0wDQYJKoZIhvcNAQELBQAwbDELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEP
|
||||
MA0GA1UEChMGVk13YXJlMQ8wDQYDVQQLEwZTcHJpbmcxEjAQBgNVBAMTCWxvY2Fs
|
||||
aG9zdDAgFw0yMzA1MDUxMTI2NThaGA8yMTIzMDQxMTExMjY1OFowbDELMAkGA1UE
|
||||
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEP
|
||||
MA0GA1UEChMGVk13YXJlMQ8wDQYDVQQLEwZTcHJpbmcxEjAQBgNVBAMTCWxvY2Fs
|
||||
aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPwHWxoE3xjRmNdD
|
||||
+m+e/aFlr5wEGQUdWSDD613OB1w7kqO/audEp3c6HxDB3GPcEL0amJwXgY6CQMYu
|
||||
sythuZX/EZSc2HdilTBu/5T+mbdWe5JkKThpiA0RYeucQfKuB7zv4ypioa4wiR4D
|
||||
nPsZXjg95OF8pCzYEssv8wT49v+M3ohWUgfF0FPlMFCSo0YVTuzB1mhDlWKq/jhQ
|
||||
11WpTmk/dQX+l6ts6bYIcJt4uItG+a68a4FutuSjZdTAE0f5SOYRBpGH96mjLwEP
|
||||
fW8ZjzvKb9g4R2kiuoPxvCDs1Y/8V2yvKqLyn5Tx9x/DjFmOi0DRK/TgELvNceCb
|
||||
UDJmhXMCAwEAAaNPME0wHQYDVR0OBBYEFMBIGU1nwix5RS3O5hGLLoMdR1+NMCwG
|
||||
A1UdEQQlMCOCCWxvY2FsaG9zdIcQAAAAAAAAAAAAAAAAAAAAAYcEfwAAATANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAhepfJgTFvqSccsT97XdAZfvB0noQx5NSynRV8NWmeOld
|
||||
hHP6Fzj6xCxHSYvlUfmX8fVP9EOAuChgcbbuTIVJBu60rnDT21oOOnp8FvNonCV6
|
||||
gJ89sCL7wZ77dw2RKIeUFjXXEV3QJhx2wCOVmLxnJspDoKFIEVjfLyiPXKxqe/6b
|
||||
dG8zzWDZ6z+M2JNCtVoOGpljpHqMPCmbDktncv6H3dDTZ83bmLj1nbpOU587gAJ8
|
||||
fl1PiUDyPRIl2cnOJd+wCHKsyym/FL7yzk0OSEZ81I92LpGd/0b2Ld3m/bpe+C4Z
|
||||
ILzLXTnC6AhrLcDc9QN/EO+BiCL52n7EplNLtSn1LQ==
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user