Skip to content

Commit

Permalink
Updates config suppor for HandlerFunctions to accept RouteProperties
Browse files Browse the repository at this point in the history
Fixes gh-3188
  • Loading branch information
spencergibb committed Mar 11, 2024
1 parent 82a6955 commit 7f1048a
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,18 @@ private RouterFunction getRouterFunction(RouteProperties routeProperties, String
// 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());
Expand All @@ -215,38 +217,40 @@ else if (response instanceof HandlerDiscoverer.Result result) {
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);
predicate.set(null);

// 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);

return builder.build();
}

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()) {
Expand All @@ -263,14 +267,14 @@ private <T> void translate(MultiValueMap<String, OperationMethod> operations, St
}

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
Expand All @@ -288,7 +292,7 @@ private static boolean matchOperation(NormalizedOperationMethod operationMethod,
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -61,31 +61,36 @@ public OperationParameters getParameters() {
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");
Expand All @@ -110,11 +115,11 @@ private String[] getFieldOrder(Shortcut shortcut) {
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand All @@ -33,6 +34,10 @@ public Collection<Method> get() {
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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand All @@ -36,6 +37,10 @@ private 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) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 7f1048a

Please sign in to comment.