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