Skip to content

Commit fd5e4d6

Browse files
committed
Make RibbonClient configurable with FeignBuilder
1 parent 98d0423 commit fd5e4d6

3 files changed

Lines changed: 105 additions & 41 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package feign.ribbon;
2+
3+
import com.google.common.base.Throwables;
4+
import com.netflix.client.ClientException;
5+
import com.netflix.client.ClientFactory;
6+
import com.netflix.client.config.IClientConfig;
7+
import com.netflix.loadbalancer.ILoadBalancer;
8+
9+
import java.io.IOException;
10+
import java.net.URI;
11+
12+
import javax.net.ssl.HostnameVerifier;
13+
import javax.net.ssl.HttpsURLConnection;
14+
import javax.net.ssl.SSLSocketFactory;
15+
16+
import feign.Client;
17+
import feign.Request;
18+
import feign.Response;
19+
import dagger.Lazy;
20+
21+
/**
22+
* RibbonClient can be used in Fiegn builder to activate smart routing and resiliency capabilities provided by Ribbon.
23+
* Ex.
24+
* <pre>
25+
* MyService api = Feign.builder.client(new RibbonClient()).target(MyService.class, "http://myAppProd");
26+
* </pre>
27+
* Where {@code myAppProd} is the ribbon client name and {@code myAppProd.ribbon.listOfServers} configuration
28+
* is set.
29+
*/
30+
public class RibbonClient implements Client {
31+
32+
private final Client delegate;
33+
34+
public RibbonClient() {
35+
this.delegate = new Client.Default(
36+
new Lazy<SSLSocketFactory>() {
37+
public SSLSocketFactory get() {
38+
return (SSLSocketFactory)SSLSocketFactory.getDefault();
39+
}
40+
},
41+
new Lazy<HostnameVerifier>() {
42+
public HostnameVerifier get() {
43+
return HttpsURLConnection.getDefaultHostnameVerifier();
44+
}
45+
}
46+
);
47+
}
48+
49+
public RibbonClient(Client delegate) {
50+
this.delegate = delegate;
51+
}
52+
53+
@Override public Response execute(Request request, Request.Options options) throws IOException {
54+
try {
55+
URI asUri = URI.create(request.url());
56+
String clientName = asUri.getHost();
57+
URI uriWithoutSchemeAndPort = URI.create(request.url().replace(asUri.getScheme() + "://" + asUri.getHost(), ""));
58+
LBClient.RibbonRequest ribbonRequest = new LBClient.RibbonRequest(request, uriWithoutSchemeAndPort);
59+
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest).toResponse();
60+
} catch (ClientException e) {
61+
if (e.getCause() instanceof IOException) {
62+
throw IOException.class.cast(e.getCause());
63+
}
64+
throw Throwables.propagate(e);
65+
}
66+
}
67+
68+
private LBClient lbClient(String clientName) {
69+
IClientConfig config = ClientFactory.getNamedConfig(clientName);
70+
ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
71+
return new LBClient(delegate, lb, config);
72+
}
73+
}

ribbon/src/main/java/feign/ribbon/RibbonModule.java

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@
1515
*/
1616
package feign.ribbon;
1717

18-
import com.google.common.base.Throwables;
19-
import com.netflix.client.ClientException;
20-
import com.netflix.client.ClientFactory;
21-
import com.netflix.client.config.IClientConfig;
22-
import com.netflix.loadbalancer.ILoadBalancer;
23-
2418
import java.io.IOException;
2519
import java.net.URI;
2620

@@ -30,8 +24,6 @@
3024

3125
import dagger.Provides;
3226
import feign.Client;
33-
import feign.Request;
34-
import feign.Response;
3527

3628
/**
3729
* Adding this module will override URL resolution of {@link feign.Client Feign's client},
@@ -55,38 +47,7 @@ public class RibbonModule {
5547
return delegate;
5648
}
5749

58-
@Provides @Singleton Client httpClient(RibbonClient ribbon) {
59-
return ribbon;
60-
}
61-
62-
@Singleton
63-
static class RibbonClient implements Client {
64-
private final Client delegate;
65-
66-
@Inject
67-
public RibbonClient(@Named("delegate") Client delegate) {
68-
this.delegate = delegate;
69-
}
70-
71-
@Override public Response execute(Request request, Request.Options options) throws IOException {
72-
try {
73-
URI asUri = URI.create(request.url());
74-
String clientName = asUri.getHost();
75-
URI uriWithoutSchemeAndPort = URI.create(request.url().replace(asUri.getScheme() + "://" + asUri.getHost(), ""));
76-
LBClient.RibbonRequest ribbonRequest = new LBClient.RibbonRequest(request, uriWithoutSchemeAndPort);
77-
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest).toResponse();
78-
} catch (ClientException e) {
79-
if (e.getCause() instanceof IOException) {
80-
throw IOException.class.cast(e.getCause());
81-
}
82-
throw Throwables.propagate(e);
83-
}
84-
}
85-
86-
private LBClient lbClient(String clientName) {
87-
IClientConfig config = ClientFactory.getNamedConfig(clientName);
88-
ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
89-
return new LBClient(delegate, lb, config);
90-
}
50+
@Provides @Singleton Client httpClient(@Named("delegate") Client client) {
51+
return new RibbonClient(client);
9152
}
9253
}

ribbon/src/test/java/feign/ribbon/RibbonClientTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.mockwebserver.MockWebServer;
2020
import com.google.mockwebserver.SocketPolicy;
2121
import dagger.Provides;
22+
import feign.Client;
2223
import feign.Feign;
2324
import feign.RequestLine;
2425
import feign.codec.Decoder;
@@ -147,6 +148,35 @@ invalid characters (ex. space).
147148
}
148149

149150

151+
@Test
152+
public void ioExceptionRetryWithBuilder() throws IOException, InterruptedException {
153+
String client = "RibbonClientTest-ioExceptionRetryWithBuilder";
154+
String serverListKey = client + ".ribbon.listOfServers";
155+
156+
MockWebServer server = new MockWebServer();
157+
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
158+
server.enqueue(new MockResponse().setBody("success!".getBytes(UTF_8)));
159+
server.play();
160+
161+
getConfigInstance().setProperty(serverListKey, hostAndPort(server.getUrl("")));
162+
163+
try {
164+
165+
TestInterface api = Feign.builder().
166+
client(new RibbonClient()).
167+
target(TestInterface.class, "http://" + client);
168+
169+
api.post();
170+
171+
assertEquals(server.getRequestCount(), 2);
172+
// TODO: verify ribbon stats match
173+
// assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat())
174+
} finally {
175+
server.shutdown();
176+
getConfigInstance().clearProperty(serverListKey);
177+
}
178+
}
179+
150180

151181
static String hostAndPort(URL url) {
152182
// our build slaves have underscores in their hostnames which aren't permitted by ribbon

0 commit comments

Comments
 (0)