Skip to content

Commit

Permalink
Makes DiscoveryClient locator integration configurable.
Browse files Browse the repository at this point in the history
fixes gh-185
  • Loading branch information
spencergibb committed Apr 24, 2018
1 parent 1a3de52 commit d894d31
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,20 @@
package org.springframework.cloud.gateway.discovery;

import java.net.URI;
import java.util.Map;

import reactor.core.publisher.Flux;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;

import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REGEXP_KEY;
import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REPLACEMENT_KEY;
import static org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory.PATTERN_KEY;
import static org.springframework.cloud.gateway.support.NameUtils.normalizeFilterFactoryName;
import static org.springframework.cloud.gateway.support.NameUtils.normalizeRoutePredicateName;

import reactor.core.publisher.Flux;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.util.StringUtils;

/**
* TODO: developer configuration, in zuul, this was opt out, should be opt in
Expand All @@ -43,47 +41,72 @@
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {

private final DiscoveryClient discoveryClient;
private final DiscoveryLocatorProperties properties;
private final String routeIdPrefix;

public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) {
public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
this.discoveryClient = discoveryClient;
this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_";
this.properties = properties;
if (StringUtils.hasText(properties.getRouteIdPrefix())) {
this.routeIdPrefix = properties.getRouteIdPrefix();
} else {
this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_";
}
}

@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(discoveryClient.getServices())
.map(serviceId -> {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
routeDefinition.setUri(URI.create("lb://" + serviceId));
SimpleEvaluationContext evalCtxt = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// add a predicate that matches the url at /serviceId
/*PredicateDefinition barePredicate = new PredicateDefinition();
barePredicate.setName(normalizePredicateName(PathRoutePredicate.class));
barePredicate.addArg(PATTERN_KEY, "/" + serviceId);
routeDefinition.getPredicates().add(barePredicate);*/
SpelExpressionParser parser = new SpelExpressionParser();
Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());
Expression urlExpr = parser.parseExpression(properties.getUrlExpression());

// add a predicate that matches the url at /serviceId/**
PredicateDefinition subPredicate = new PredicateDefinition();
subPredicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
subPredicate.addArg(PATTERN_KEY, "/" + serviceId + "/**");
routeDefinition.getPredicates().add(subPredicate);
return Flux.fromIterable(discoveryClient.getServices())
.map(discoveryClient::getInstances)
.filter(instances -> !instances.isEmpty())
.map(instances -> instances.get(0))
.filter(instance -> {
Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
if (include == null) {
return false;
}
return include;
})
.map(instance -> {
String serviceId = instance.getServiceId();

//TODO: support for other default predicates
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));

// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "/" + serviceId + "/(?<remaining>.*)";
String replacement = "/${remaining}";
filter.addArg(REGEXP_KEY, regex);
filter.addArg(REPLACEMENT_KEY, replacement);
routeDefinition.getFilters().add(filter);
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instance, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}

//TODO: support for default filters
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instance, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}

return routeDefinition;
return routeDefinition;
});
}

String getValueFromExpr(SimpleEvaluationContext evalCtxt, SpelExpressionParser parser, ServiceInstance instance, Map.Entry<String, String> entry) {
Expression valueExpr = parser.parseExpression(entry.getValue());
return valueExpr.getValue(evalCtxt, instance, String.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2013-2018 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
*
* http://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.discovery;


import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.core.style.ToStringCreator;

@ConfigurationProperties("spring.cloud.gateway.discovery.locator")
public class DiscoveryLocatorProperties {

/** Flag that enables DiscoveryClient gateway integration */
private boolean enabled = false;

/**
* The prefix for the routeId, defaults to discoveryClient.getClass().getSimpleName() + "_".
* Service Id will be appended to create the routeId.
*/
private String routeIdPrefix;

/**
* SpEL expression that will evaluate whether to include a service in gateway integration or not,
* defaults to: true
*/
private String includeExpression = "true";

/** SpEL expression that create the uri for each route, defaults to: 'lb://'+serviceId */
private String urlExpression = "'lb://'+serviceId";

private List<PredicateDefinition> predicates = new ArrayList<>();

private List<FilterDefinition> filters = new ArrayList<>();

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getRouteIdPrefix() {
return routeIdPrefix;
}

public void setRouteIdPrefix(String routeIdPrefix) {
this.routeIdPrefix = routeIdPrefix;
}

public String getIncludeExpression() {
return includeExpression;
}

public void setIncludeExpression(String includeExpression) {
this.includeExpression = includeExpression;
}

public String getUrlExpression() {
return urlExpression;
}

public void setUrlExpression(String urlExpression) {
this.urlExpression = urlExpression;
}

public List<PredicateDefinition> getPredicates() {
return predicates;
}

public void setPredicates(List<PredicateDefinition> predicates) {
this.predicates = predicates;
}

public List<FilterDefinition> getFilters() {
return filters;
}

public void setFilters(List<FilterDefinition> filters) {
this.filters = filters;
}

@Override
public String toString() {
return new ToStringCreator(this)
.append("enabled", enabled)
.append("routeIdPrefix", routeIdPrefix)
.append("includeExpression", includeExpression)
.append("urlExpression", urlExpression)
.append("predicates", predicates)
.append("filters", filters)
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,77 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;

import java.util.ArrayList;
import java.util.List;

import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REGEXP_KEY;
import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REPLACEMENT_KEY;
import static org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory.PATTERN_KEY;
import static org.springframework.cloud.gateway.support.NameUtils.normalizeFilterFactoryName;
import static org.springframework.cloud.gateway.support.NameUtils.normalizeRoutePredicateName;

/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@ConditionalOnClass({DispatcherHandler.class, DiscoveryClient.class})
@EnableConfigurationProperties
public class GatewayDiscoveryClientAutoConfiguration {

@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient);
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}

@Bean
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}

public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList<>();
// TODO: add a predicate that matches the url at /serviceId?

// add a predicate that matches the url at /serviceId/**
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'");
definitions.add(predicate);
return definitions;
}

public static List<FilterDefinition> initFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList<>();

// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg(REGEXP_KEY, regex);
filter.addArg(REPLACEMENT_KEY, replacement);
definitions.add(filter);

return definitions;
}

}
Expand Down
Loading

0 comments on commit d894d31

Please sign in to comment.