Query param route predicate - extension of QueryRoutePredicateFactory (#3472)
* Creation of QueryParamRoutePredicateFactory A predicate that checks if a query parameter value matches criteria of a given predicate. Signed-off-by: Francesco Poli <polifr@gmail.com> * Fix predicate method Signed-off-by: Francesco Poli <polifr@gmail.com> * Factory fixes and junit test coverage Signed-off-by: Francesco Poli <polifr@gmail.com> * Fix on predicate check for tests Signed-off-by: Francesco Poli <polifr@gmail.com> * Regexp management via predicate and configuration extension Signed-off-by: Francesco Poli <polifr@gmail.com> * Validation enforcing - tryout Signed-off-by: Francesco Poli <polifr@gmail.com> * Checkstyle formatting fix Signed-off-by: Francesco Poli <polifr@gmail.com> * Deletion of QueryParamRoutePredicateFactory class and test Signed-off-by: Francesco Poli <polifr@gmail.com> * Update in QueryRoutePredicateFactory creation with Predicate Signed-off-by: Francesco Poli <polifr@gmail.com> * Unit test update Signed-off-by: Francesco Poli <polifr@gmail.com> * Update QueryRoutePredicateFactoryPredicateTests.java Fix copyright header comment Signed-off-by: Francesco Poli <polifr@gmail.com> --------- Signed-off-by: Francesco Poli <polifr@gmail.com>
This commit is contained in:
@@ -210,6 +210,7 @@ public class GatewayAutoConfiguration {
|
||||
* @deprecated in favour of
|
||||
* {@link org.springframework.cloud.gateway.support.config.KeyValueConverter}
|
||||
*/
|
||||
@Deprecated
|
||||
@Bean
|
||||
public org.springframework.cloud.gateway.support.KeyValueConverter deprecatedKeyValueConverter() {
|
||||
return new org.springframework.cloud.gateway.support.KeyValueConverter();
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -40,13 +41,18 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
|
||||
*/
|
||||
public static final String REGEXP_KEY = "regexp";
|
||||
|
||||
/**
|
||||
* Predicate key.
|
||||
*/
|
||||
public static final String PREDICATE_KEY = "predicate";
|
||||
|
||||
public QueryRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList(PARAM_KEY, REGEXP_KEY);
|
||||
return Arrays.asList(PARAM_KEY, REGEXP_KEY, PREDICATE_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,7 +60,7 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
|
||||
return new GatewayPredicate() {
|
||||
@Override
|
||||
public boolean test(ServerWebExchange exchange) {
|
||||
if (!StringUtils.hasText(config.regexp)) {
|
||||
if (!StringUtils.hasText(config.regexp) && config.predicate == null) {
|
||||
// check existence of header
|
||||
return exchange.getRequest().getQueryParams().containsKey(config.param);
|
||||
}
|
||||
@@ -63,8 +69,13 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
|
||||
if (values == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Predicate<String> predicate = config.predicate;
|
||||
if (StringUtils.hasText(config.regexp)) {
|
||||
predicate = value -> value.matches(config.regexp);
|
||||
}
|
||||
for (String value : values) {
|
||||
if (value != null && value.matches(config.regexp)) {
|
||||
if (value != null && predicate.test(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -90,8 +101,10 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
|
||||
|
||||
private String regexp;
|
||||
|
||||
private Predicate<String> predicate;
|
||||
|
||||
public String getParam() {
|
||||
return param;
|
||||
return this.param;
|
||||
}
|
||||
|
||||
public Config setParam(String param) {
|
||||
@@ -100,7 +113,7 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
|
||||
}
|
||||
|
||||
public String getRegexp() {
|
||||
return regexp;
|
||||
return this.regexp;
|
||||
}
|
||||
|
||||
public Config setRegexp(String regexp) {
|
||||
@@ -108,6 +121,26 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
|
||||
return this;
|
||||
}
|
||||
|
||||
public Predicate<String> getPredicate() {
|
||||
return this.predicate;
|
||||
}
|
||||
|
||||
public Config setPredicate(Predicate<String> predicate) {
|
||||
this.predicate = predicate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the validation done on predicate configuration: {@link #regexp} and
|
||||
* {@link #predicate} can't be both set at runtime.
|
||||
* @return <code>false</code> if {@link #regexp} and {@link #predicate} are both
|
||||
* set in this predicate factory configuration
|
||||
*/
|
||||
@AssertTrue
|
||||
public boolean isValid() {
|
||||
return !(StringUtils.hasText(this.regexp) && this.predicate != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -204,6 +204,18 @@ public class PredicateSpec extends UriSpec {
|
||||
getBean(ReadBodyRoutePredicateFactory.class).applyAsync(c -> c.setPredicate(inClass, predicate)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A predicate that checks if a query parameter value matches criteria of a given
|
||||
* predicate.
|
||||
* @param param the query parameter name
|
||||
* @param predicate a predicate to check the value of the param
|
||||
* @return a {@link BooleanSpec} to be used to add logical operators
|
||||
*/
|
||||
public BooleanSpec query(String param, Predicate<String> predicate) {
|
||||
return asyncPredicate(
|
||||
getBean(QueryRoutePredicateFactory.class).applyAsync(c -> c.setParam(param).setPredicate(predicate)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A predicate that checks if a query parameter matches a regular expression.
|
||||
* @param param the query parameter name
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2013-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.cloud.gateway.handler.predicate;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory.Config;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
import org.springframework.cloud.gateway.support.HasConfig;
|
||||
import org.springframework.cloud.gateway.test.BaseWebClientTests;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
/**
|
||||
* Test class for {@link QueryRoutePredicateFactory} for <code>predicate</code> parameter.
|
||||
*
|
||||
* @see QueryRoutePredicateFactory
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@DirtiesContext
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
public class QueryRoutePredicateFactoryPredicateTests extends BaseWebClientTests {
|
||||
|
||||
@Test
|
||||
public void noQueryParamWorks(CapturedOutput output) {
|
||||
this.testClient.get()
|
||||
.uri("/get")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectHeader()
|
||||
.valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin");
|
||||
assertThat(output).doesNotContain("Error applying predicate for route: foo_query_param");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamPredicateTrue() {
|
||||
this.testClient.get()
|
||||
.uri("/get?foo=1234567")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectHeader()
|
||||
.valueEquals(ROUTE_ID_HEADER, "foo_query_param");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamPredicateFalse(CapturedOutput output) {
|
||||
this.testClient.get()
|
||||
.uri("/get?foo=123")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectHeader()
|
||||
.valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin");
|
||||
assertThat(output).doesNotContain("Error applying predicate for route: foo_query_param");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyQueryParamWorks(CapturedOutput output) {
|
||||
this.testClient.get()
|
||||
.uri("/get?foo")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectHeader()
|
||||
.valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin");
|
||||
assertThat(output).doesNotContain("Error applying predicate for route: foo_query_param");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfig() {
|
||||
Config config = new Config();
|
||||
config.setParam("query_param");
|
||||
Predicate<ServerWebExchange> predicate = new QueryRoutePredicateFactory().apply(config);
|
||||
assertThat(predicate).isInstanceOf(HasConfig.class);
|
||||
assertThat(config).isSameAs(((HasConfig) predicate).getConfig());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringFormat() {
|
||||
Config config = new Config();
|
||||
config.setParam("query_param");
|
||||
Predicate<ServerWebExchange> predicate = new QueryRoutePredicateFactory().apply(config);
|
||||
assertThat(predicate.toString()).contains("Query: param=query_param");
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@Import(DefaultTestConfig.class)
|
||||
public static class TestConfig {
|
||||
|
||||
private static final int PARAM_LENGTH = 5;
|
||||
|
||||
@Value("${test.uri}")
|
||||
private String uri;
|
||||
|
||||
@Bean
|
||||
RouteLocator queryRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("foo_query_param",
|
||||
r -> r.query("foo", queryParamPredicate()).filters(f -> f.prefixPath("/httpbin")).uri(this.uri))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Predicate<String> queryParamPredicate() {
|
||||
return p -> p == null ? false : p.length() > PARAM_LENGTH;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,6 +43,11 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
/**
|
||||
* Test class for {@link QueryRoutePredicateFactory} for <code>regex</code> parameter.
|
||||
*
|
||||
* @see QueryRoutePredicateFactory
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@DirtiesContext
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
|
||||
@@ -46,6 +46,7 @@ import static org.junit.Assume.assumeThat;
|
||||
org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactoryTests.class,
|
||||
org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.class,
|
||||
org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactoryTests.class,
|
||||
org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactoryPredicateTests.class,
|
||||
org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactoryIntegrationTests.class,
|
||||
org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactoryTests.class,
|
||||
org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactoryTests.class,
|
||||
|
||||
Reference in New Issue
Block a user