From 943d1b9bbef6b7729f29ca942b806b4773d6e666 Mon Sep 17 00:00:00 2001 From: Julien Roy Date: Mon, 1 Dec 2014 14:22:16 +0100 Subject: [PATCH 1/2] Connect Feign to resilient Ribbon client --- .../netflix/feign/FeignAutoConfiguration.java | 8 ++ .../cloud/netflix/feign/FeignConfigurer.java | 11 +- .../feign/ribbon/RibbonLoadBalancer.java | 134 ++++++++++++++++++ .../ribbon/RibbonLoadBalancerClient.java | 65 +++++++++ 4 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancer.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancerClient.java diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java index 4b93a325..f713978a 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java @@ -1,14 +1,18 @@ package org.springframework.cloud.netflix.feign; +import feign.Client; import feign.Contract; import feign.Feign; import feign.Logger; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cloud.netflix.feign.ribbon.RibbonLoadBalancerClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; +import com.netflix.loadbalancer.ILoadBalancer; + /** * @author Spencer Gibb */ @@ -36,4 +40,8 @@ public class FeignAutoConfiguration { public Contract feignContract() { return new SpringMvcContract(); } + + @Bean + @ConditionalOnClass(ILoadBalancer.class) + public Client feignRibbonClient() { return new RibbonLoadBalancerClient(); } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfigurer.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfigurer.java index 4b3cfaba..289a8ea7 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfigurer.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfigurer.java @@ -1,5 +1,6 @@ package org.springframework.cloud.netflix.feign; +import feign.Client; import feign.Contract; import feign.Feign; import feign.Logger; @@ -36,6 +37,9 @@ public class FeignConfigurer { @Autowired Contract contract; + @Autowired(required = false) + Client ribbonClient; + protected Feign.Builder feign() { //ConfigurationManager.getConfigInstance().setProperty("exampleBackend.ribbon.listOfServers", "localhost:7080"); //exampleBackend.ribbon.NIWSServerListClassName=my.package.MyServerList @@ -53,7 +57,12 @@ public class FeignConfigurer { protected T loadBalance(Feign.Builder builder, Class type, String schemeName) { String name = URI.create(schemeName).getHost(); ribbonClientPreprocessor.preprocess(name); - return builder.target(LoadBalancingTarget.create(type, schemeName)); + + if(ribbonClient != null) { + return builder.client(ribbonClient).target(type, schemeName); + } else { + return builder.target(LoadBalancingTarget.create(type, schemeName)); + } } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancer.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancer.java new file mode 100644 index 00000000..3d8ab9a5 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancer.java @@ -0,0 +1,134 @@ +package org.springframework.cloud.netflix.feign.ribbon; + +import java.io.IOException; +import java.net.URI; +import java.util.Collection; +import java.util.Map; + +import com.netflix.client.AbstractLoadBalancerAwareClient; +import com.netflix.client.ClientException; +import com.netflix.client.ClientRequest; +import com.netflix.client.IResponse; +import com.netflix.client.RequestSpecificRetryHandler; +import com.netflix.client.RetryHandler; +import com.netflix.client.config.CommonClientConfigKey; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; + +import feign.Client; +import feign.Request; +import feign.RequestTemplate; +import feign.Response; + +public class RibbonLoadBalancer extends AbstractLoadBalancerAwareClient { + + private final Client delegate; + private final int connectTimeout; + private final int readTimeout; + private final IClientConfig clientConfig; + + public RibbonLoadBalancer(Client delegate, ILoadBalancer lb, IClientConfig clientConfig) { + super(lb, clientConfig); + this.setRetryHandler(RetryHandler.DEFAULT); + this.clientConfig = clientConfig; + this.delegate = delegate; + connectTimeout = clientConfig.get(CommonClientConfigKey.ConnectTimeout); + readTimeout = clientConfig.get(CommonClientConfigKey.ReadTimeout); + } + + @Override + public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException { + Request.Options options; + if (configOverride != null) { + options = new Request.Options(configOverride.get(CommonClientConfigKey.ConnectTimeout, connectTimeout), (configOverride.get(CommonClientConfigKey.ReadTimeout, readTimeout))); + } else { + options = new Request.Options(connectTimeout, readTimeout); + } + Response response = delegate.execute(request.toRequest(), options); + return new RibbonResponse(request.getUri(), response); + } + + @Override + public RequestSpecificRetryHandler getRequestSpecificRetryHandler( + RibbonRequest request, IClientConfig requestConfig) { + if (clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations, false)) { + return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); + } + if (!request.toRequest().method().equals("GET")) { + return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig); + } else { + return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); + } + } + + static class RibbonRequest extends ClientRequest implements Cloneable { + + private final Request request; + + RibbonRequest(Request request, URI uri) { + this.request = request; + setUri(uri); + } + + Request toRequest() { + return new RequestTemplate() + .method(request.method()) + .append(getUri().toASCIIString()) + .headers(request.headers()) + .body(request.body(), request.charset()) + .request(); + } + + public Object clone() { + return new RibbonRequest(request, getUri()); + } + } + + static class RibbonResponse implements IResponse { + + private final URI uri; + private final Response response; + + RibbonResponse(URI uri, Response response) { + this.uri = uri; + this.response = response; + } + + @Override + public Object getPayload() throws ClientException { + return response.body(); + } + + @Override + public boolean hasPayload() { + return response.body() != null; + } + + @Override + public boolean isSuccess() { + return response.status() == 200; + } + + @Override + public URI getRequestedURI() { + return uri; + } + + @Override + public Map> getHeaders() { + return response.headers(); + } + + Response toResponse() { + return response; + } + + @Override + public void close() throws IOException { + if (response != null && response.body() != null) { + response.body().close(); + } + } + + } + } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancerClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancerClient.java new file mode 100644 index 00000000..d47f4031 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancerClient.java @@ -0,0 +1,65 @@ +package org.springframework.cloud.netflix.feign.ribbon; + +import java.io.IOException; +import java.net.URI; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.netflix.ribbon.RibbonClientPreprocessor; + +import com.google.common.base.Throwables; +import com.netflix.client.ClientException; +import com.netflix.client.ClientFactory; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; + +import dagger.Lazy; +import feign.Client; +import feign.Request; +import feign.Response; + +/** + * @author: Julien Roy + */ +public class RibbonLoadBalancerClient implements Client { + + private Client defaultClient = new Default( + new Lazy() { + @Override + public SSLSocketFactory get() { + return (SSLSocketFactory) SSLSocketFactory.getDefault(); + } + }, new Lazy() { + @Override + public HostnameVerifier get() { + return HttpsURLConnection.getDefaultHostnameVerifier(); + } + }); + + @Override + public Response execute(Request request, Request.Options options) throws IOException { + try { + + URI asUri = URI.create(request.url()); + String clientName = asUri.getHost(); + URI uriWithoutSchemeAndPort = URI.create(request.url().replace(asUri.getScheme() + "://" + asUri.getHost(), "")); + RibbonLoadBalancer.RibbonRequest ribbonRequest = new RibbonLoadBalancer.RibbonRequest(request, uriWithoutSchemeAndPort); + return lbClient(clientName).executeWithLoadBalancer(ribbonRequest).toResponse(); + + } catch (ClientException e) { + if (e.getCause() instanceof IOException) { + throw IOException.class.cast(e.getCause()); + } + throw Throwables.propagate(e); + } + } + + private RibbonLoadBalancer lbClient(String clientName) { + IClientConfig config = ClientFactory.getNamedConfig(clientName); + ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName); + return new RibbonLoadBalancer(defaultClient, lb, config); + } +} From a7c6c88ff0dc9e373a56c3ca15bf98ae72ce512a Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 1 Dec 2014 13:14:14 -0700 Subject: [PATCH 2/2] renamed RibbonLoadBalancerClient to FeignRibbonClient --- .../cloud/netflix/feign/FeignAutoConfiguration.java | 5 +++-- .../cloud/netflix/feign/FeignConfigurer.java | 2 -- ...oadBalancerClient.java => FeignRibbonClient.java} | 12 +++++++----- 3 files changed, 10 insertions(+), 9 deletions(-) rename spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/{RibbonLoadBalancerClient.java => FeignRibbonClient.java} (90%) diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java index f713978a..40978aac 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java @@ -6,7 +6,7 @@ import feign.Feign; import feign.Logger; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.cloud.netflix.feign.ribbon.RibbonLoadBalancerClient; +import org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; @@ -15,6 +15,7 @@ import com.netflix.loadbalancer.ILoadBalancer; /** * @author Spencer Gibb + * @author Julien Roy */ @Configuration @ConditionalOnClass(Feign.class) @@ -43,5 +44,5 @@ public class FeignAutoConfiguration { @Bean @ConditionalOnClass(ILoadBalancer.class) - public Client feignRibbonClient() { return new RibbonLoadBalancerClient(); } + public Client feignRibbonClient() { return new FeignRibbonClient(); } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfigurer.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfigurer.java index 289a8ea7..5a6447ba 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfigurer.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfigurer.java @@ -41,8 +41,6 @@ public class FeignConfigurer { Client ribbonClient; protected Feign.Builder feign() { - //ConfigurationManager.getConfigInstance().setProperty("exampleBackend.ribbon.listOfServers", "localhost:7080"); - //exampleBackend.ribbon.NIWSServerListClassName=my.package.MyServerList return Feign.builder() .logger(logger) .encoder(encoder) diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancerClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClient.java similarity index 90% rename from spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancerClient.java rename to spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClient.java index d47f4031..73c4df33 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RibbonLoadBalancerClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClient.java @@ -7,9 +7,6 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.netflix.ribbon.RibbonClientPreprocessor; - import com.google.common.base.Throwables; import com.netflix.client.ClientException; import com.netflix.client.ClientFactory; @@ -22,9 +19,10 @@ import feign.Request; import feign.Response; /** - * @author: Julien Roy + * @author Julien Roy + * @author Spencer Gibb */ -public class RibbonLoadBalancerClient implements Client { +public class FeignRibbonClient implements Client { private Client defaultClient = new Default( new Lazy() { @@ -62,4 +60,8 @@ public class RibbonLoadBalancerClient implements Client { ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName); return new RibbonLoadBalancer(defaultClient, lb, config); } + + public void setDefaultClient(Client defaultClient) { + this.defaultClient = defaultClient; + } }