Updates config suppor for HandlerFunctions to accept RouteProperties

Fixes gh-3188
This commit is contained in:
sgibb
2024-03-11 18:30:49 -04:00
parent 82a6955705
commit 7f1048a0df
5 changed files with 59 additions and 40 deletions

View File

@@ -185,16 +185,18 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrar implements ImportBeanDe
// TODO: cache?
// translate handlerFunction
String scheme = routeProperties.getUri().getScheme();
Map<String, String> handlerArgs = new HashMap<>();
// TODO: avoid hardcoded scheme/uri args
// maybe find empty args or single RouteProperties param?
if (scheme.equals("lb")) {
handlerArgs.put("uri", routeProperties.getUri().toString());
}
Map<String, Object> handlerArgs = new HashMap<>();
Optional<NormalizedOperationMethod> handlerOperationMethod = findOperation(handlerOperations,
scheme.toLowerCase(), handlerArgs);
if (handlerOperationMethod.isEmpty()) {
throw new IllegalStateException("Unable to find HandlerFunction for scheme: " + scheme);
// single RouteProperties param
handlerArgs.clear();
String routePropsKey = StringUtils.uncapitalize(RouteProperties.class.getSimpleName());
handlerArgs.put(routePropsKey, routeProperties);
handlerOperationMethod = findOperation(handlerOperations, scheme.toLowerCase(), handlerArgs);
if (handlerOperationMethod.isEmpty()) {
throw new IllegalStateException("Unable to find HandlerFunction for scheme: " + scheme);
}
}
NormalizedOperationMethod normalizedOpMethod = handlerOperationMethod.get();
Object response = invokeOperation(normalizedOpMethod, normalizedOpMethod.getNormalizedArgs());
@@ -215,21 +217,21 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrar implements ImportBeanDe
MultiValueMap<String, OperationMethod> predicateOperations = predicateDiscoverer.getOperations();
final AtomicReference<RequestPredicate> predicate = new AtomicReference<>();
routeProperties.getPredicates()
.forEach(predicateProperties -> translate(predicateOperations, predicateProperties.getName(),
predicateProperties.getArgs(), RequestPredicate.class, requestPredicate -> {
log.trace(LogMessage.format("Adding predicate to route %s - %s", routeId,
predicateProperties));
if (predicate.get() == null) {
predicate.set(requestPredicate);
}
else {
RequestPredicate combined = predicate.get().and(requestPredicate);
predicate.set(combined);
}
log.trace(LogMessage.format("Combined predicate for route %s - %s", routeId,
predicate.get()));
}));
routeProperties.getPredicates().forEach(predicateProperties -> {
Map<String, Object> args = new LinkedHashMap<>(predicateProperties.getArgs());
translate(predicateOperations, predicateProperties.getName(), args, RequestPredicate.class,
requestPredicate -> {
log.trace(LogMessage.format("Adding predicate to route %s - %s", routeId, predicateProperties));
if (predicate.get() == null) {
predicate.set(requestPredicate);
}
else {
RequestPredicate combined = predicate.get().and(requestPredicate);
predicate.set(combined);
}
log.trace(LogMessage.format("Combined predicate for route %s - %s", routeId, predicate.get()));
});
});
// combine predicate and handlerFunction
builder.route(predicate.get(), handlerFunction);
@@ -237,8 +239,10 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrar implements ImportBeanDe
// translate filters
MultiValueMap<String, OperationMethod> filterOperations = filterDiscoverer.getOperations();
routeProperties.getFilters().forEach(filterProperties -> translate(filterOperations, filterProperties.getName(),
filterProperties.getArgs(), HandlerFilterFunction.class, builder::filter));
routeProperties.getFilters().forEach(filterProperties -> {
Map<String, Object> args = new LinkedHashMap<>(filterProperties.getArgs());
translate(filterOperations, filterProperties.getName(), args, HandlerFilterFunction.class, builder::filter);
});
builder.withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, routeId);
@@ -246,7 +250,7 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrar implements ImportBeanDe
}
private <T> void translate(MultiValueMap<String, OperationMethod> operations, String operationName,
Map<String, String> operationArgs, Class<T> returnType, Consumer<T> operationHandler) {
Map<String, Object> operationArgs, Class<T> returnType, Consumer<T> operationHandler) {
String normalizedName = StringUtils.uncapitalize(operationName);
Optional<NormalizedOperationMethod> operationMethod = findOperation(operations, normalizedName, operationArgs);
if (operationMethod.isPresent()) {
@@ -263,14 +267,14 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrar implements ImportBeanDe
}
private Optional<NormalizedOperationMethod> findOperation(MultiValueMap<String, OperationMethod> operations,
String operationName, Map<String, String> operationArgs) {
String operationName, Map<String, Object> operationArgs) {
return operations.getOrDefault(operationName, Collections.emptyList()).stream()
.map(operationMethod -> new NormalizedOperationMethod(operationMethod, operationArgs))
.filter(opeMethod -> matchOperation(opeMethod, operationArgs)).findFirst();
}
private static boolean matchOperation(NormalizedOperationMethod operationMethod, Map<String, String> args) {
Map<String, String> normalizedArgs = operationMethod.getNormalizedArgs();
private static boolean matchOperation(NormalizedOperationMethod operationMethod, Map<String, Object> args) {
Map<String, Object> normalizedArgs = operationMethod.getNormalizedArgs();
OperationParameters parameters = operationMethod.getParameters();
if (operationMethod.isConfigurable()) {
// this is a special case
@@ -288,7 +292,7 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrar implements ImportBeanDe
return true;
}
private <T> T invokeOperation(OperationMethod operationMethod, Map<String, String> operationArgs) {
private <T> T invokeOperation(OperationMethod operationMethod, Map<String, Object> operationArgs) {
Map<String, Object> args = new HashMap<>();
if (operationMethod.isConfigurable()) {
OperationParameter operationParameter = operationMethod.getParameters().get(0);

View File

@@ -35,13 +35,13 @@ public class NormalizedOperationMethod implements OperationMethod {
private final OperationMethod delegate;
private final Map<String, String> normalizedArgs;
private final Map<String, Object> normalizedArgs;
/**
* Create a new {@link DefaultOperationMethod} instance.
* @param method the source method
*/
public NormalizedOperationMethod(OperationMethod delegate, Map<String, String> args) {
public NormalizedOperationMethod(OperationMethod delegate, Map<String, Object> args) {
this.delegate = delegate;
normalizedArgs = normalizeArgs(args);
}
@@ -61,31 +61,36 @@ public class NormalizedOperationMethod implements OperationMethod {
return delegate.getParameters();
}
public Map<String, String> getNormalizedArgs() {
public Map<String, Object> getNormalizedArgs() {
return normalizedArgs;
}
private Map<String, String> normalizeArgs(Map<String, String> operationArgs) {
@Override
public String toString() {
return delegate.toString();
}
private Map<String, Object> normalizeArgs(Map<String, Object> operationArgs) {
if (hasGeneratedKey(operationArgs)) {
Shortcut shortcut = getMethod().getAnnotation(Shortcut.class);
if (shortcut != null) {
String[] fieldOrder = getFieldOrder(shortcut);
return switch (shortcut.type()) {
case DEFAULT -> {
Map<String, String> map = new HashMap<>();
Map<String, Object> map = new HashMap<>();
int entryIdx = 0;
for (Map.Entry<String, String> entry : operationArgs.entrySet()) {
for (Map.Entry<String, Object> entry : operationArgs.entrySet()) {
String key = normalizeKey(entry.getKey(), entryIdx, operationArgs, fieldOrder);
// TODO: support spel?
// getValue(parser, beanFactory, entry.getValue());
String value = entry.getValue();
Object value = entry.getValue();
map.put(key, value);
entryIdx++;
}
yield map;
}
case LIST -> {
Map<String, String> map = new HashMap<>();
Map<String, Object> map = new HashMap<>();
// field order should be of size 1
Assert.isTrue(fieldOrder != null && fieldOrder.length == 1,
"Shortcut Configuration Type GATHER_LIST must have shortcutFieldOrder of size 1");
@@ -110,11 +115,11 @@ public class NormalizedOperationMethod implements OperationMethod {
return fieldOrder;
}
private static boolean hasGeneratedKey(Map<String, String> operationArgs) {
private static boolean hasGeneratedKey(Map<String, Object> operationArgs) {
return operationArgs.keySet().stream().anyMatch(key -> key.startsWith(NameUtils.GENERATED_NAME_PREFIX));
}
static String normalizeKey(String key, int entryIdx, Map<String, String> args, String[] fieldOrder) {
static String normalizeKey(String key, int entryIdx, Map<String, Object> args, String[] fieldOrder) {
// RoutePredicateFactory has name hints and this has a fake key name
// replace with the matching key hint
if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX) && fieldOrder.length > 0 && entryIdx < args.size()

View File

@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerDiscoverer;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier;
@@ -33,6 +34,10 @@ public class LoadBalancerHandlerSupplier implements HandlerSupplier {
return Arrays.asList(getClass().getMethods());
}
public static HandlerDiscoverer.Result lb(RouteProperties routeProperties) {
return lb(routeProperties.getUri());
}
public static HandlerDiscoverer.Result lb(URI uri) {
// TODO: how to do something other than http
return new HandlerDiscoverer.Result(HandlerFunctions.http(),

View File

@@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicReference;
import jakarta.servlet.ServletException;
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
import org.springframework.web.servlet.function.HandlerFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
@@ -36,6 +37,10 @@ public abstract class HandlerFunctions {
}
public static HandlerFunction<ServerResponse> forward(RouteProperties routeProperties) {
return forward(routeProperties.getUri().getPath());
}
public static HandlerFunction<ServerResponse> forward(String path) {
// ok() is wrong, but can be overridden by the forwarded request.
return request -> GatewayServerResponse.ok().build((httpServletRequest, httpServletResponse) -> {

View File

@@ -45,7 +45,7 @@ spring.cloud.gateway.mvc:
name: X-Test
values: listRoute3
- id: listRoute4
uri: https://example1.com
uri: forward:/mycontroller
predicates:
- Path=/anything/example1
filters: