From 307a2c7d7be1fccd97e233bec065c28cdc653730 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 3 Oct 2023 16:44:20 +0200 Subject: [PATCH] Polishing external contribution See gh-29985 --- .../server/DefaultEntityResponseBuilder.java | 9 +- .../DefaultResourceCacheLookupStrategy.java | 33 ------- .../function/server/EntityResponse.java | 14 ++- .../server/ResourceCacheLookupStrategy.java | 36 -------- .../server/ResourceHandlerFunction.java | 20 ++--- .../server/RouterFunctionBuilder.java | 16 ++-- .../function/server/RouterFunctions.java | 85 +++++++++++++++---- .../server/ResourceHandlerFunctionTests.java | 4 +- .../server/RouterFunctionBuilderTests.java | 4 +- .../function/ResourceHandlerFunction.java | 17 +++- .../function/RouterFunctionBuilder.java | 18 +++- .../web/servlet/function/RouterFunctions.java | 77 +++++++++++++++-- .../ResourceHandlerFunctionTests.java | 4 +- .../function/RouterFunctionBuilderTests.java | 21 ++++- 14 files changed, 236 insertions(+), 122 deletions(-) delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultResourceCacheLookupStrategy.java delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceCacheLookupStrategy.java diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java index 80a3034a16a..87697d0da1e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * 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. @@ -115,6 +115,13 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { return this; } + @Override + public EntityResponse.Builder headers(Consumer headersConsumer) { + Assert.notNull(headersConsumer, "HeadersConsumer must not be null"); + headersConsumer.accept(this.headers); + return this; + } + @Override public EntityResponse.Builder allow(HttpMethod... allowedMethods) { this.headers.setAllow(new LinkedHashSet<>(Arrays.asList(allowedMethods))); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultResourceCacheLookupStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultResourceCacheLookupStrategy.java deleted file mode 100644 index 2cc05e120a5..00000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultResourceCacheLookupStrategy.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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(); - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java index f4e02acd1b9..8bbbb32320b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * 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. @@ -149,6 +149,18 @@ public interface EntityResponse extends ServerResponse { */ Builder headers(HttpHeaders headers); + /** + * Manipulate this entity's headers with the given consumer. The + * headers provided to the consumer are "live", so that the consumer can be used to + * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, + * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other + * {@link HttpHeaders} methods. + * @param headersConsumer a function that consumes the {@code HttpHeaders} + * @return this builder + * @since 6.1 + */ + Builder headers(Consumer headersConsumer); + /** * Set the HTTP status. * @param status the response status diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceCacheLookupStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceCacheLookupStrategy.java deleted file mode 100644 index 462cc192802..00000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceCacheLookupStrategy.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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); - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java index 783ce93e959..2fa724917de 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * 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. @@ -23,11 +23,12 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; import java.util.Set; +import java.util.function.BiConsumer; import reactor.core.publisher.Mono; import org.springframework.core.io.Resource; -import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; @@ -46,18 +47,13 @@ class ResourceHandlerFunction implements HandlerFunction { private final Resource resource; - private final CacheControl cacheControl; - - public ResourceHandlerFunction(Resource resource) { - this.resource = resource; - this.cacheControl = CacheControl.empty(); - } + private final BiConsumer headersConsumer; - public ResourceHandlerFunction(Resource resource, ResourceCacheLookupStrategy strategy) { + public ResourceHandlerFunction(Resource resource, BiConsumer headersConsumer) { this.resource = resource; - this.cacheControl = strategy.lookupCacheControl(resource); + this.headersConsumer = headersConsumer; } @@ -66,14 +62,14 @@ class ResourceHandlerFunction implements HandlerFunction { HttpMethod method = request.method(); if (HttpMethod.GET.equals(method)) { return EntityResponse.fromObject(this.resource) - .cacheControl(this.cacheControl) + .headers(headers -> this.headersConsumer.accept(this.resource, headers)) .build() .map(response -> response); } else if (HttpMethod.HEAD.equals(method)) { Resource headResource = new HeadMethodResource(this.resource); return EntityResponse.fromObject(headResource) - .cacheControl(this.cacheControl) + .headers(headers -> this.headersConsumer.accept(this.resource, headers)) .build() .map(response -> response); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java index 308d3171401..716566a6de6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * 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. @@ -19,6 +19,7 @@ package org.springframework.web.reactive.function.server; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -30,6 +31,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.util.Assert; @@ -242,8 +244,10 @@ class RouterFunctionBuilder implements RouterFunctions.Builder { } @Override - public RouterFunctions.Builder resources(String pattern, Resource location, ResourceCacheLookupStrategy resourceCacheLookupStrategy) { - return add(RouterFunctions.resources(pattern,location,resourceCacheLookupStrategy)); + public RouterFunctions.Builder resources(String pattern, Resource location, + BiConsumer headersConsumer) { + + return add(RouterFunctions.resources(pattern, location, headersConsumer)); } @Override @@ -252,8 +256,10 @@ class RouterFunctionBuilder implements RouterFunctions.Builder { } @Override - public RouterFunctions.Builder resources(Function> lookupFunction, ResourceCacheLookupStrategy resourceCacheLookupStrategy) { - return add(RouterFunctions.resources(lookupFunction,resourceCacheLookupStrategy)); + public RouterFunctions.Builder resources(Function> lookupFunction, + BiConsumer headersConsumer) { + + return add(RouterFunctions.resources(lookupFunction, headersConsumer)); } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java index 9de4c492971..cf6a1eed034 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -32,6 +33,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.server.reactive.HttpHandler; @@ -156,10 +158,26 @@ public abstract class RouterFunctions { * @see #resourceLookupFunction(String, Resource) */ public static RouterFunction resources(String pattern, Resource location) { - return resources(resourceLookupFunction(pattern, location), ResourceCacheLookupStrategy.noCaching()); + return resources(resourceLookupFunction(pattern, location), (resource, httpHeaders) -> {}); } - public static RouterFunction resources(String pattern, Resource location, ResourceCacheLookupStrategy lookupStrategy) { - return resources(resourceLookupFunction(pattern, location), lookupStrategy); + + /** + * Route requests that match the given pattern to resources relative to the given root location. + * For instance + *
+	 * Resource location = new FileSystemResource("public-resources/");
+	 * RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
+     * 
+ * @param pattern the pattern to match + * @param location the location directory relative to which resources should be resolved + * @param headersConsumer provides access to the HTTP headers for served resources + * @return a router function that routes to resources + * @since 6.1 + * @see #resourceLookupFunction(String, Resource) + */ + public static RouterFunction resources(String pattern, Resource location, + BiConsumer headersConsumer) { + return resources(resourceLookupFunction(pattern, location), headersConsumer); } /** @@ -189,10 +207,21 @@ public abstract class RouterFunctions { * @return a router function that routes to resources */ public static RouterFunction resources(Function> lookupFunction) { - return new ResourcesRouterFunction(lookupFunction, ResourceCacheLookupStrategy.noCaching()); + return new ResourcesRouterFunction(lookupFunction, (resource, httpHeaders) -> {}); } - public static RouterFunction resources(Function> lookupFunction, ResourceCacheLookupStrategy lookupStrategy) { - return new ResourcesRouterFunction(lookupFunction, lookupStrategy); + + /** + * Route to resources using the provided lookup function. If the lookup function provides a + * {@link Resource} for the given request, it will be it will be exposed using a + * {@link HandlerFunction} that handles GET, HEAD, and OPTIONS requests. + * @param lookupFunction the function to provide a {@link Resource} given the {@link ServerRequest} + * @param headersConsumer provides access to the HTTP headers for served resources + * @return a router function that routes to resources + * @since 6.1 + */ + public static RouterFunction resources(Function> lookupFunction, + BiConsumer headersConsumer) { + return new ResourcesRouterFunction(lookupFunction, headersConsumer); } /** @@ -658,7 +687,21 @@ public abstract class RouterFunctions { * @return this builder */ Builder resources(String pattern, Resource location); - Builder resources(String pattern, Resource location, ResourceCacheLookupStrategy resourceCacheLookupStrategy); + + /** + * Route requests that match the given pattern to resources relative to the given root location. + * For instance + *
+		 * Resource location = new FileSystemResource("public-resources/");
+		 * RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
+	     * 
+ * @param pattern the pattern to match + * @param location the location directory relative to which resources should be resolved + * @param headersConsumer provides access to the HTTP headers for served resources + * @return this builder + * @since 6.1 + */ + Builder resources(String pattern, Resource location, BiConsumer headersConsumer); /** * Route to resources using the provided lookup function. If the lookup function provides a @@ -668,7 +711,17 @@ public abstract class RouterFunctions { * @return this builder */ Builder resources(Function> lookupFunction); - Builder resources(Function> lookupFunction, ResourceCacheLookupStrategy resourceCacheLookupStrategy); + + /** + * Route to resources using the provided lookup function. If the lookup function provides a + * {@link Resource} for the given request, it will be it will be exposed using a + * {@link HandlerFunction} that handles GET, HEAD, and OPTIONS requests. + * @param lookupFunction the function to provide a {@link Resource} given the {@link ServerRequest} + * @param headersConsumer provides access to the HTTP headers for served resources + * @return this builder + * @since 6.1 + */ + Builder resources(Function> lookupFunction, BiConsumer headersConsumer); /** * Route to the supplied router function if the given request predicate applies. This method @@ -1150,22 +1203,20 @@ public abstract class RouterFunctions { private final Function> lookupFunction; - private final ResourceCacheLookupStrategy lookupStrategy; + private final BiConsumer headersConsumer; - public ResourcesRouterFunction(Function> lookupFunction) { - this(lookupFunction, ResourceCacheLookupStrategy.noCaching()); - } - public ResourcesRouterFunction(Function> lookupFunction, ResourceCacheLookupStrategy lookupStrategy) { + public ResourcesRouterFunction(Function> lookupFunction, + BiConsumer headersConsumer) { Assert.notNull(lookupFunction, "Function must not be null"); - Assert.notNull(lookupStrategy, "Strategy must not be null"); + Assert.notNull(headersConsumer, "HeadersConsumer must not be null"); this.lookupFunction = lookupFunction; - this.lookupStrategy = lookupStrategy; + this.headersConsumer = headersConsumer; } @Override public Mono> route(ServerRequest request) { - return this.lookupFunction.apply(request).map(resource -> new ResourceHandlerFunction(resource, this.lookupStrategy)); + return this.lookupFunction.apply(request).map(resource -> new ResourceHandlerFunction(resource, this.headersConsumer)); } @Override diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java index 086eedaa7d9..376fe409bcd 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * 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. @@ -47,7 +47,7 @@ public class ResourceHandlerFunctionTests { private final Resource resource = new ClassPathResource("response.txt", getClass()); - private final ResourceHandlerFunction handlerFunction = new ResourceHandlerFunction(this.resource); + private final ResourceHandlerFunction handlerFunction = new ResourceHandlerFunction(this.resource, (r, h) -> {}); private ServerResponse.Context context; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java index 95e312ebf07..053add50fe5 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -139,7 +139,7 @@ public class RouterFunctionBuilderTests { assertThat(resource.exists()).isTrue(); RouterFunction route = RouterFunctions.route() - .resources("/resources/**", resource, resource1 -> CacheControl.maxAge(Duration.ofSeconds(60))) + .resources("/resources/**", resource, (r, headers) -> headers.setCacheControl(CacheControl.maxAge(Duration.ofSeconds(60)))) .build(); MockServerHttpRequest mockRequest = MockServerHttpRequest.get("https://localhost/resources/response.txt").build(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java index 0012c3d0da8..3ee4a3df601 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * 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. @@ -23,8 +23,10 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; import java.util.Set; +import java.util.function.BiConsumer; import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; @@ -43,9 +45,12 @@ class ResourceHandlerFunction implements HandlerFunction { private final Resource resource; + private final BiConsumer headersConsumer; - public ResourceHandlerFunction(Resource resource) { + + public ResourceHandlerFunction(Resource resource, BiConsumer headersConsumer) { this.resource = resource; + this.headersConsumer = headersConsumer; } @@ -53,11 +58,15 @@ class ResourceHandlerFunction implements HandlerFunction { public ServerResponse handle(ServerRequest request) { HttpMethod method = request.method(); if (HttpMethod.GET.equals(method)) { - return EntityResponse.fromObject(this.resource).build(); + return EntityResponse.fromObject(this.resource) + .headers(headers -> this.headersConsumer.accept(this.resource, headers)) + .build(); } else if (HttpMethod.HEAD.equals(method)) { Resource headResource = new HeadMethodResource(this.resource); - return EntityResponse.fromObject(headResource).build(); + return EntityResponse.fromObject(headResource) + .headers(headers -> this.headersConsumer.accept(this.resource, headers)) + .build(); } else if (HttpMethod.OPTIONS.equals(method)) { return ServerResponse.ok() diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java index e8730910a14..0a845ec8b43 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * 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. @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -28,6 +29,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.util.Assert; @@ -239,11 +241,25 @@ class RouterFunctionBuilder implements RouterFunctions.Builder { return add(RouterFunctions.resources(pattern, location)); } + @Override + public RouterFunctions.Builder resources(String pattern, Resource location, + BiConsumer headersConsumer) { + + return add(RouterFunctions.resources(pattern, location, headersConsumer)); + } + @Override public RouterFunctions.Builder resources(Function> lookupFunction) { return add(RouterFunctions.resources(lookupFunction)); } + @Override + public RouterFunctions.Builder resources(Function> lookupFunction, + BiConsumer headersConsumer) { + + return add(RouterFunctions.resources(lookupFunction, headersConsumer)); + } + @Override public RouterFunctions.Builder nest(RequestPredicate predicate, Consumer builderConsumer) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java index 95a59373f4b..a20154203f0 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * 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. @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -30,6 +31,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; import org.springframework.web.util.pattern.PathPatternParser; @@ -139,7 +141,26 @@ public abstract class RouterFunctions { * @see #resourceLookupFunction(String, Resource) */ public static RouterFunction resources(String pattern, Resource location) { - return resources(resourceLookupFunction(pattern, location)); + return resources(resourceLookupFunction(pattern, location), (resource, httpHeaders) -> {}); + } + + /** + * Route requests that match the given pattern to resources relative to the given root location. + * For instance + *
+	 * Resource location = new FileSystemResource("public-resources/");
+	 * RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
+     * 
+ * @param pattern the pattern to match + * @param location the location directory relative to which resources should be resolved + * @param headersConsumer provides access to the HTTP headers for served resources + * @return a router function that routes to resources + * @since 6.1 + * @see #resourceLookupFunction(String, Resource) + */ + public static RouterFunction resources(String pattern, Resource location, + BiConsumer headersConsumer) { + return resources(resourceLookupFunction(pattern, location), headersConsumer); } /** @@ -169,9 +190,23 @@ public abstract class RouterFunctions { * @return a router function that routes to resources */ public static RouterFunction resources(Function> lookupFunction) { - return new ResourcesRouterFunction(lookupFunction); + return new ResourcesRouterFunction(lookupFunction, (resource, httpHeaders) -> {}); + } + + /** + * Route to resources using the provided lookup function. If the lookup function provides a + * {@link Resource} for the given request, it will be it will be exposed using a + * {@link HandlerFunction} that handles GET, HEAD, and OPTIONS requests. + * @param lookupFunction the function to provide a {@link Resource} given the {@link ServerRequest} + * @param headersConsumer provides access to the HTTP headers for served resources + * @return a router function that routes to resources + * @since 6.1 + */ + public static RouterFunction resources(Function> lookupFunction, BiConsumer headersConsumer) { + return new ResourcesRouterFunction(lookupFunction, headersConsumer); } + /** * Changes the {@link PathPatternParser} on the given {@linkplain RouterFunction router function}. This method * can be used to change the {@code PathPatternParser} properties from the defaults, for instance to change @@ -563,6 +598,21 @@ public abstract class RouterFunctions { */ Builder resources(String pattern, Resource location); + /** + * Route requests that match the given pattern to resources relative to the given root location. + * For instance + *
+		 * Resource location = new FileSystemResource("public-resources/");
+		 * RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
+	     * 
+ * @param pattern the pattern to match + * @param location the location directory relative to which resources should be resolved + * @param headersConsumer provides access to the HTTP headers for served resources + * @return this builder + * @since 6.1 + */ + Builder resources(String pattern, Resource location, BiConsumer headersConsumer); + /** * Route to resources using the provided lookup function. If the lookup function provides a * {@link Resource} for the given request, it will be it will be exposed using a @@ -572,6 +622,17 @@ public abstract class RouterFunctions { */ Builder resources(Function> lookupFunction); + /** + * Route to resources using the provided lookup function. If the lookup function provides a + * {@link Resource} for the given request, it will be it will be exposed using a + * {@link HandlerFunction} that handles GET, HEAD, and OPTIONS requests. + * @param lookupFunction the function to provide a {@link Resource} given the {@link ServerRequest} + * @param headersConsumer provides access to the HTTP headers for served resources + * @return this builder + * @since 6.1 + */ + Builder resources(Function> lookupFunction, BiConsumer headersConsumer); + /** * Route to the supplied router function if the given request predicate applies. This method * can be used to create nested routes, where a group of routes share a @@ -1059,14 +1120,20 @@ public abstract class RouterFunctions { private final Function> lookupFunction; - public ResourcesRouterFunction(Function> lookupFunction) { + private final BiConsumer headersConsumer; + + + public ResourcesRouterFunction(Function> lookupFunction, + BiConsumer headersConsumer) { Assert.notNull(lookupFunction, "Function must not be null"); + Assert.notNull(headersConsumer, "HeadersConsumer must not be null"); this.lookupFunction = lookupFunction; + this.headersConsumer = headersConsumer; } @Override public Optional> route(ServerRequest request) { - return this.lookupFunction.apply(request).map(ResourceHandlerFunction::new); + return this.lookupFunction.apply(request).map(resource -> new ResourceHandlerFunction(resource, this.headersConsumer)); } @Override diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java index 96c76e6530d..a132b943acc 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -50,7 +50,7 @@ public class ResourceHandlerFunctionTests { private final Resource resource = new ClassPathResource("response.txt", getClass()); - private final ResourceHandlerFunction handlerFunction = new ResourceHandlerFunction(this.resource); + private final ResourceHandlerFunction handlerFunction = new ResourceHandlerFunction(this.resource, (r, h) -> {}); private ServerResponse.Context context; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/RouterFunctionBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/RouterFunctionBuilderTests.java index c9a497a2a4d..7ad6ac8082d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/RouterFunctionBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/RouterFunctionBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -17,6 +17,7 @@ package org.springframework.web.servlet.function; import java.io.IOException; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Optional; @@ -27,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.http.CacheControl; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; @@ -118,6 +120,23 @@ class RouterFunctionBuilderTests { assertThat(responseStatus).isEmpty(); } + @Test + public void resourcesCaching() { + Resource resource = new ClassPathResource("/org/springframework/web/servlet/function/"); + assertThat(resource.exists()).isTrue(); + + RouterFunction route = RouterFunctions.route() + .resources("/resources/**", resource, (r, headers) -> headers.setCacheControl(CacheControl.maxAge(Duration.ofSeconds(60)))) + .build(); + + ServerRequest resourceRequest = initRequest("GET", "/resources/response.txt"); + + Optional responseCacheControl = route.route(resourceRequest) + .map(handlerFunction -> handle(handlerFunction, resourceRequest)) + .map(response -> response.headers().getCacheControl()); + assertThat(responseCacheControl).contains("max-age=60"); + } + @Test void nest() { RouterFunction route = RouterFunctions.route()