Skip to content

Commit ad50de1

Browse files
jakobfelsatdmpoutsma
authored andcommitted
Provide access to HTTP headers in resource routing
This commit adds additional overloaded methods that allow for HTTP header manipulation of served resources. Closes gh-29985
1 parent faaf3a6 commit ad50de1

File tree

6 files changed

+138
-5
lines changed

6 files changed

+138
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2002-2023 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.web.reactive.function.server;
18+
19+
import org.springframework.core.io.Resource;
20+
import org.springframework.http.CacheControl;
21+
22+
/**
23+
* Default lookup that performs no caching.
24+
* @author Jakob Fels
25+
*/
26+
public class DefaultResourceCacheLookupStrategy implements ResourceCacheLookupStrategy {
27+
28+
@Override
29+
public CacheControl lookupCacheControl(Resource resource) {
30+
return CacheControl.empty();
31+
}
32+
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2002-2023 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.web.reactive.function.server;
18+
19+
import org.springframework.core.io.Resource;
20+
import org.springframework.http.CacheControl;
21+
22+
/**
23+
* Strategy interface to allow for looking up cache control for a given resource.
24+
*
25+
* @author Jakob Fels
26+
*/
27+
public interface ResourceCacheLookupStrategy {
28+
29+
30+
static ResourceCacheLookupStrategy noCaching() {
31+
return new DefaultResourceCacheLookupStrategy();
32+
}
33+
34+
CacheControl lookupCacheControl(Resource resource);
35+
36+
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import reactor.core.publisher.Mono;
2828

2929
import org.springframework.core.io.Resource;
30+
import org.springframework.http.CacheControl;
3031
import org.springframework.http.HttpMethod;
3132
import org.springframework.http.HttpStatus;
3233
import org.springframework.lang.Nullable;
@@ -45,23 +46,35 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
4546

4647

4748
private final Resource resource;
49+
private final CacheControl cacheControl;
4850

4951

5052
public ResourceHandlerFunction(Resource resource) {
5153
this.resource = resource;
54+
this.cacheControl = CacheControl.empty();
55+
}
56+
57+
58+
public ResourceHandlerFunction(Resource resource, ResourceCacheLookupStrategy strategy) {
59+
this.resource = resource;
60+
this.cacheControl = strategy.lookupCacheControl(resource);
5261
}
5362

5463

5564
@Override
5665
public Mono<ServerResponse> handle(ServerRequest request) {
5766
HttpMethod method = request.method();
5867
if (HttpMethod.GET.equals(method)) {
59-
return EntityResponse.fromObject(this.resource).build()
68+
return EntityResponse.fromObject(this.resource)
69+
.cacheControl(this.cacheControl)
70+
.build()
6071
.map(response -> response);
6172
}
6273
else if (HttpMethod.HEAD.equals(method)) {
6374
Resource headResource = new HeadMethodResource(this.resource);
64-
return EntityResponse.fromObject(headResource).build()
75+
return EntityResponse.fromObject(headResource)
76+
.cacheControl(this.cacheControl)
77+
.build()
6578
.map(response -> response);
6679
}
6780
else if (HttpMethod.OPTIONS.equals(method)) {

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,21 @@ public RouterFunctions.Builder resources(String pattern, Resource location) {
241241
return add(RouterFunctions.resources(pattern, location));
242242
}
243243

244+
@Override
245+
public RouterFunctions.Builder resources(String pattern, Resource location, ResourceCacheLookupStrategy resourceCacheLookupStrategy) {
246+
return add(RouterFunctions.resources(pattern,location,resourceCacheLookupStrategy));
247+
}
248+
244249
@Override
245250
public RouterFunctions.Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
246251
return add(RouterFunctions.resources(lookupFunction));
247252
}
248253

254+
@Override
255+
public RouterFunctions.Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy resourceCacheLookupStrategy) {
256+
return add(RouterFunctions.resources(lookupFunction,resourceCacheLookupStrategy));
257+
}
258+
249259
@Override
250260
public RouterFunctions.Builder nest(RequestPredicate predicate,
251261
Consumer<RouterFunctions.Builder> builderConsumer) {

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,10 @@ public static <T extends ServerResponse> RouterFunction<T> nest(
156156
* @see #resourceLookupFunction(String, Resource)
157157
*/
158158
public static RouterFunction<ServerResponse> resources(String pattern, Resource location) {
159-
return resources(resourceLookupFunction(pattern, location));
159+
return resources(resourceLookupFunction(pattern, location), ResourceCacheLookupStrategy.noCaching());
160+
}
161+
public static RouterFunction<ServerResponse> resources(String pattern, Resource location, ResourceCacheLookupStrategy lookupStrategy) {
162+
return resources(resourceLookupFunction(pattern, location), lookupStrategy);
160163
}
161164

162165
/**
@@ -186,7 +189,10 @@ public static Function<ServerRequest, Mono<Resource>> resourceLookupFunction(Str
186189
* @return a router function that routes to resources
187190
*/
188191
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
189-
return new ResourcesRouterFunction(lookupFunction);
192+
return new ResourcesRouterFunction(lookupFunction, ResourceCacheLookupStrategy.noCaching());
193+
}
194+
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy lookupStrategy) {
195+
return new ResourcesRouterFunction(lookupFunction, lookupStrategy);
190196
}
191197

192198
/**
@@ -652,6 +658,7 @@ public interface Builder {
652658
* @return this builder
653659
*/
654660
Builder resources(String pattern, Resource location);
661+
Builder resources(String pattern, Resource location, ResourceCacheLookupStrategy resourceCacheLookupStrategy);
655662

656663
/**
657664
* Route to resources using the provided lookup function. If the lookup function provides a
@@ -661,6 +668,7 @@ public interface Builder {
661668
* @return this builder
662669
*/
663670
Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction);
671+
Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy resourceCacheLookupStrategy);
664672

665673
/**
666674
* Route to the supplied router function if the given request predicate applies. This method
@@ -1142,14 +1150,22 @@ private static class ResourcesRouterFunction extends AbstractRouterFunction<Ser
11421150

11431151
private final Function<ServerRequest, Mono<Resource>> lookupFunction;
11441152

1153+
private final ResourceCacheLookupStrategy lookupStrategy;
1154+
11451155
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction) {
1156+
this(lookupFunction, ResourceCacheLookupStrategy.noCaching());
1157+
}
1158+
1159+
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy lookupStrategy) {
11461160
Assert.notNull(lookupFunction, "Function must not be null");
1161+
Assert.notNull(lookupStrategy, "Strategy must not be null");
11471162
this.lookupFunction = lookupFunction;
1163+
this.lookupStrategy = lookupStrategy;
11481164
}
11491165

11501166
@Override
11511167
public Mono<HandlerFunction<ServerResponse>> route(ServerRequest request) {
1152-
return this.lookupFunction.apply(request).map(ResourceHandlerFunction::new);
1168+
return this.lookupFunction.apply(request).map(resource -> new ResourceHandlerFunction(resource, this.lookupStrategy));
11531169
}
11541170

11551171
@Override

spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.web.reactive.function.server;
1818

1919
import java.io.IOException;
20+
import java.time.Duration;
2021
import java.util.Collections;
2122
import java.util.List;
2223
import java.util.Map;
@@ -28,6 +29,8 @@
2829

2930
import org.springframework.core.io.ClassPathResource;
3031
import org.springframework.core.io.Resource;
32+
import org.springframework.http.CacheControl;
33+
import org.springframework.http.HttpHeaders;
3134
import org.springframework.http.HttpStatus;
3235
import org.springframework.http.HttpStatusCode;
3336
import org.springframework.http.MediaType;
@@ -130,6 +133,28 @@ public void resources() {
130133
.verifyComplete();
131134
}
132135

136+
@Test
137+
public void resourcesCaching() {
138+
Resource resource = new ClassPathResource("/org/springframework/web/reactive/function/server/");
139+
assertThat(resource.exists()).isTrue();
140+
141+
RouterFunction<ServerResponse> route = RouterFunctions.route()
142+
.resources("/resources/**", resource, resource1 -> CacheControl.maxAge(Duration.ofSeconds(60)))
143+
.build();
144+
145+
MockServerHttpRequest mockRequest = MockServerHttpRequest.get("https://localhost/resources/response.txt").build();
146+
ServerRequest resourceRequest = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
147+
148+
Mono<String> responseMono = route.route(resourceRequest)
149+
.flatMap(handlerFunction -> handlerFunction.handle(resourceRequest))
150+
.map(ServerResponse::headers)
151+
.mapNotNull(HttpHeaders::getCacheControl);
152+
153+
StepVerifier.create(responseMono)
154+
.expectNext("max-age=60")
155+
.verifyComplete();
156+
}
157+
133158
@Test
134159
public void nest() {
135160
RouterFunction<?> route = RouterFunctions.route()

0 commit comments

Comments
 (0)