Commit 45143bed by Spencer Gibb

Add support for Ribbon loadbalancing in RestTemplate via an…

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
parent 39356928
...@@ -210,7 +210,7 @@ ...@@ -210,7 +210,7 @@
<eureka.version>1.1.135</eureka.version> <eureka.version>1.1.135</eureka.version>
<feign.version>6.1.2</feign.version> <feign.version>6.1.2</feign.version>
<hystrix.version>1.4.0-RC4</hystrix.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> <turbine.version>0.4</turbine.version>
<zuul.version>1.0.24</zuul.version> <zuul.version>1.0.24</zuul.version>
</properties> </properties>
......
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();
}
}
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; ...@@ -19,6 +19,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.ClientProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -47,4 +48,9 @@ public class EurekaClientAutoConfiguration { ...@@ -47,4 +48,9 @@ public class EurekaClientAutoConfiguration {
return new EurekaInstanceConfigBean(); return new EurekaInstanceConfigBean();
} }
@Bean
@ConditionalOnMissingBean(EurekaRibbonInitializer.class)
public EurekaRibbonInitializer eurekaRibbonInitializer(ClientProperties clientProperties) {
return new EurekaRibbonInitializer(clientProperties);
}
} }
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);
}
}
package org.springframework.cloud.netflix.feign; package org.springframework.cloud.netflix.feign;
import com.netflix.config.ConfigurationManager;
import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
import feign.Contract; import feign.Contract;
import feign.Feign; import feign.Feign;
import feign.Logger; import feign.Logger;
...@@ -50,16 +48,7 @@ public class FeignConfigurer { ...@@ -50,16 +48,7 @@ public class FeignConfigurer {
protected <T> T loadBalance(Feign.Builder builder, Class<T> type, String schemeName) { protected <T> T loadBalance(Feign.Builder builder, Class<T> type, String schemeName) {
String name = URI.create(schemeName).getHost(); String name = URI.create(schemeName).getHost();
setServiceListClassAndVIP(name);
return builder.target(LoadBalancingTarget.create(type, schemeName)); 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);
}
} }
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);
}
}
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);
}
}
package org.springframework.cloud.netflix.zuul.filters.route; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
......
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.ClientAutoConfiguration,\
org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,\ org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,\
org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\ org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\ org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\
org.springframework.cloud.netflix.servo.ServoMetricsAutoConfiguration org.springframework.cloud.netflix.servo.ServoMetricsAutoConfiguration
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment