Commit 8a497e65 by Johannes Edmeier

Enable Client for reactive web applications

parent 7e32dfcc
......@@ -70,6 +70,11 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>2.5.1</version>
......
......@@ -62,6 +62,19 @@ public class SpringBootAdminClientAutoConfiguration {
}
}
@Configuration
@ConditionalOnWebApplication(type = Type.REACTIVE)
public static class ReactiveConfiguration {
@Bean
@ConditionalOnMissingBean
public ApplicationFactory applicationFactory(InstanceProperties instance,
ManagementServerProperties management,
ServerProperties server,
EndpointPathProvider endpointPathProvider) {
return new DefaultApplicationFactory(instance, management, server, endpointPathProvider);
}
}
@Bean
@ConditionalOnMissingBean
public ApplicationRegistrator registrator(ClientProperties client,
......
......@@ -24,11 +24,10 @@ import java.util.Map;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.server.Ssl;
import org.springframework.context.event.EventListener;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.util.UriComponentsBuilder;
/**
......@@ -77,7 +76,7 @@ public class DefaultApplicationFactory implements ApplicationFactory {
String baseUrl = instance.getServiceBaseUrl();
if (getLocalServerPort() == null && StringUtils.isEmpty(baseUrl)) {
throw new IllegalStateException("service-base-url must be set when deployed to servlet-container");
throw new IllegalStateException("couldn't determine local port. Please supply service-base-url.");
}
UriComponentsBuilder builder;
......@@ -209,14 +208,12 @@ public class DefaultApplicationFactory implements ApplicationFactory {
}
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
if (event.getApplicationContext() instanceof WebApplicationContext) {
localServerPort = event.getApplicationContext()
.getEnvironment()
.getProperty("local.server.port", Integer.class);
localManagementPort = event.getApplicationContext()
.getEnvironment()
.getProperty("local.management.port", Integer.class, localServerPort);
public void onWebServerInitialized(WebServerInitializedEvent event) {
if ("server".equals(event.getServerId())) {
localServerPort = event.getWebServer().getPort();
}
if ("management".equals(event.getServerId())) {
localManagementPort = event.getWebServer().getPort();
}
}
}
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
......@@ -16,7 +16,6 @@
package de.codecentric.boot.admin.client.registration;
import java.util.concurrent.ScheduledFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
......@@ -25,7 +24,6 @@ import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.context.WebApplicationContext;
/**
* Listener responsible for starting and stopping the registration task when the application is
......@@ -34,71 +32,64 @@ import org.springframework.web.context.WebApplicationContext;
* @author Johannes Edmeier
*/
public class RegistrationApplicationListener {
private static final Logger LOGGER = LoggerFactory
.getLogger(RegistrationApplicationListener.class);
private final ApplicationRegistrator registrator;
private final TaskScheduler taskScheduler;
private boolean autoDeregister = false;
private boolean autoRegister = true;
private long registerPeriod = 10_000L;
private volatile ScheduledFuture<?> scheduledTask;
private static final Logger LOGGER = LoggerFactory.getLogger(RegistrationApplicationListener.class);
private final ApplicationRegistrator registrator;
private final TaskScheduler taskScheduler;
private boolean autoDeregister = false;
private boolean autoRegister = true;
private long registerPeriod = 10_000L;
private volatile ScheduledFuture<?> scheduledTask;
public RegistrationApplicationListener(ApplicationRegistrator registrator,
TaskScheduler taskScheduler) {
this.registrator = registrator;
this.taskScheduler = taskScheduler;
}
public RegistrationApplicationListener(ApplicationRegistrator registrator, TaskScheduler taskScheduler) {
this.registrator = registrator;
this.taskScheduler = taskScheduler;
}
@EventListener
@Order(Ordered.LOWEST_PRECEDENCE)
public void onApplicationReady(ApplicationReadyEvent event) {
if (event.getApplicationContext() instanceof WebApplicationContext && autoRegister) {
startRegisterTask();
}
}
@EventListener
@Order(Ordered.LOWEST_PRECEDENCE)
public void onApplicationReady(ApplicationReadyEvent event) {
if (event.getApplicationContext().getParent() == null && autoRegister) {
startRegisterTask();
}
}
@EventListener
@Order(Ordered.LOWEST_PRECEDENCE)
public void onClosedContext(ContextClosedEvent event) {
if (event.getApplicationContext() instanceof WebApplicationContext) {
stopRegisterTask();
@EventListener
@Order(Ordered.LOWEST_PRECEDENCE)
public void onClosedContext(ContextClosedEvent event) {
if (event.getApplicationContext().getParent() == null) {
stopRegisterTask();
if (autoDeregister) {
registrator.deregister();
}
}
}
if (autoDeregister) {
registrator.deregister();
}
}
}
public void startRegisterTask() {
if (scheduledTask != null && !scheduledTask.isDone()) {
return;
}
public void startRegisterTask() {
if (scheduledTask != null && !scheduledTask.isDone()) {
return;
}
scheduledTask = taskScheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
registrator.register();
}
}, registerPeriod);
LOGGER.debug("Scheduled registration task for every {}ms", registerPeriod);
}
scheduledTask = taskScheduler.scheduleAtFixedRate(registrator::register, registerPeriod);
LOGGER.debug("Scheduled registration task for every {}ms", registerPeriod);
}
public void stopRegisterTask() {
if (scheduledTask != null && !scheduledTask.isDone()) {
scheduledTask.cancel(true);
LOGGER.debug("Canceled registration task");
}
}
public void stopRegisterTask() {
if (scheduledTask != null && !scheduledTask.isDone()) {
scheduledTask.cancel(true);
LOGGER.debug("Canceled registration task");
}
}
public void setAutoDeregister(boolean autoDeregister) {
this.autoDeregister = autoDeregister;
}
public void setAutoDeregister(boolean autoDeregister) {
this.autoDeregister = autoDeregister;
}
public void setAutoRegister(boolean autoRegister) {
this.autoRegister = autoRegister;
}
public void setAutoRegister(boolean autoRegister) {
this.autoRegister = autoRegister;
}
public void setRegisterPeriod(long registerPeriod) {
this.registerPeriod = registerPeriod;
}
public void setRegisterPeriod(long registerPeriod) {
this.registerPeriod = registerPeriod;
}
}
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codecentric.boot.admin.client.config;
import wiremock.org.apache.http.HttpStatus;
import org.junit.Test;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.moreThanOrExactly;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
public abstract class AbstractClientApplicationTest {
private WireMockServer wiremock;
public void setUp() throws Exception {
wiremock = new WireMockServer(wireMockConfig().dynamicPort());
wiremock.start();
configureFor(wiremock.port());
ResponseDefinitionBuilder response = aResponse().withStatus(HttpStatus.SC_CREATED)
.withHeader("Content-Type", "application/json")
.withHeader("Location", "http://localhost:" +
wiremock.port() +
"/instances/abcdef")
.withBody("{ \"id\" : \"abcdef\" }");
stubFor(post(urlEqualTo("/instances")).willReturn(response));
}
@Test
public void test_context() throws InterruptedException {
Thread.sleep(1000L);
String serviceHost = "http://localhost:" + getServerPort();
String managementHost = "http://localhost:" + getManagementPort();
String body = "{ \"name\" : \"Test-Client\"," + //
" \"managementUrl\" : \"" + managementHost + "/mgmt\"," + //
" \"healthUrl\" : \"" + managementHost + "/mgmt/health\"," + //
" \"serviceUrl\" : \"" + serviceHost + "/\", " + //
" \"metadata\" : {} }";
RequestPatternBuilder request = postRequestedFor(urlEqualTo("/instances")).withHeader("Content-Type",
equalTo("application/json")).withRequestBody(equalToJson(body));
verify(moreThanOrExactly(1), request);
}
public void shutdown() {
wiremock.stop();
}
protected abstract int getServerPort();
protected abstract int getManagementPort();
protected int getWirmockPort() {
return wiremock.port();
}
}
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codecentric.boot.admin.client.config;
import org.junit.After;
import org.junit.Before;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
public class ClientReactiveApplicationTest extends AbstractClientApplicationTest {
private ConfigurableApplicationContext instance;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
instance = SpringApplication.run(TestClientApplication.class, "--spring.main.web-application-type=reactive",
"--spring.application.name=Test-Client", "--server.port=0", "--management.context-path=/mgmt",
"--endpoints.health.enabled=true",
"--spring.boot.admin.client.url=http://localhost:" + getWirmockPort());
}
@Override
@After
public void shutdown() {
instance.close();
super.shutdown();
}
@Override
protected int getServerPort() {
return instance.getEnvironment().getProperty("local.server.port", Integer.class, 0);
}
@Override
protected int getManagementPort() {
return instance.getEnvironment().getProperty("local.management.port", Integer.class, 0);
}
@SpringBootConfiguration
@EnableAutoConfiguration(exclude = WebMvcAutoConfiguration.class)
public static class TestClientApplication {
}
}
......@@ -16,73 +16,43 @@
package de.codecentric.boot.admin.client.config;
import wiremock.org.apache.http.HttpStatus;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.moreThanOrExactly;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
public class ClientServletApplicationTest {
public class ClientServletApplicationTest extends AbstractClientApplicationTest {
private ConfigurableApplicationContext instance;
private WireMockServer wiremock;
@Override
@Before
public void setUp() throws Exception {
wiremock = new WireMockServer(wireMockConfig().dynamicPort());
wiremock.start();
configureFor(wiremock.port());
ResponseDefinitionBuilder response = aResponse().withStatus(HttpStatus.SC_CREATED)
.withHeader("Content-Type", "application/json")
.withHeader("Location", "http://localhost:" +
wiremock.port() +
"/instances/abcdef")
.withBody("{ \"id\" : \"abcdef\" }");
stubFor(post(urlEqualTo("/instances")).willReturn(response));
super.setUp();
instance = SpringApplication.run(TestClientApplication.class, "--spring.main.web-application-type=servlet",
"--spring.application.name=Test-Client", "--server.port=0", "--management.context-path=/mgmt",
"--endpoints.health.enabled=true",
"--spring.boot.admin.client.url=http://localhost:" + wiremock.port());
}
@Test
public void test_context() throws InterruptedException {
Thread.sleep(1000L);
String serviceHost = "http://localhost:" + instance.getEnvironment().getProperty("local.server.port");
String managementHost = "http://localhost:" + instance.getEnvironment().getProperty("local.management.port");
String body = "{ \"name\" : \"Test-Client\"," + //
" \"managementUrl\" : \"" + managementHost + "/mgmt\"," + //
" \"healthUrl\" : \"" + managementHost + "/mgmt/health\"," + //
" \"serviceUrl\" : \"" + serviceHost + "/\", " + //
" \"metadata\" : {} }";
RequestPatternBuilder request = postRequestedFor(urlEqualTo("/instances")).withHeader("Content-Type",
equalTo("application/json")).withRequestBody(equalToJson(body));
verify(moreThanOrExactly(1), request);
"--spring.boot.admin.client.url=http://localhost:" + getWirmockPort());
}
@Override
@After
public void shutdown() {
instance.close();
wiremock.stop();
super.shutdown();
}
@Override
protected int getServerPort() {
return instance.getEnvironment().getProperty("local.server.port", Integer.class, 0);
}
@Override
protected int getManagementPort() {
return instance.getEnvironment().getProperty("local.management.port", Integer.class, 0);
}
@SpringBootConfiguration
......
......@@ -22,14 +22,13 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.server.Ssl;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
......@@ -187,16 +186,36 @@ public class DefaultApplicationFactoryTest {
private void publishApplicationReadyEvent(DefaultApplicationFactory factory,
Integer serverport,
Integer managementport) {
MockEnvironment env = new MockEnvironment();
if (serverport != null) {
env.setProperty("local.server.port", serverport.toString());
factory.onWebServerInitialized(new TestWebServerInitializedEvent("server", serverport));
factory.onWebServerInitialized(
new TestWebServerInitializedEvent("management", managementport != null ? managementport : serverport));
}
private static class TestWebServerInitializedEvent extends WebServerInitializedEvent {
private final String serverId;
private final WebServer server;
private TestWebServerInitializedEvent(String serverId, int port) {
super(mock(WebServer.class));
this.serverId = serverId;
this.server = mock(WebServer.class);
when(server.getPort()).thenReturn(port);
}
@Override
public ApplicationContext getApplicationContext() {
return null;
}
if (managementport != null) {
env.setProperty("local.management.port", managementport.toString());
@Override
public String getServerId() {
return this.serverId;
}
ConfigurableWebApplicationContext context = mock(ConfigurableWebApplicationContext.class);
when(context.getEnvironment()).thenReturn(env);
factory.onApplicationReady(new ApplicationReadyEvent(mock(SpringApplication.class), new String[]{}, context));
@Override
public WebServer getWebServer() {
return this.server;
}
}
}
......@@ -22,14 +22,13 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
......@@ -111,17 +110,35 @@ public class ServletApplicationFactoryTest {
private void publishApplicationReadyEvent(DefaultApplicationFactory factory,
Integer serverport,
Integer managementport) {
MockEnvironment env = new MockEnvironment();
if (serverport != null) {
env.setProperty("local.server.port", serverport.toString());
factory.onWebServerInitialized(new TestWebServerInitializedEvent("server", serverport));
factory.onWebServerInitialized(
new TestWebServerInitializedEvent("management", managementport != null ? managementport : serverport));
}
private static class TestWebServerInitializedEvent extends WebServerInitializedEvent {
private final String serverId;
private final WebServer server;
private TestWebServerInitializedEvent(String serverId, int port) {
super(mock(WebServer.class));
this.serverId = serverId;
this.server = mock(WebServer.class);
when(server.getPort()).thenReturn(port);
}
@Override
public ApplicationContext getApplicationContext() {
return null;
}
if (managementport != null) {
env.setProperty("local.management.port", managementport.toString());
@Override
public String getServerId() {
return this.serverId;
}
ConfigurableWebApplicationContext context = mock(ConfigurableWebApplicationContext.class);
when(context.getEnvironment()).thenReturn(env);
factory.onApplicationReady(new ApplicationReadyEvent(mock(SpringApplication.class), new String[]{}, context));
@Override
public WebServer getWebServer() {
return this.server;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment