Skip to content

Commit 0c2c66b

Browse files
Christophe Maillardrstoyanchev
Christophe Maillard
authored andcommitted
Add HttpHandlerDecoratorFactory
See gh-26502
1 parent bd8e682 commit 0c2c66b

File tree

3 files changed

+134
-12
lines changed

3 files changed

+134
-12
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-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 org.springframework.http.server.reactive;
18+
19+
import org.springframework.context.ApplicationContext;
20+
21+
import java.util.function.Function;
22+
import java.util.function.UnaryOperator;
23+
24+
/**
25+
* Allows registering a bean that will decorate the instance of {@link HttpHandler},
26+
* used by {@link org.springframework.web.server.adapter.WebHttpHandlerBuilder#applicationContext(ApplicationContext)};
27+
*
28+
* @since 5.3.4
29+
*/
30+
public interface HttpHandlerDecoratorFactory extends UnaryOperator<HttpHandler> {
31+
32+
static HttpHandlerDecoratorFactory identity() {
33+
return x -> x;
34+
}
35+
36+
default Function<HttpHandler, HttpHandler> toFunction() {
37+
return this;
38+
}
39+
40+
}

spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
2929
import org.springframework.http.codec.ServerCodecConfigurer;
3030
import org.springframework.http.server.reactive.HttpHandler;
31+
import org.springframework.http.server.reactive.HttpHandlerDecoratorFactory;
3132
import org.springframework.lang.Nullable;
3233
import org.springframework.util.Assert;
3334
import org.springframework.util.ObjectUtils;
@@ -77,7 +78,6 @@ public final class WebHttpHandlerBuilder {
7778
/** Well-known name for the ForwardedHeaderTransformer in the bean factory. */
7879
public static final String FORWARDED_HEADER_TRANSFORMER_BEAN_NAME = "forwardedHeaderTransformer";
7980

80-
8181
private final WebHandler webHandler;
8282

8383
@Nullable
@@ -87,6 +87,9 @@ public final class WebHttpHandlerBuilder {
8787

8888
private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>();
8989

90+
@Nullable
91+
private Function<HttpHandler, HttpHandler> httpHandlerDecorator;
92+
9093
@Nullable
9194
private WebSessionManager sessionManager;
9295

@@ -99,9 +102,6 @@ public final class WebHttpHandlerBuilder {
99102
@Nullable
100103
private ForwardedHeaderTransformer forwardedHeaderTransformer;
101104

102-
@Nullable
103-
private Function<HttpHandler, HttpHandler> httpHandlerDecorator;
104-
105105

106106
/**
107107
* Private constructor to use when initialized from an ApplicationContext.
@@ -147,6 +147,8 @@ public static WebHttpHandlerBuilder webHandler(WebHandler webHandler) {
147147
* see {@link AnnotationAwareOrderComparator}.
148148
* <li>{@link WebExceptionHandler} [0..N] -- detected by type and
149149
* ordered.
150+
* <li>{@link HttpHandlerDecoratorFactory} [0..N] -- detected by type and
151+
* ordered.
150152
* <li>{@link WebSessionManager} [0..1] -- looked up by the name
151153
* {@link #WEB_SESSION_MANAGER_BEAN_NAME}.
152154
* <li>{@link ServerCodecConfigurer} [0..1] -- looked up by the name
@@ -158,6 +160,7 @@ public static WebHttpHandlerBuilder webHandler(WebHandler webHandler) {
158160
* @return the prepared builder
159161
*/
160162
public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
163+
161164
WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder(
162165
context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context);
163166

@@ -166,12 +169,20 @@ public static WebHttpHandlerBuilder applicationContext(ApplicationContext contex
166169
.orderedStream()
167170
.collect(Collectors.toList());
168171
builder.filters(filters -> filters.addAll(webFilters));
172+
169173
List<WebExceptionHandler> exceptionHandlers = context
170174
.getBeanProvider(WebExceptionHandler.class)
171175
.orderedStream()
172176
.collect(Collectors.toList());
173177
builder.exceptionHandlers(handlers -> handlers.addAll(exceptionHandlers));
174178

179+
Function<HttpHandler, HttpHandler> httpHandlerDecorator = context
180+
.getBeanProvider(HttpHandlerDecoratorFactory.class)
181+
.orderedStream()
182+
.map(HttpHandlerDecoratorFactory::toFunction)
183+
.reduce(Function.identity(), Function::andThen);
184+
builder.httpHandlerDecorator(httpHandlerDecorator);
185+
175186
try {
176187
builder.sessionManager(
177188
context.getBean(WEB_SESSION_MANAGER_BEAN_NAME, WebSessionManager.class));

spring-web/src/test/java/org/springframework/web/server/adapter/WebHttpHandlerBuilderTests.java

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,7 @@
1616

1717
package org.springframework.web.server.adapter;
1818

19-
import java.nio.charset.StandardCharsets;
20-
import java.util.Collections;
21-
import java.util.concurrent.atomic.AtomicBoolean;
22-
import java.util.function.BiFunction;
23-
2419
import org.junit.jupiter.api.Test;
25-
import reactor.core.publisher.Flux;
26-
import reactor.core.publisher.Mono;
27-
2820
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2921
import org.springframework.context.annotation.Bean;
3022
import org.springframework.context.annotation.Configuration;
@@ -33,13 +25,22 @@
3325
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
3426
import org.springframework.http.HttpHeaders;
3527
import org.springframework.http.server.reactive.HttpHandler;
28+
import org.springframework.http.server.reactive.HttpHandlerDecoratorFactory;
3629
import org.springframework.http.server.reactive.ServerHttpRequest;
3730
import org.springframework.web.server.ServerWebExchange;
3831
import org.springframework.web.server.WebExceptionHandler;
3932
import org.springframework.web.server.WebFilter;
4033
import org.springframework.web.server.WebHandler;
4134
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
4235
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse;
36+
import reactor.core.publisher.Flux;
37+
import reactor.core.publisher.Mono;
38+
39+
import java.nio.charset.StandardCharsets;
40+
import java.util.Collections;
41+
import java.util.concurrent.atomic.AtomicBoolean;
42+
import java.util.concurrent.atomic.AtomicLong;
43+
import java.util.function.BiFunction;
4344

4445
import static java.time.Duration.ofMillis;
4546
import static org.assertj.core.api.Assertions.assertThat;
@@ -139,12 +140,82 @@ void httpHandlerDecorator() {
139140
assertThat(success.get()).isTrue();
140141
}
141142

143+
@Test
144+
void httpHandlerDecoratorBeans() {
145+
146+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
147+
context.register(HttpHandlerDecoratorBeansConfig.class);
148+
context.refresh();
149+
HttpHandler builder = WebHttpHandlerBuilder.applicationContext(context).build();
150+
151+
builder.handle(MockServerHttpRequest.get("/").build(), new MockServerHttpResponse()).block();
152+
153+
AtomicLong decorator1NanoTime = context.getBean("decorator1NanoTime", AtomicLong.class);
154+
AtomicLong decorator2NanoTime = context.getBean("decorator2NanoTime", AtomicLong.class);
155+
AtomicLong decorator3NanoTime = context.getBean("decorator3NanoTime", AtomicLong.class);
156+
assertThat(decorator1NanoTime).hasValueLessThan(decorator3NanoTime.get());
157+
assertThat(decorator3NanoTime).hasValueLessThan(decorator2NanoTime.get());
158+
159+
}
160+
142161
private static Mono<Void> writeToResponse(ServerWebExchange exchange, String value) {
143162
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
144163
DataBuffer buffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes);
145164
return exchange.getResponse().writeWith(Flux.just(buffer));
146165
}
147166

167+
@Configuration
168+
static class HttpHandlerDecoratorBeansConfig {
169+
170+
@Bean
171+
public WebHandler webHandler() {
172+
return exchange -> Mono.empty();
173+
}
174+
175+
@Bean
176+
public AtomicLong decorator1NanoTime() {
177+
return new AtomicLong();
178+
}
179+
180+
@Bean
181+
@Order(1)
182+
public HttpHandlerDecoratorFactory decorator1() {
183+
return handler -> {
184+
decorator1NanoTime().set(System.nanoTime());
185+
return handler;
186+
};
187+
}
188+
189+
@Bean
190+
public AtomicLong decorator2NanoTime() {
191+
return new AtomicLong();
192+
}
193+
194+
@Bean
195+
@Order(3)
196+
public HttpHandlerDecoratorFactory decorator2() {
197+
return handler -> {
198+
decorator2NanoTime().set(System.nanoTime());
199+
return handler;
200+
};
201+
}
202+
203+
@Bean
204+
public AtomicLong decorator3NanoTime() {
205+
return new AtomicLong();
206+
}
207+
208+
@Bean
209+
@Order(2)
210+
public HttpHandlerDecoratorFactory decorator3() {
211+
return handler -> {
212+
decorator3NanoTime().set(System.nanoTime());
213+
return handler;
214+
};
215+
}
216+
217+
}
218+
148219

149220
@Configuration
150221
@SuppressWarnings("unused")

0 commit comments

Comments
 (0)