Browse Source

Polishing external contribution

See gh-29985
pull/31413/head
Arjen Poutsma 1 year ago
parent
commit
307a2c7d7b
  1. 9
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java
  2. 33
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultResourceCacheLookupStrategy.java
  3. 14
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java
  4. 36
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceCacheLookupStrategy.java
  5. 20
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java
  6. 16
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java
  7. 85
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java
  8. 4
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java
  9. 4
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java
  10. 17
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java
  11. 18
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java
  12. 77
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java
  13. 4
      spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java
  14. 21
      spring-webmvc/src/test/java/org/springframework/web/servlet/function/RouterFunctionBuilderTests.java

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

@ -1,5 +1,5 @@ @@ -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<T> implements EntityResponse.Builder<T> { @@ -115,6 +115,13 @@ class DefaultEntityResponseBuilder<T> implements EntityResponse.Builder<T> {
return this;
}
@Override
public EntityResponse.Builder<T> headers(Consumer<HttpHeaders> headersConsumer) {
Assert.notNull(headersConsumer, "HeadersConsumer must not be null");
headersConsumer.accept(this.headers);
return this;
}
@Override
public EntityResponse.Builder<T> allow(HttpMethod... allowedMethods) {
this.headers.setAllow(new LinkedHashSet<>(Arrays.asList(allowedMethods)));

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

@ -1,33 +0,0 @@ @@ -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();
}
}

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

@ -1,5 +1,5 @@ @@ -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<T> extends ServerResponse { @@ -149,6 +149,18 @@ public interface EntityResponse<T> extends ServerResponse {
*/
Builder<T> 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<T> headers(Consumer<HttpHeaders> headersConsumer);
/**
* Set the HTTP status.
* @param status the response status

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

@ -1,36 +0,0 @@ @@ -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);
}

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

@ -1,5 +1,5 @@ @@ -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; @@ -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<ServerResponse> { @@ -46,18 +47,13 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
private final Resource resource;
private final CacheControl cacheControl;
public ResourceHandlerFunction(Resource resource) {
this.resource = resource;
this.cacheControl = CacheControl.empty();
}
private final BiConsumer<Resource, HttpHeaders> headersConsumer;
public ResourceHandlerFunction(Resource resource, ResourceCacheLookupStrategy strategy) {
public ResourceHandlerFunction(Resource resource, BiConsumer<Resource, HttpHeaders> headersConsumer) {
this.resource = resource;
this.cacheControl = strategy.lookupCacheControl(resource);
this.headersConsumer = headersConsumer;
}
@ -66,14 +62,14 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> { @@ -66,14 +62,14 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
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);
}

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

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 { @@ -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<Resource, HttpHeaders> headersConsumer) {
return add(RouterFunctions.resources(pattern, location, headersConsumer));
}
@Override
@ -252,8 +256,10 @@ class RouterFunctionBuilder implements RouterFunctions.Builder { @@ -252,8 +256,10 @@ class RouterFunctionBuilder implements RouterFunctions.Builder {
}
@Override
public RouterFunctions.Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy resourceCacheLookupStrategy) {
return add(RouterFunctions.resources(lookupFunction,resourceCacheLookupStrategy));
public RouterFunctions.Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
return add(RouterFunctions.resources(lookupFunction, headersConsumer));
}
@Override

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

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 { @@ -156,10 +158,26 @@ public abstract class RouterFunctions {
* @see #resourceLookupFunction(String, Resource)
*/
public static RouterFunction<ServerResponse> resources(String pattern, Resource location) {
return resources(resourceLookupFunction(pattern, location), ResourceCacheLookupStrategy.noCaching());
return resources(resourceLookupFunction(pattern, location), (resource, httpHeaders) -> {});
}
public static RouterFunction<ServerResponse> 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
* <pre class="code">
* Resource location = new FileSystemResource("public-resources/");
* RouterFunction&lt;ServerResponse&gt; resources = RouterFunctions.resources("/resources/**", location);
* </pre>
* @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<ServerResponse> resources(String pattern, Resource location,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
return resources(resourceLookupFunction(pattern, location), headersConsumer);
}
/**
@ -189,10 +207,21 @@ public abstract class RouterFunctions { @@ -189,10 +207,21 @@ 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, ResourceCacheLookupStrategy.noCaching());
return new ResourcesRouterFunction(lookupFunction, (resource, httpHeaders) -> {});
}
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> 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<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
return new ResourcesRouterFunction(lookupFunction, headersConsumer);
}
/**
@ -658,7 +687,21 @@ public abstract class RouterFunctions { @@ -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
* <pre class="code">
* Resource location = new FileSystemResource("public-resources/");
* RouterFunction&lt;ServerResponse&gt; resources = RouterFunctions.resources("/resources/**", location);
* </pre>
* @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<Resource, HttpHeaders> headersConsumer);
/**
* Route to resources using the provided lookup function. If the lookup function provides a
@ -668,7 +711,17 @@ public abstract class RouterFunctions { @@ -668,7 +711,17 @@ 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 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<ServerRequest, Mono<Resource>> lookupFunction, BiConsumer<Resource, HttpHeaders> headersConsumer);
/**
* Route to the supplied router function if the given request predicate applies. This method
@ -1150,22 +1203,20 @@ public abstract class RouterFunctions { @@ -1150,22 +1203,20 @@ public abstract class RouterFunctions {
private final Function<ServerRequest, Mono<Resource>> lookupFunction;
private final ResourceCacheLookupStrategy lookupStrategy;
private final BiConsumer<Resource, HttpHeaders> headersConsumer;
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction) {
this(lookupFunction, ResourceCacheLookupStrategy.noCaching());
}
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction, ResourceCacheLookupStrategy lookupStrategy) {
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction,
BiConsumer<Resource, HttpHeaders> 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<HandlerFunction<ServerResponse>> 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

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

@ -1,5 +1,5 @@ @@ -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 { @@ -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;

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

@ -1,5 +1,5 @@ @@ -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 { @@ -139,7 +139,7 @@ public class RouterFunctionBuilderTests {
assertThat(resource.exists()).isTrue();
RouterFunction<ServerResponse> 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();

17
spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java

@ -1,5 +1,5 @@ @@ -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; @@ -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<ServerResponse> { @@ -43,9 +45,12 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
private final Resource resource;
private final BiConsumer<Resource, HttpHeaders> headersConsumer;
public ResourceHandlerFunction(Resource resource) {
public ResourceHandlerFunction(Resource resource, BiConsumer<Resource, HttpHeaders> headersConsumer) {
this.resource = resource;
this.headersConsumer = headersConsumer;
}
@ -53,11 +58,15 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> { @@ -53,11 +58,15 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
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()

18
spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 { @@ -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<Resource, HttpHeaders> headersConsumer) {
return add(RouterFunctions.resources(pattern, location, headersConsumer));
}
@Override
public RouterFunctions.Builder resources(Function<ServerRequest, Optional<Resource>> lookupFunction) {
return add(RouterFunctions.resources(lookupFunction));
}
@Override
public RouterFunctions.Builder resources(Function<ServerRequest, Optional<Resource>> lookupFunction,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
return add(RouterFunctions.resources(lookupFunction, headersConsumer));
}
@Override
public RouterFunctions.Builder nest(RequestPredicate predicate,
Consumer<RouterFunctions.Builder> builderConsumer) {

77
spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 { @@ -139,7 +141,26 @@ 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), (resource, httpHeaders) -> {});
}
/**
* Route requests that match the given pattern to resources relative to the given root location.
* For instance
* <pre class="code">
* Resource location = new FileSystemResource("public-resources/");
* RouterFunction&lt;ServerResponse&gt; resources = RouterFunctions.resources("/resources/**", location);
* </pre>
* @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<ServerResponse> resources(String pattern, Resource location,
BiConsumer<Resource, HttpHeaders> headersConsumer) {
return resources(resourceLookupFunction(pattern, location), headersConsumer);
}
/**
@ -169,9 +190,23 @@ public abstract class RouterFunctions { @@ -169,9 +190,23 @@ public abstract class RouterFunctions {
* @return a router function that routes to resources
*/
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Optional<Resource>> 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<ServerResponse> resources(Function<ServerRequest, Optional<Resource>> lookupFunction, BiConsumer<Resource, HttpHeaders> 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 { @@ -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
* <pre class="code">
* Resource location = new FileSystemResource("public-resources/");
* RouterFunction&lt;ServerResponse&gt; resources = RouterFunctions.resources("/resources/**", location);
* </pre>
* @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<Resource, HttpHeaders> 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 { @@ -572,6 +622,17 @@ public abstract class RouterFunctions {
*/
Builder resources(Function<ServerRequest, Optional<Resource>> 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<ServerRequest, Optional<Resource>> lookupFunction, BiConsumer<Resource, HttpHeaders> headersConsumer);
/**
* Route to the supplied router function if the given request predicate applies. This method
* can be used to create <strong>nested routes</strong>, where a group of routes share a
@ -1059,14 +1120,20 @@ public abstract class RouterFunctions { @@ -1059,14 +1120,20 @@ public abstract class RouterFunctions {
private final Function<ServerRequest, Optional<Resource>> lookupFunction;
public ResourcesRouterFunction(Function<ServerRequest, Optional<Resource>> lookupFunction) {
private final BiConsumer<Resource, HttpHeaders> headersConsumer;
public ResourcesRouterFunction(Function<ServerRequest, Optional<Resource>> lookupFunction,
BiConsumer<Resource, HttpHeaders> 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<HandlerFunction<ServerResponse>> route(ServerRequest request) {
return this.lookupFunction.apply(request).map(ResourceHandlerFunction::new);
return this.lookupFunction.apply(request).map(resource -> new ResourceHandlerFunction(resource, this.headersConsumer));
}
@Override

4
spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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;

21
spring-webmvc/src/test/java/org/springframework/web/servlet/function/RouterFunctionBuilderTests.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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 { @@ -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<ServerResponse> route = RouterFunctions.route()
.resources("/resources/**", resource, (r, headers) -> headers.setCacheControl(CacheControl.maxAge(Duration.ofSeconds(60))))
.build();
ServerRequest resourceRequest = initRequest("GET", "/resources/response.txt");
Optional<String> responseCacheControl = route.route(resourceRequest)
.map(handlerFunction -> handle(handlerFunction, resourceRequest))
.map(response -> response.headers().getCacheControl());
assertThat(responseCacheControl).contains("max-age=60");
}
@Test
void nest() {
RouterFunction<ServerResponse> route = RouterFunctions.route()

Loading…
Cancel
Save