Commit 52ab46b2 by Johannes Edmeier

Make it easier to add custome InstanceExchangeFilterFunction

parent 1279ae84
...@@ -22,14 +22,18 @@ import de.codecentric.boot.admin.server.domain.entities.InstanceRepository; ...@@ -22,14 +22,18 @@ import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.notify.LoggingNotifier; import de.codecentric.boot.admin.server.notify.LoggingNotifier;
import de.codecentric.boot.admin.server.notify.RemindingNotifier; import de.codecentric.boot.admin.server.notify.RemindingNotifier;
import de.codecentric.boot.admin.server.notify.filter.FilteringNotifier; import de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;
import de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction;
import java.time.Duration; import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpMethod;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
...@@ -39,6 +43,9 @@ import org.springframework.security.web.authentication.SavedRequestAwareAuthenti ...@@ -39,6 +43,9 @@ import org.springframework.security.web.authentication.SavedRequestAwareAuthenti
@EnableAutoConfiguration @EnableAutoConfiguration
@EnableAdminServer @EnableAdminServer
public class SpringBootAdminApplication { public class SpringBootAdminApplication {
private static final Logger log = LoggerFactory.getLogger(SpringBootAdminApplication.class);
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SpringBootAdminApplication.class, args); SpringApplication.run(SpringBootAdminApplication.class, args);
} }
...@@ -83,6 +90,16 @@ public class SpringBootAdminApplication { ...@@ -83,6 +90,16 @@ public class SpringBootAdminApplication {
} }
// end::configuration-spring-security[] // end::configuration-spring-security[]
@Bean
public InstanceExchangeFilterFunction auditLog() {
return (instance, request, next) -> {
if (HttpMethod.DELETE.equals(request.method()) || HttpMethod.POST.equals(request.method())) {
log.info("{} for {} on {}", request.method(), instance.getId(), request.url());
}
return next.exchange(request);
};
}
@Configuration @Configuration
public static class NotifierConfig { public static class NotifierConfig {
private final InstanceRepository repository; private final InstanceRepository repository;
......
...@@ -2498,9 +2498,9 @@ ...@@ -2498,9 +2498,9 @@
} }
}, },
"css-loader": { "css-loader": {
"version": "0.28.9", "version": "0.28.10",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.9.tgz", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.10.tgz",
"integrity": "sha512-r3dgelMm/mkPz5Y7m9SeiGE46i2VsEU/OYbez+1llfxtv8b2y5/b5StaeEvPK3S5tlNQI+tDW/xDIhKJoZgDtw==", "integrity": "sha512-X1IJteKnW9Llmrd+lJ0f7QZHh9Arf+11S7iRcoT2+riig3BK0QaCaOtubAulMK6Itbo08W6d3l8sW21r+Jhp5Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"babel-code-frame": "6.26.0", "babel-code-frame": "6.26.0",
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
"clean-webpack-plugin": "^0.1.18", "clean-webpack-plugin": "^0.1.18",
"cross-env": "^5.1.3", "cross-env": "^5.1.3",
"css-hot-loader": "^1.3.7", "css-hot-loader": "^1.3.7",
"css-loader": "^0.28.9", "css-loader": "^0.28.10",
"css-mqpacker": "^6.0.2", "css-mqpacker": "^6.0.2",
"eslint": "^4.18.1", "eslint": "^4.18.1",
"eslint-loader": "^1.9.0", "eslint-loader": "^1.9.0",
......
...@@ -36,13 +36,19 @@ import de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStr ...@@ -36,13 +36,19 @@ import de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStr
import de.codecentric.boot.admin.server.web.client.BasicAuthHttpHeaderProvider; import de.codecentric.boot.admin.server.web.client.BasicAuthHttpHeaderProvider;
import de.codecentric.boot.admin.server.web.client.CompositeHttpHeadersProvider; import de.codecentric.boot.admin.server.web.client.CompositeHttpHeadersProvider;
import de.codecentric.boot.admin.server.web.client.HttpHeadersProvider; import de.codecentric.boot.admin.server.web.client.HttpHeadersProvider;
import de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction;
import de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions;
import de.codecentric.boot.admin.server.web.client.InstanceWebClient; import de.codecentric.boot.admin.server.web.client.InstanceWebClient;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
...@@ -141,4 +147,17 @@ public class AdminServerAutoConfiguration { ...@@ -141,4 +147,17 @@ public class AdminServerAutoConfiguration {
public SnapshottingInstanceRepository instanceRepository(InstanceEventStore eventStore) { public SnapshottingInstanceRepository instanceRepository(InstanceEventStore eventStore) {
return new SnapshottingInstanceRepository(eventStore); return new SnapshottingInstanceRepository(eventStore);
} }
@Bean
@ConditionalOnMissingBean
public InstanceWebClient instanceWebClient(HttpHeadersProvider httpHeadersProvider,
ObjectProvider<List<InstanceExchangeFilterFunction>> filtersProvider) {
List<InstanceExchangeFilterFunction> filters = filtersProvider.getIfAvailable(Collections::emptyList);
WebClientCustomizer customizer = (webClient) -> filters.forEach(instanceFilter -> webClient.filter(
InstanceExchangeFilterFunctions.toExchangeFilterFunction(instanceFilter)));
return new InstanceWebClient(httpHeadersProvider, adminServerProperties.getMonitor().getConnectTimeout(),
adminServerProperties.getMonitor().getReadTimeout(), customizer);
}
} }
...@@ -25,7 +25,6 @@ import de.codecentric.boot.admin.server.utils.jackson.RegistrationDeserializer; ...@@ -25,7 +25,6 @@ import de.codecentric.boot.admin.server.utils.jackson.RegistrationDeserializer;
import de.codecentric.boot.admin.server.utils.jackson.SanitizingMapSerializer; import de.codecentric.boot.admin.server.utils.jackson.SanitizingMapSerializer;
import de.codecentric.boot.admin.server.web.ApplicationsController; import de.codecentric.boot.admin.server.web.ApplicationsController;
import de.codecentric.boot.admin.server.web.InstancesController; import de.codecentric.boot.admin.server.web.InstancesController;
import de.codecentric.boot.admin.server.web.client.HttpHeadersProvider;
import de.codecentric.boot.admin.server.web.client.InstanceWebClient; import de.codecentric.boot.admin.server.web.client.InstanceWebClient;
import de.codecentric.boot.admin.server.web.servlet.InstancesProxyController; import de.codecentric.boot.admin.server.web.servlet.InstancesProxyController;
...@@ -62,14 +61,6 @@ public class AdminServerWebConfiguration { ...@@ -62,14 +61,6 @@ public class AdminServerWebConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public InstanceWebClient instanceWebClient(HttpHeadersProvider httpHeadersProvider) {
return new InstanceWebClient(httpHeadersProvider, adminServerProperties.getMonitor().getConnectTimeout(),
adminServerProperties.getMonitor().getReadTimeout());
}
@Bean
@ConditionalOnMissingBean
public ApplicationsController applicationsController(InstanceRegistry instanceRegistry, public ApplicationsController applicationsController(InstanceRegistry instanceRegistry,
InstanceEventPublisher eventPublisher) { InstanceEventPublisher eventPublisher) {
return new ApplicationsController(instanceRegistry, eventPublisher); return new ApplicationsController(instanceRegistry, eventPublisher);
......
/*
* Copyright 2014-2018 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.server.web.client;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import reactor.core.publisher.Mono;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
@FunctionalInterface
public interface InstanceExchangeFilterFunction {
Mono<ClientResponse> exchange(Instance instance, ClientRequest request, ExchangeFunction next);
}
...@@ -48,13 +48,13 @@ import org.springframework.web.util.UriComponentsBuilder; ...@@ -48,13 +48,13 @@ import org.springframework.web.util.UriComponentsBuilder;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
public final class InstanceFilterFunctions { public final class InstanceExchangeFilterFunctions {
private static final String ATTRIBUTE_INSTANCE = "instance"; public static final String ATTRIBUTE_INSTANCE = "instance";
private static final String ATTRIBUTE_ENDPOINT = "endpointId"; public static final String ATTRIBUTE_ENDPOINT = "endpointId";
private static final MediaType ACTUATOR_V1_MEDIATYPE = MediaType.parseMediaType(ActuatorMediaType.V1_JSON); private static final MediaType ACTUATOR_V1_MEDIATYPE = MediaType.parseMediaType(ActuatorMediaType.V1_JSON);
private static final MediaType ACTUATOR_V2_MEDIATYPE = MediaType.parseMediaType(ActuatorMediaType.V2_JSON); private static final MediaType ACTUATOR_V2_MEDIATYPE = MediaType.parseMediaType(ActuatorMediaType.V2_JSON);
private InstanceFilterFunctions() { private InstanceExchangeFilterFunctions() {
} }
public static ExchangeFilterFunction setInstance(Instance instance) { public static ExchangeFilterFunction setInstance(Instance instance) {
...@@ -71,7 +71,7 @@ public final class InstanceFilterFunctions { ...@@ -71,7 +71,7 @@ public final class InstanceFilterFunctions {
} }
public static ExchangeFilterFunction addHeaders(HttpHeadersProvider httpHeadersProvider) { public static ExchangeFilterFunction addHeaders(HttpHeadersProvider httpHeadersProvider) {
return withInstance((instance, request, next) -> { return toExchangeFilterFunction((instance, request, next) -> {
ClientRequest newRequest = ClientRequest.from(request) ClientRequest newRequest = ClientRequest.from(request)
.headers(headers -> headers.addAll( .headers(headers -> headers.addAll(
httpHeadersProvider.getHeaders(instance))) httpHeadersProvider.getHeaders(instance)))
...@@ -80,7 +80,7 @@ public final class InstanceFilterFunctions { ...@@ -80,7 +80,7 @@ public final class InstanceFilterFunctions {
}); });
} }
public static ExchangeFilterFunction withInstance(InstanceExchangeFilterFunction delegate) { public static ExchangeFilterFunction toExchangeFilterFunction(InstanceExchangeFilterFunction delegate) {
return (request, next) -> { return (request, next) -> {
Optional<?> instance = request.attribute(ATTRIBUTE_INSTANCE); Optional<?> instance = request.attribute(ATTRIBUTE_INSTANCE);
if (instance.isPresent() && instance.get() instanceof Instance) { if (instance.isPresent() && instance.get() instanceof Instance) {
...@@ -90,13 +90,8 @@ public final class InstanceFilterFunctions { ...@@ -90,13 +90,8 @@ public final class InstanceFilterFunctions {
}; };
} }
@FunctionalInterface
public interface InstanceExchangeFilterFunction {
Mono<ClientResponse> exchange(Instance instance, ClientRequest request, ExchangeFunction next);
}
public static ExchangeFilterFunction rewriteEndpointUrl() { public static ExchangeFilterFunction rewriteEndpointUrl() {
return withInstance((instance, request, next) -> { return toExchangeFilterFunction((instance, request, next) -> {
if (request.url().isAbsolute()) { if (request.url().isAbsolute()) {
return next.exchange(request); return next.exchange(request);
} }
......
...@@ -24,6 +24,7 @@ import reactor.core.publisher.Mono; ...@@ -24,6 +24,7 @@ import reactor.core.publisher.Mono;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector;
...@@ -37,35 +38,44 @@ public class InstanceWebClient { ...@@ -37,35 +38,44 @@ public class InstanceWebClient {
} }
public InstanceWebClient(HttpHeadersProvider httpHeadersProvider, Duration connectTimeout, Duration readTimeout) { public InstanceWebClient(HttpHeadersProvider httpHeadersProvider, Duration connectTimeout, Duration readTimeout) {
this(createDefaultWebClient(connectTimeout, readTimeout), httpHeadersProvider); this(httpHeadersProvider, connectTimeout, readTimeout, builder -> { });
}
public InstanceWebClient(HttpHeadersProvider httpHeadersProvider,
Duration connectTimeout,
Duration readTimeout,
WebClientCustomizer customizer) {
this(createDefaultWebClient(connectTimeout, readTimeout, customizer), httpHeadersProvider);
} }
public InstanceWebClient(WebClient webClient, HttpHeadersProvider httpHeadersProvider) { public InstanceWebClient(WebClient webClient, HttpHeadersProvider httpHeadersProvider) {
this.webClient = webClient.mutate().filters(filters -> { this.webClient = webClient.mutate().filters(filters -> {
filters.add(InstanceFilterFunctions.addHeaders(httpHeadersProvider)); filters.add(InstanceExchangeFilterFunctions.addHeaders(httpHeadersProvider));
filters.add(InstanceFilterFunctions.rewriteEndpointUrl()); filters.add(InstanceExchangeFilterFunctions.rewriteEndpointUrl());
filters.add(InstanceFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.health())); filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.health()));
filters.add(InstanceFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.env())); filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.env()));
filters.add(InstanceFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.httptrace())); filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.httptrace()));
filters.add(InstanceFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.threaddump())); filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.threaddump()));
filters.add(InstanceFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.liquibase())); filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.liquibase()));
filters.add(InstanceFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.flyway())); filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.flyway()));
}).build(); }).build();
} }
public WebClient instance(Mono<Instance> instance) { public WebClient instance(Mono<Instance> instance) {
return webClient.mutate()// return webClient.mutate()//
.filters(filters -> filters.add(0, InstanceFilterFunctions.setInstance(instance)))// .filters(filters -> filters.add(0, InstanceExchangeFilterFunctions.setInstance(instance)))//
.build(); .build();
} }
public WebClient instance(Instance instance) { public WebClient instance(Instance instance) {
return webClient.mutate()// return webClient.mutate()//
.filters(filters -> filters.add(0, InstanceFilterFunctions.setInstance(instance)))// .filters(filters -> filters.add(0, InstanceExchangeFilterFunctions.setInstance(instance)))//
.build(); .build();
} }
private static WebClient createDefaultWebClient(Duration connectTimeout, Duration readTimeout) { private static WebClient createDefaultWebClient(Duration connectTimeout,
Duration readTimeout,
WebClientCustomizer customizer) {
ReactorClientHttpConnector connector = new ReactorClientHttpConnector( ReactorClientHttpConnector connector = new ReactorClientHttpConnector(
options -> options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) connectTimeout.toMillis())// options -> options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) connectTimeout.toMillis())//
.compression(true)// .compression(true)//
...@@ -73,10 +83,12 @@ public class InstanceWebClient { ...@@ -73,10 +83,12 @@ public class InstanceWebClient {
ctx.addHandlerLast( ctx.addHandlerLast(
new ReadTimeoutHandler(readTimeout.toMillis(), TimeUnit.MILLISECONDS)); new ReadTimeoutHandler(readTimeout.toMillis(), TimeUnit.MILLISECONDS));
})); }));
return WebClient.builder()
.clientConnector(connector) WebClient.Builder builder = WebClient.builder()
.defaultHeader(HttpHeaders.ACCEPT, ActuatorMediaType.V2_JSON, ActuatorMediaType.V1_JSON, .clientConnector(connector)
MediaType.APPLICATION_JSON_VALUE) .defaultHeader(HttpHeaders.ACCEPT, ActuatorMediaType.V2_JSON,
.build(); ActuatorMediaType.V1_JSON, MediaType.APPLICATION_JSON_VALUE);
customizer.customize(builder);
return builder.build();
} }
} }
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