Add resource redirection to WebFlux functional router
See gh-27257
This commit is contained in:
parent
46108deff4
commit
2d7b2e59b6
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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 java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Lookup function used by {@link RouterFunctions#resource(RequestPredicate, Resource)} and
|
||||
* {@link RouterFunctions#resource(RequestPredicate, Resource, java.util.function.BiConsumer)}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.1.4
|
||||
*/
|
||||
class PredicateResourceLookupFunction implements Function<ServerRequest, Mono<Resource>> {
|
||||
|
||||
private final RequestPredicate predicate;
|
||||
|
||||
private final Resource resource;
|
||||
|
||||
public PredicateResourceLookupFunction(RequestPredicate predicate, Resource resource) {
|
||||
Assert.notNull(predicate, "'predicate' must not be null");
|
||||
Assert.notNull(resource, "'resource' must not be null");
|
||||
this.predicate = predicate;
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Resource> apply(ServerRequest serverRequest) {
|
||||
return this.predicate.test(serverRequest) ? Mono.just(this.resource) : Mono.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
|
|
@ -39,6 +39,7 @@ import org.springframework.util.Assert;
|
|||
* Default implementation of {@link RouterFunctions.Builder}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.1
|
||||
*/
|
||||
class RouterFunctionBuilder implements RouterFunctions.Builder {
|
||||
|
|
@ -238,6 +239,17 @@ class RouterFunctionBuilder implements RouterFunctions.Builder {
|
|||
return add(RouterFunctions.route(predicate, handlerFunction));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouterFunctions.Builder resource(RequestPredicate predicate, Resource resource) {
|
||||
return add(RouterFunctions.resource(predicate, resource));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouterFunctions.Builder resource(RequestPredicate predicate, Resource resource,
|
||||
BiConsumer<Resource, HttpHeaders> headersConsumer) {
|
||||
return add(RouterFunctions.resource(predicate, resource, headersConsumer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouterFunctions.Builder resources(String pattern, Resource location) {
|
||||
return add(RouterFunctions.resources(pattern, location));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
|
|
@ -59,6 +59,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
|
|||
* environments, Reactor, or Undertow.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
public abstract class RouterFunctions {
|
||||
|
|
@ -145,6 +146,40 @@ public abstract class RouterFunctions {
|
|||
return new DefaultNestedRouterFunction<>(predicate, routerFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requests that match the given predicate to the given resource.
|
||||
* For instance
|
||||
* <pre class="code">
|
||||
* Resource resource = new ClassPathResource("static/index.html")
|
||||
* RouterFunction<ServerResponse> resources = RouterFunctions.resource(path("/api/**").negate(), resource);
|
||||
* </pre>
|
||||
* @param predicate predicate to match
|
||||
* @param resource the resources to serve
|
||||
* @return a router function that routes to a resource
|
||||
* @since 6.1.4
|
||||
*/
|
||||
public static RouterFunction<ServerResponse> resource(RequestPredicate predicate, Resource resource) {
|
||||
return resources(new PredicateResourceLookupFunction(predicate, resource), (consumerResource, httpHeaders) -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requests that match the given predicate to the given resource.
|
||||
* For instance
|
||||
* <pre class="code">
|
||||
* Resource resource = new ClassPathResource("static/index.html")
|
||||
* RouterFunction<ServerResponse> resources = RouterFunctions.resource(path("/api/**").negate(), resource);
|
||||
* </pre>
|
||||
* @param predicate predicate to match
|
||||
* @param resource the resources to serve
|
||||
* @param headersConsumer provides access to the HTTP headers for served resources
|
||||
* @return a router function that routes to a resource
|
||||
* @since 6.1.4
|
||||
*/
|
||||
public static RouterFunction<ServerResponse> resource(RequestPredicate predicate, Resource resource,
|
||||
BiConsumer<Resource, HttpHeaders> headersConsumer) {
|
||||
return resources(new PredicateResourceLookupFunction(predicate, resource), headersConsumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requests that match the given pattern to resources relative to the given root location.
|
||||
* For instance
|
||||
|
|
@ -692,6 +727,35 @@ public abstract class RouterFunctions {
|
|||
*/
|
||||
Builder add(RouterFunction<ServerResponse> routerFunction);
|
||||
|
||||
/**
|
||||
* Route requests that match the given predicate to the given resource.
|
||||
* For instance
|
||||
* <pre class="code">
|
||||
* Resource resource = new ClassPathResource("static/index.html")
|
||||
* RouterFunction<ServerResponse> resources = RouterFunctions.resource(path("/api/**").negate(), resource);
|
||||
* </pre>
|
||||
* @param predicate predicate to match
|
||||
* @param resource the resources to serve
|
||||
* @return a router function that routes to a resource
|
||||
* @since 6.1.4
|
||||
*/
|
||||
Builder resource(RequestPredicate predicate, Resource resource);
|
||||
|
||||
/**
|
||||
* Route requests that match the given predicate to the given resource.
|
||||
* For instance
|
||||
* <pre class="code">
|
||||
* Resource resource = new ClassPathResource("static/index.html")
|
||||
* RouterFunction<ServerResponse> resources = RouterFunctions.resource(path("/api/**").negate(), resource);
|
||||
* </pre>
|
||||
* @param predicate predicate to match
|
||||
* @param resource the resources to serve
|
||||
* @param headersConsumer provides access to the HTTP headers for served resources
|
||||
* @return a router function that routes to a resource
|
||||
* @since 6.1.4
|
||||
*/
|
||||
Builder resource(RequestPredicate predicate, Resource resource, BiConsumer<Resource, HttpHeaders> headersConsumer);
|
||||
|
||||
/**
|
||||
* Route requests that match the given pattern to resources relative to the given root location.
|
||||
* For instance
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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,6 +23,7 @@ import kotlinx.coroutines.reactor.awaitSingle
|
|||
import kotlinx.coroutines.reactor.mono
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.http.HttpStatusCode
|
||||
import org.springframework.http.MediaType
|
||||
|
|
@ -499,6 +500,15 @@ class CoRouterFunctionDsl internal constructor (private val init: (CoRouterFunct
|
|||
builder.add(RouterFunctions.route(RequestPredicates.path(this), asHandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requests that match the given predicate to the given resource.
|
||||
* @since 6.1.4
|
||||
* @see RouterFunctions.resource
|
||||
*/
|
||||
fun resource(predicate: RequestPredicate, resource: Resource, headersConsumer: (Resource, HttpHeaders) -> Unit = { _, _ -> }) {
|
||||
builder.resource(predicate, resource, headersConsumer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requests that match the given pattern to resources relative to the given root location.
|
||||
* @see RouterFunctions.resources
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
|
|
@ -617,6 +617,15 @@ class RouterFunctionDsl internal constructor (private val init: RouterFunctionDs
|
|||
builder.add(RouterFunctions.route(RequestPredicates.path(this), HandlerFunction { f(it).cast(ServerResponse::class.java) }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requests that match the given predicate to the given resource.
|
||||
* @since 6.1.4
|
||||
* @see RouterFunctions.resource
|
||||
*/
|
||||
fun resource(predicate: RequestPredicate, resource: Resource) {
|
||||
builder.resource(predicate, resource)
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requests that match the given pattern to resources relative to the given root location.
|
||||
* @see RouterFunctions.resources
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import org.springframework.web.testfixture.server.MockServerWebExchange;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.HEAD;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
|
|
@ -102,6 +103,27 @@ class RouterFunctionBuilderTests {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
void resource() {
|
||||
Resource resource = new ClassPathResource("/org/springframework/web/reactive/function/server/response.txt");
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route()
|
||||
.resource(path("/test"), resource)
|
||||
.build();
|
||||
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest.get("https://localhost/test").build();
|
||||
ServerRequest resourceRequest = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<HttpStatusCode> responseMono = route.route(resourceRequest)
|
||||
.flatMap(handlerFunction -> handlerFunction.handle(resourceRequest))
|
||||
.map(ServerResponse::statusCode);
|
||||
|
||||
StepVerifier.create(responseMono)
|
||||
.expectNext(HttpStatus.OK)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resources() {
|
||||
Resource resource = new ClassPathResource("/org/springframework/web/reactive/function/server/");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
|
|
@ -93,16 +93,6 @@ class CoRouterFunctionDslTests {
|
|||
.verifyComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resourceByPath() {
|
||||
val mockRequest = get("https://example.com/org/springframework/web/reactive/function/response.txt")
|
||||
.build()
|
||||
val request = DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList())
|
||||
StepVerifier.create(sampleRouter().route(request))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun method() {
|
||||
val mockRequest = patch("https://example.com/")
|
||||
|
|
@ -124,6 +114,24 @@ class CoRouterFunctionDslTests {
|
|||
|
||||
@Test
|
||||
fun resource() {
|
||||
val mockRequest = get("https://example.com/response2.txt").build()
|
||||
val request = DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList())
|
||||
StepVerifier.create(sampleRouter().route(request))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resources() {
|
||||
val mockRequest = get("https://example.com/resources/response.txt").build()
|
||||
val request = DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList())
|
||||
StepVerifier.create(sampleRouter().route(request))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resourcesLookupFunction() {
|
||||
val mockRequest = get("https://example.com/response.txt").build()
|
||||
val request = DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList())
|
||||
StepVerifier.create(sampleRouter().route(request))
|
||||
|
|
@ -305,8 +313,9 @@ class CoRouterFunctionDslTests {
|
|||
GET("/api/foo/", ::handle)
|
||||
}
|
||||
headers({ it.header("bar").isNotEmpty() }, ::handle)
|
||||
resources("/org/springframework/web/reactive/function/**",
|
||||
ClassPathResource("/org/springframework/web/reactive/function/response.txt"))
|
||||
resource(path("/response2.txt"), ClassPathResource("/org/springframework/web/reactive/function/response.txt"))
|
||||
resources("/resources/**",
|
||||
ClassPathResource("/org/springframework/web/reactive/function/"))
|
||||
resources {
|
||||
if (it.path() == "/response.txt") {
|
||||
ClassPathResource("/org/springframework/web/reactive/function/response.txt")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
|
|
@ -91,16 +91,6 @@ class RouterFunctionDslTests {
|
|||
.verifyComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resourceByPath() {
|
||||
val mockRequest = get("https://example.com/org/springframework/web/reactive/function/response.txt")
|
||||
.build()
|
||||
val request = DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList())
|
||||
StepVerifier.create(sampleRouter().route(request))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun method() {
|
||||
val mockRequest = patch("https://example.com/")
|
||||
|
|
@ -122,6 +112,24 @@ class RouterFunctionDslTests {
|
|||
|
||||
@Test
|
||||
fun resource() {
|
||||
val mockRequest = get("https://example.com/response2.txt").build()
|
||||
val request = DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList())
|
||||
StepVerifier.create(sampleRouter().route(request))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resources() {
|
||||
val mockRequest = get("https://example.com/resources/response.txt").build()
|
||||
val request = DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList())
|
||||
StepVerifier.create(sampleRouter().route(request))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resourcesLookupFunction() {
|
||||
val mockRequest = get("https://example.com/response.txt").build()
|
||||
val request = DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList())
|
||||
StepVerifier.create(sampleRouter().route(request))
|
||||
|
|
@ -237,8 +245,9 @@ class RouterFunctionDslTests {
|
|||
GET("/api/foo/", ::handle)
|
||||
}
|
||||
headers({ it.header("bar").isNotEmpty() }, ::handle)
|
||||
resources("/org/springframework/web/reactive/function/**",
|
||||
ClassPathResource("/org/springframework/web/reactive/function/response.txt"))
|
||||
resource(path("/response2.txt"), ClassPathResource("/org/springframework/web/reactive/function/response.txt"))
|
||||
resources("/resources/**",
|
||||
ClassPathResource("/org/springframework/web/reactive/function/"))
|
||||
resources {
|
||||
if (it.path() == "/response.txt") {
|
||||
Mono.just(ClassPathResource("/org/springframework/web/reactive/function/response.txt"))
|
||||
|
|
|
|||
Loading…
Reference in New Issue