Skip to content

Commit 84aed67

Browse files
SteKoeulischulte
andauthored
fix: allow to set custom header providers in reactive context (#2142)
* fix: allow to set custom header providers in reactive context * chore: update copyright year information * chore: rearrange code and add tests * Replace lambda with Method reference Co-authored-by: ulrichschulte <[email protected]>
1 parent 118d789 commit 84aed67

File tree

6 files changed

+212
-1
lines changed

6 files changed

+212
-1
lines changed

spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerInstanceWebClientConfiguration.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import de.codecentric.boot.admin.server.web.client.cookies.CookieStoreCleanupTrigger;
4444
import de.codecentric.boot.admin.server.web.client.cookies.JdkPerInstanceCookieStore;
4545
import de.codecentric.boot.admin.server.web.client.cookies.PerInstanceCookieStore;
46+
import de.codecentric.boot.admin.server.web.client.reactive.CompositeReactiveHttpHeadersProvider;
47+
import de.codecentric.boot.admin.server.web.client.reactive.ReactiveHttpHeadersProvider;
4648

4749
@Configuration(proxyBeanMethods = false)
4850
@Lazy(false)
@@ -86,6 +88,16 @@ public InstanceExchangeFilterFunction addHeadersInstanceExchangeFilter(
8688
return InstanceExchangeFilterFunctions.addHeaders(new CompositeHttpHeadersProvider(headersProviders));
8789
}
8890

91+
@Bean
92+
@Order(0)
93+
@ConditionalOnBean(ReactiveHttpHeadersProvider.class)
94+
@ConditionalOnMissingBean(name = "addReactiveHeadersInstanceExchangeFilter")
95+
public InstanceExchangeFilterFunction addReactiveHeadersInstanceExchangeFilter(
96+
List<ReactiveHttpHeadersProvider> reactiveHeadersProviders) {
97+
return InstanceExchangeFilterFunctions
98+
.addHeadersReactive(new CompositeReactiveHttpHeadersProvider(reactiveHeadersProviders));
99+
}
100+
89101
@Bean
90102
@Order(10)
91103
@ConditionalOnMissingBean(name = "rewriteEndpointUrlInstanceExchangeFilter")

spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/InstanceExchangeFilterFunctions.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import de.codecentric.boot.admin.server.domain.values.InstanceId;
4343
import de.codecentric.boot.admin.server.web.client.cookies.PerInstanceCookieStore;
4444
import de.codecentric.boot.admin.server.web.client.exception.ResolveEndpointException;
45+
import de.codecentric.boot.admin.server.web.client.reactive.ReactiveHttpHeadersProvider;
4546

4647
import static java.util.Arrays.asList;
4748
import static java.util.Collections.singletonList;
@@ -71,6 +72,15 @@ public static InstanceExchangeFilterFunction addHeaders(HttpHeadersProvider http
7172
};
7273
}
7374

75+
public static InstanceExchangeFilterFunction addHeadersReactive(ReactiveHttpHeadersProvider httpHeadersProvider) {
76+
return (instance, request, next) -> httpHeadersProvider.getHeaders(instance).flatMap((httpHeaders) -> {
77+
ClientRequest requestWithAdditionalHeaders = ClientRequest.from(request)
78+
.headers((headers) -> headers.addAll(httpHeaders)).build();
79+
80+
return next.exchange(requestWithAdditionalHeaders);
81+
}).switchIfEmpty(Mono.defer(() -> next.exchange(request)));
82+
}
83+
7484
public static InstanceExchangeFilterFunction rewriteEndpointUrl() {
7585
return (instance, request, next) -> {
7686
if (request.url().isAbsolute()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2014-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package de.codecentric.boot.admin.server.web.client.reactive;
18+
19+
import java.util.Arrays;
20+
import java.util.Collection;
21+
import java.util.List;
22+
23+
import org.springframework.http.HttpHeaders;
24+
import reactor.core.publisher.Mono;
25+
26+
import de.codecentric.boot.admin.server.domain.entities.Instance;
27+
28+
import static java.util.stream.Collectors.toList;
29+
30+
public class CompositeReactiveHttpHeadersProvider implements ReactiveHttpHeadersProvider {
31+
32+
private final Collection<ReactiveHttpHeadersProvider> delegates;
33+
34+
public CompositeReactiveHttpHeadersProvider(Collection<ReactiveHttpHeadersProvider> delegates) {
35+
this.delegates = delegates;
36+
}
37+
38+
@Override
39+
public Mono<HttpHeaders> getHeaders(Instance instance) {
40+
List<Mono<HttpHeaders>> headers = delegates.stream()
41+
.map((reactiveHttpHeadersProvider) -> reactiveHttpHeadersProvider.getHeaders(instance))
42+
.collect(toList());
43+
44+
return Mono.zip(headers, this::mergeMonosToHeaders);
45+
}
46+
47+
private HttpHeaders mergeMonosToHeaders(Object[] e) {
48+
return Arrays.stream(e).map(HttpHeaders.class::cast).reduce(new HttpHeaders(), (h1, h2) -> {
49+
h1.addAll(h2);
50+
return h1;
51+
});
52+
}
53+
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2014-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package de.codecentric.boot.admin.server.web.client.reactive;
18+
19+
import org.springframework.http.HttpHeaders;
20+
import reactor.core.publisher.Mono;
21+
22+
import de.codecentric.boot.admin.server.domain.entities.Instance;
23+
24+
/**
25+
* Is responsible to provide the {@link HttpHeaders} used to interact with the given
26+
* {@link Instance}.
27+
*/
28+
public interface ReactiveHttpHeadersProvider {
29+
30+
Mono<HttpHeaders> getHeaders(Instance instance);
31+
32+
}

spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/InstanceExchangeFilterFunctionsTest.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2021 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -287,6 +287,46 @@ void should_add_headers_from_provider() {
287287

288288
}
289289

290+
@Nested
291+
class AddHeadersReactive {
292+
293+
@Test
294+
void should_add_headers_from_provider() {
295+
InstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.addHeadersReactive((i) -> {
296+
HttpHeaders headers = new HttpHeaders();
297+
headers.add("X-INSTANCE-ID", i.getId().getValue());
298+
return Mono.just(headers);
299+
});
300+
301+
ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("/test"))
302+
.attribute(ATTRIBUTE_INSTANCE, INSTANCE).build();
303+
304+
Mono<ClientResponse> response = filter.filter(INSTANCE, request, (req) -> {
305+
assertThat(req.headers().get("X-INSTANCE-ID")).containsExactly(INSTANCE.getId().getValue());
306+
return Mono.just(ClientResponse.create(HttpStatus.OK).build());
307+
});
308+
309+
StepVerifier.create(response).expectNextCount(1).verifyComplete();
310+
}
311+
312+
@Test
313+
void should_pass_on_mono_empty() {
314+
InstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions
315+
.addHeadersReactive((i) -> Mono.empty());
316+
317+
ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("/test"))
318+
.attribute(ATTRIBUTE_INSTANCE, INSTANCE).build();
319+
320+
Mono<ClientResponse> response = filter.filter(INSTANCE, request, (req) -> {
321+
assertThat(req.headers().size()).isEqualTo(0);
322+
return Mono.just(ClientResponse.create(HttpStatus.OK).build());
323+
});
324+
325+
StepVerifier.create(response).expectNextCount(1).verifyComplete();
326+
}
327+
328+
}
329+
290330
@Nested
291331
class AddDefaultHeaders {
292332

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2014-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package de.codecentric.boot.admin.server.web.client.reactive;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.http.HttpHeaders;
21+
import reactor.core.publisher.Mono;
22+
import reactor.test.StepVerifier;
23+
24+
import static java.util.Arrays.asList;
25+
import static java.util.Collections.emptyList;
26+
import static java.util.Collections.singletonList;
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
public class CompositeReactiveHttpHeadersProviderTest {
30+
31+
@Test
32+
public void should_return_all_headers() {
33+
ReactiveHttpHeadersProvider provider = new CompositeReactiveHttpHeadersProvider(asList((i) -> {
34+
HttpHeaders headers = new HttpHeaders();
35+
headers.set("a", "1");
36+
headers.set("b", "2-a");
37+
return Mono.just(headers);
38+
}, (i) -> {
39+
HttpHeaders headers = new HttpHeaders();
40+
headers.set("b", "2-b");
41+
headers.set("c", "3");
42+
return Mono.just(headers);
43+
}));
44+
45+
StepVerifier.create(provider.getHeaders(null)).thenConsumeWhile((headers) -> {
46+
assertThat(headers.get("a")).isEqualTo(singletonList("1"));
47+
assertThat(headers.get("b")).isEqualTo(asList("2-a", "2-b"));
48+
assertThat(headers.get("c")).isEqualTo(singletonList("3"));
49+
return true;
50+
}).verifyComplete();
51+
}
52+
53+
@Test
54+
public void should_return_empty_headers() {
55+
CompositeReactiveHttpHeadersProvider provider = new CompositeReactiveHttpHeadersProvider(emptyList());
56+
57+
StepVerifier.create(provider.getHeaders(null)).thenConsumeWhile((headers) -> {
58+
assertThat(headers.size()).isEqualTo(0);
59+
return true;
60+
}).verifyComplete();
61+
}
62+
63+
}

0 commit comments

Comments
 (0)