Add support for Ribbon loadbalancing in RestTemplate via an HttpRequestInterceptor that when supplied a uri such as http://myservice/my/endpoint (where myservice is a service as defined in a service registry, such as Eureka), it replaces 'myservice' with the actual host and port as returned from the configured ribbon loadbalancer.
closes gh-12
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -210,7 +210,7 @@
|
||||
<eureka.version>1.1.135</eureka.version>
|
||||
<feign.version>6.1.2</feign.version>
|
||||
<hystrix.version>1.4.0-RC4</hystrix.version>
|
||||
<ribbon.version>0.3.12</ribbon.version>
|
||||
<ribbon.version>0.3.13</ribbon.version>
|
||||
<turbine.version>0.4</turbine.version>
|
||||
<zuul.version>1.0.24</zuul.version>
|
||||
</properties>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springframework.cloud.client;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* TODO: move org.springframework.cloud.client to a spring-cloud-common project
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
@ConditionalOnExpression("${spring.cloud.client.enabled:true}")
|
||||
public class ClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ClientProperties.class)
|
||||
public ClientProperties clientProperties() {
|
||||
return new ClientProperties();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.cloud.client;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("spring.cloud.client")
|
||||
public class ClientProperties {
|
||||
private List<String> serviceIds;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.ClientProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -47,4 +48,9 @@ public class EurekaClientAutoConfiguration {
|
||||
return new EurekaInstanceConfigBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(EurekaRibbonInitializer.class)
|
||||
public EurekaRibbonInitializer eurekaRibbonInitializer(ClientProperties clientProperties) {
|
||||
return new EurekaRibbonInitializer(clientProperties);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.springframework.cloud.netflix.eureka;
|
||||
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
|
||||
import org.springframework.cloud.client.ClientProperties;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class EurekaRibbonInitializer {
|
||||
|
||||
public EurekaRibbonInitializer(ClientProperties clientProperties) {
|
||||
if (clientProperties.getServiceIds() != null) {
|
||||
for (String serviceId : clientProperties.getServiceIds()) {
|
||||
setServiceListClassAndVIP(serviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setServiceListClassAndVIP(String serviceId) {
|
||||
setProp(serviceId, "NIWSServerListClassName", DiscoveryEnabledNIWSServerList.class.getName());
|
||||
setProp(serviceId, "DeploymentContextBasedVipAddresses", serviceId); //FIXME: what should this be?
|
||||
}
|
||||
|
||||
private static void setProp(String serviceId, String suffix, String value) {
|
||||
ConfigurationManager.getConfigInstance().setProperty(serviceId + ".ribbon." + suffix, value);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
|
||||
import feign.Contract;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
@@ -50,16 +48,7 @@ public class FeignConfigurer {
|
||||
|
||||
protected <T> T loadBalance(Feign.Builder builder, Class<T> type, String schemeName) {
|
||||
String name = URI.create(schemeName).getHost();
|
||||
setServiceListClassAndVIP(name);
|
||||
return builder.target(LoadBalancingTarget.create(type, schemeName));
|
||||
}
|
||||
|
||||
public static void setServiceListClassAndVIP(String serviceId) {
|
||||
setProp(serviceId, "NIWSServerListClassName", DiscoveryEnabledNIWSServerList.class.getName());
|
||||
setProp(serviceId, "DeploymentContextBasedVipAddresses", serviceId); //FIXME: what should this be?
|
||||
}
|
||||
|
||||
private static void setProp(String serviceId, String suffix, String value) {
|
||||
ConfigurationManager.getConfigInstance().setProperty(serviceId + ".ribbon." + suffix, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.eureka.EurekaRibbonInitializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureAfter(EurekaClientAutoConfiguration.class)
|
||||
public class RibbonAutoConfiguration {
|
||||
|
||||
//TODO: why doesn't @AutoConfigureAfter(EurekaClientAutoConfiguration.class) do what the following does for order?
|
||||
@Autowired
|
||||
EurekaRibbonInitializer eurekaRibbonInitializer;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RestTemplate.class)
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RibbonInterceptor ribbonInterceptor() {
|
||||
return new RibbonInterceptor();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
List<ClientHttpRequestInterceptor> list = new ArrayList<>();
|
||||
list.add(ribbonInterceptor());
|
||||
restTemplate().setInterceptors(list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import com.netflix.client.ClientFactory;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.client.support.HttpRequestWrapper;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class RibbonInterceptor implements ClientHttpRequestInterceptor {
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
|
||||
HttpRequestWrapper wrapper = new HttpRequestWrapper(request) {
|
||||
@Override
|
||||
public URI getURI() {
|
||||
URI originalUri = super.getURI();
|
||||
String serviceName = originalUri.getHost();
|
||||
ILoadBalancer loadBalancer = ClientFactory.getNamedLoadBalancer(serviceName);
|
||||
Server server = loadBalancer.chooseServer(null);
|
||||
if (server == null) {
|
||||
throw new IllegalStateException("Unable to locate ILoadBalancer for service: "+ serviceName);
|
||||
}
|
||||
URI uri = UriComponentsBuilder.fromUri(originalUri)
|
||||
.host(server.getHost())
|
||||
.port(server.getPort())
|
||||
.build()
|
||||
.toUri();
|
||||
return uri;
|
||||
}
|
||||
};
|
||||
return execution.execute(wrapper, body);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import static org.springframework.cloud.netflix.feign.FeignConfigurer.setServiceListClassAndVIP;
|
||||
import static org.springframework.cloud.netflix.eureka.EurekaRibbonInitializer.setServiceListClassAndVIP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.client.ClientAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.servo.ServoMetricsAutoConfiguration
|
||||
Reference in New Issue
Block a user