/*
* Copyright 2013-2015 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.netflix.zuul.filters.discovery;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
import lombok.extern.apachecommons.CommonsLog;
/**
* A {@link RouteLocator} that combines static, configured routes with those from a
* {@link DiscoveryClient}. The discovery client takes precedence.
*
* @author Spencer Gibb
* @author Dave Syer
*/
@CommonsLog
public class DiscoveryClientRouteLocator extends SimpleRouteLocator
implements RefreshableRouteLocator {
public static final String DEFAULT_ROUTE = "/**";
private DiscoveryClient discovery;
private ZuulProperties properties;
private ServiceRouteMapper serviceRouteMapper;
public DiscoveryClientRouteLocator(String servletPath, DiscoveryClient discovery,
ZuulProperties properties) {
super(servletPath, properties);
if (properties.isIgnoreLocalService()) {
ServiceInstance instance = null;
try {
instance = discovery.getLocalServiceInstance();
} catch (Exception e) {
log.warn("Error locating local service instance", e);
}
if (instance != null) {
String localServiceId = instance.getServiceId();
if (!properties.getIgnoredServices().contains(localServiceId)) {
properties.getIgnoredServices().add(localServiceId);
}
}
}
this.serviceRouteMapper = new SimpleServiceRouteMapper();
this.discovery = discovery;
this.properties = properties;
}
public DiscoveryClientRouteLocator(String servletPath, DiscoveryClient discovery,
ZuulProperties properties, ServiceRouteMapper serviceRouteMapper) {
this(servletPath, discovery, properties);
this.serviceRouteMapper = serviceRouteMapper;
}
public void addRoute(String path, String location) {
this.properties.getRoutes().put(path, new ZuulRoute(path, location));
refresh();
}
public void addRoute(ZuulRoute route) {
this.properties.getRoutes().put(route.getPath(), route);
refresh();
}
@Override
protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
routesMap.putAll(super.locateRoutes());
if (this.discovery != null) {
Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>();
for (ZuulRoute route : routesMap.values()) {
String serviceId = route.getServiceId();
if (serviceId == null) {
serviceId = route.getId();
}
if (serviceId != null) {
staticServices.put(serviceId, route);
}
}
// Add routes for discovery services by default
List<String> services = this.discovery.getServices();
String[] ignored = this.properties.getIgnoredServices()
.toArray(new String[0]);
for (String serviceId : services) {
// Ignore specifically ignored services and those that were manually
// configured
String key = "/" + mapRouteToService(serviceId) + "/**";
if (staticServices.containsKey(serviceId)
&& staticServices.get(serviceId).getUrl() == null) {
// Explicitly configured with no URL, cannot be ignored
// all static routes are already in routesMap
// Update location using serviceId if location is null
ZuulRoute staticRoute = staticServices.get(serviceId);
if (!StringUtils.hasText(staticRoute.getLocation())) {
staticRoute.setLocation(serviceId);
}
}
if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
&& !routesMap.containsKey(key)) {
// Not ignored
routesMap.put(key, new ZuulRoute(key, serviceId));
}
}
}
if (routesMap.get(DEFAULT_ROUTE) != null) {
ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
// Move the defaultServiceId to the end
routesMap.remove(DEFAULT_ROUTE);
routesMap.put(DEFAULT_ROUTE, defaultRoute);
}
LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
String path = entry.getKey();
// Prepend with slash if not already present.
if (!path.startsWith("/")) {
path = "/" + path;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
path = this.properties.getPrefix() + path;
if (!path.startsWith("/")) {
path = "/" + path;
}
}
values.put(path, entry.getValue());
}
return values;
}
@Override
public void refresh() {
doRefresh();
}
protected String mapRouteToService(String serviceId) {
return this.serviceRouteMapper.apply(serviceId);
}
protected void addConfiguredRoutes(Map<String, ZuulRoute> routes) {
Map<String, ZuulRoute> routeEntries = this.properties.getRoutes();
for (ZuulRoute entry : routeEntries.values()) {
String route = entry.getPath();
if (routes.containsKey(route)) {
log.warn("Overwriting route " + route + ": already defined by "
+ routes.get(route));
}
routes.put(route, entry);
}
}
}