Browse Source

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
pull/31413/head
Jakob Fels 2 years ago committed by Arjen Poutsma
parent
commit
ad50de169c
  1. 33
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultResourceCacheLookupStrategy.java
  2. 36
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceCacheLookupStrategy.java
  3. 17
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java
  4. 10
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java
  5. 22
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java
  6. 25
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java

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

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
/*
* Copyright 2002-2023 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
*
* https://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 org.springframework.web.reactive.function.server;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
/**
* Default lookup that performs no caching.
* @author Jakob Fels
*/
public class DefaultResourceCacheLookupStrategy implements ResourceCacheLookupStrategy {
@Override
public CacheControl lookupCacheControl(Resource resource) {
return CacheControl.empty();
}
}

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

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
/*
* Copyright 2002-2023 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
*
* https://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 org.springframework.web.reactive.function.server;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
/**
* Strategy interface to allow for looking up cache control for a given resource.
*
* @author Jakob Fels
*/
public interface ResourceCacheLookupStrategy {
static ResourceCacheLookupStrategy noCaching() {
return new DefaultResourceCacheLookupStrategy();
}
CacheControl lookupCacheControl(Resource resource);
}

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

@ -27,6 +27,7 @@ import java.util.Set; @@ -27,6 +27,7 @@ import java.util.Set;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
@ -45,10 +46,18 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> { @@ -45,10 +46,18 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
private final Resource resource;
private final CacheControl cacheControl;
public ResourceHandlerFunction(Resource resource) {
this.resource = resource;
this.cacheControl = CacheControl.empty();
}
public ResourceHandlerFunction(Resource resource, ResourceCacheLookupStrategy strategy) {
this.resource = resource;
this.cacheControl = strategy.lookupCacheControl(resource);
}
@ -56,12 +65,16 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> { @@ -56,12 +65,16 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
public Mono<ServerResponse> handle(ServerRequest request) {
HttpMethod method = request.method();
if (HttpMethod.GET.equals(method)) {
return EntityResponse.fromObject(this.resource).build()
return EntityResponse.fromObject(this.resource)
.cacheControl(this.cacheControl)
.build()
.map(response -> response);
}
else if (HttpMethod.HEAD.equals(method)) {
Resource headResource = new HeadMethodResource(this.resource);
return EntityResponse.fromObject(headResource).build()
return EntityResponse.fromObject(headResource)
.cacheControl(this.cacheControl)
.build()
.map(response -> response);
}
else if (HttpMethod.OPTIONS.equals(method)) {

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

@ -241,11 +241,21 @@ class RouterFunctionBuilder implements RouterFunctions.Builder { @@ -241,11 +241,21 @@ class RouterFunctionBuilder implements RouterFunctions.Builder {
return add(RouterFunctions.resources(pattern, location));
}
@Override
public RouterFunctions.Builder resources(String pattern, Resource location, ResourceCacheLookupStrategy resourceCacheLookupStrategy) {
return add(RouterFunctions.resources(pattern,location,resourceCacheLookupStrategy));
}
@Override
public RouterFunctions.Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
return add(RouterFunctions.resources(lookupFunction));
}
@Override
public RouterFunctions.Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy resourceCacheLookupStrategy) {
return add(RouterFunctions.resources(lookupFunction,resourceCacheLookupStrategy));
}
@Override
public RouterFunctions.Builder nest(RequestPredicate predicate,
Consumer<RouterFunctions.Builder> builderConsumer) {

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

@ -156,7 +156,10 @@ public abstract class RouterFunctions { @@ -156,7 +156,10 @@ public abstract class RouterFunctions {
* @see #resourceLookupFunction(String, Resource)
*/
public static RouterFunction<ServerResponse> resources(String pattern, Resource location) {
return resources(resourceLookupFunction(pattern, location));
return resources(resourceLookupFunction(pattern, location), ResourceCacheLookupStrategy.noCaching());
}
public static RouterFunction<ServerResponse> resources(String pattern, Resource location, ResourceCacheLookupStrategy lookupStrategy) {
return resources(resourceLookupFunction(pattern, location), lookupStrategy);
}
/**
@ -186,7 +189,10 @@ public abstract class RouterFunctions { @@ -186,7 +189,10 @@ public abstract class RouterFunctions {
* @return a router function that routes to resources
*/
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
return new ResourcesRouterFunction(lookupFunction);
return new ResourcesRouterFunction(lookupFunction, ResourceCacheLookupStrategy.noCaching());
}
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy lookupStrategy) {
return new ResourcesRouterFunction(lookupFunction, lookupStrategy);
}
/**
@ -652,6 +658,7 @@ public abstract class RouterFunctions { @@ -652,6 +658,7 @@ public abstract class RouterFunctions {
* @return this builder
*/
Builder resources(String pattern, Resource location);
Builder resources(String pattern, Resource location, ResourceCacheLookupStrategy resourceCacheLookupStrategy);
/**
* Route to resources using the provided lookup function. If the lookup function provides a
@ -661,6 +668,7 @@ public abstract class RouterFunctions { @@ -661,6 +668,7 @@ public abstract class RouterFunctions {
* @return this builder
*/
Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction);
Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy resourceCacheLookupStrategy);
/**
* Route to the supplied router function if the given request predicate applies. This method
@ -1142,14 +1150,22 @@ public abstract class RouterFunctions { @@ -1142,14 +1150,22 @@ public abstract class RouterFunctions {
private final Function<ServerRequest, Mono<Resource>> lookupFunction;
private final ResourceCacheLookupStrategy lookupStrategy;
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction) {
this(lookupFunction, ResourceCacheLookupStrategy.noCaching());
}
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy lookupStrategy) {
Assert.notNull(lookupFunction, "Function must not be null");
Assert.notNull(lookupStrategy, "Strategy must not be null");
this.lookupFunction = lookupFunction;
this.lookupStrategy = lookupStrategy;
}
@Override
public Mono<HandlerFunction<ServerResponse>> route(ServerRequest request) {
return this.lookupFunction.apply(request).map(ResourceHandlerFunction::new);
return this.lookupFunction.apply(request).map(resource -> new ResourceHandlerFunction(resource, this.lookupStrategy));
}
@Override

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

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.web.reactive.function.server;
import java.io.IOException;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -28,6 +29,8 @@ import reactor.test.StepVerifier; @@ -28,6 +29,8 @@ import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
@ -130,6 +133,28 @@ public class RouterFunctionBuilderTests { @@ -130,6 +133,28 @@ public class RouterFunctionBuilderTests {
.verifyComplete();
}
@Test
public void resourcesCaching() {
Resource resource = new ClassPathResource("/org/springframework/web/reactive/function/server/");
assertThat(resource.exists()).isTrue();
RouterFunction<ServerResponse> route = RouterFunctions.route()
.resources("/resources/**", resource, resource1 -> CacheControl.maxAge(Duration.ofSeconds(60)))
.build();
MockServerHttpRequest mockRequest = MockServerHttpRequest.get("https://localhost/resources/response.txt").build();
ServerRequest resourceRequest = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
Mono<String> responseMono = route.route(resourceRequest)
.flatMap(handlerFunction -> handlerFunction.handle(resourceRequest))
.map(ServerResponse::headers)
.mapNotNull(HttpHeaders::getCacheControl);
StepVerifier.create(responseMono)
.expectNext("max-age=60")
.verifyComplete();
}
@Test
public void nest() {
RouterFunction<?> route = RouterFunctions.route()

Loading…
Cancel
Save