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..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 @@ -1,16 +1,21 @@ 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.FeignRibbonClient; 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 + * @author Julien Roy */ @Configuration @ConditionalOnClass(Feign.class) @@ -36,4 +41,8 @@ public class FeignAutoConfiguration { public Contract feignContract() { return new SpringMvcContract(); } + + @Bean + @ConditionalOnClass(ILoadBalancer.class) + 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 4b3cfaba..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 @@ -1,5 +1,6 @@ package org.springframework.cloud.netflix.feign; +import feign.Client; import feign.Contract; import feign.Feign; import feign.Logger; @@ -36,9 +37,10 @@ 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 return Feign.builder() .logger(logger) .encoder(encoder) @@ -53,7 +55,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/FeignRibbonClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClient.java new file mode 100644 index 00000000..73c4df33 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClient.java @@ -0,0 +1,67 @@ +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 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 + * @author Spencer Gibb + */ +public class FeignRibbonClient 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); + } + + public void setDefaultClient(Client defaultClient) { + this.defaultClient = defaultClient; + } +} 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(); + } + } + + } + }