Skip to content

Commit 4f6f8d4

Browse files
bstick12Adrian Cole
authored andcommitted
Adds LBClientFactory to enable caching of Ribbon LBClients
Before, LBClients were created for each request, which led to issues such as OpenFeign#182. Moreover, a user could not avoid using Ribbon's static factories. Adding LBClientFactory allows users to control how Ribbon resources are created.
1 parent ad5519b commit 4f6f8d4

File tree

8 files changed

+138
-27
lines changed

8 files changed

+138
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### Version 7.3
22
* Adds Request.Options support to RibbonClient
3+
* Adds LBClientFactory to enable caching of Ribbon LBClients
34
* Updates to Ribbon 2.0-RC13
45
* Updates to Jackson 2.5.1
56
* Supports query parameters without values

ribbon/src/main/java/feign/ribbon/LBClient.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,21 @@
3535
import feign.RequestTemplate;
3636
import feign.Response;
3737

38-
class LBClient
39-
extends AbstractLoadBalancerAwareClient<LBClient.RibbonRequest, LBClient.RibbonResponse> {
38+
public final class LBClient extends
39+
AbstractLoadBalancerAwareClient<LBClient.RibbonRequest, LBClient.RibbonResponse> {
4040

41-
private final Client delegate;
4241
private final int connectTimeout;
4342
private final int readTimeout;
4443
private final IClientConfig clientConfig;
4544

46-
LBClient(Client delegate, ILoadBalancer lb, IClientConfig clientConfig) {
45+
public static LBClient create(ILoadBalancer lb, IClientConfig clientConfig) {
46+
return new LBClient(lb, clientConfig);
47+
}
48+
49+
LBClient(ILoadBalancer lb, IClientConfig clientConfig) {
4750
super(lb, clientConfig);
4851
this.setRetryHandler(RetryHandler.DEFAULT);
4952
this.clientConfig = clientConfig;
50-
this.delegate = delegate;
5153
connectTimeout = clientConfig.get(CommonClientConfigKey.ConnectTimeout);
5254
readTimeout = clientConfig.get(CommonClientConfigKey.ReadTimeout);
5355
}
@@ -64,7 +66,7 @@ public RibbonResponse execute(RibbonRequest request, IClientConfig configOverrid
6466
} else {
6567
options = new Request.Options(connectTimeout, readTimeout);
6668
}
67-
Response response = delegate.execute(request.toRequest(), options);
69+
Response response = request.client().execute(request.toRequest(), options);
6870
return new RibbonResponse(request.getUri(), response);
6971
}
7072

@@ -84,8 +86,10 @@ public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
8486
static class RibbonRequest extends ClientRequest implements Cloneable {
8587

8688
private final Request request;
89+
private final Client client;
8790

88-
RibbonRequest(Request request, URI uri) {
91+
RibbonRequest(Client client, Request request, URI uri) {
92+
this.client = client;
8993
this.request = request;
9094
setUri(uri);
9195
}
@@ -99,8 +103,12 @@ Request toRequest() {
99103
.request();
100104
}
101105

106+
Client client() {
107+
return client;
108+
}
109+
102110
public Object clone() {
103-
return new RibbonRequest(request, getUri());
111+
return new RibbonRequest(client, request, getUri());
104112
}
105113
}
106114

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package feign.ribbon;
2+
3+
import com.netflix.client.ClientFactory;
4+
import com.netflix.client.config.IClientConfig;
5+
import com.netflix.loadbalancer.ILoadBalancer;
6+
7+
public interface LBClientFactory {
8+
9+
LBClient create(String clientName);
10+
11+
/**
12+
* Uses {@link ClientFactory} static factories from ribbon to create an LBClient.
13+
*/
14+
public static final class Default implements LBClientFactory {
15+
@Override
16+
public LBClient create(String clientName) {
17+
IClientConfig config = ClientFactory.getNamedConfig(clientName);
18+
ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
19+
return LBClient.create(lb, config);
20+
}
21+
}
22+
}

ribbon/src/main/java/feign/ribbon/LoadBalancingTarget.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public Request apply(RequestTemplate input) {
103103
@Override
104104
public boolean equals(Object obj) {
105105
if (obj instanceof LoadBalancingTarget) {
106-
LoadBalancingTarget<?> other = (LoadBalancingTarget) obj;
106+
LoadBalancingTarget<?> other = (LoadBalancingTarget<?>) obj;
107107
return type.equals(other.type)
108108
&& name.equals(other.name);
109109
}

ribbon/src/main/java/feign/ribbon/RibbonClient.java

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package feign.ribbon;
22

33
import com.netflix.client.ClientException;
4-
import com.netflix.client.ClientFactory;
54
import com.netflix.client.config.CommonClientConfigKey;
65
import com.netflix.client.config.DefaultClientConfigImpl;
7-
import com.netflix.client.config.IClientConfig;
8-
import com.netflix.loadbalancer.ILoadBalancer;
96

107
import java.io.IOException;
118
import java.net.URI;
@@ -20,21 +17,37 @@
2017
import feign.Response;
2118

2219
/**
23-
* RibbonClient can be used in Fiegn builder to activate smart routing and resiliency capabilities
20+
* RibbonClient can be used in Feign builder to activate smart routing and resiliency capabilities
2421
* provided by Ribbon. Ex.
22+
*
2523
* <pre>
26-
* MyService api = Feign.builder.client(new RibbonClient()).target(MyService.class,
27-
* "http://myAppProd");
24+
* MyService api = Feign.builder.client(RibbonClient.create()).target(MyService.class,
25+
* &quot;http://myAppProd&quot;);
2826
* </pre>
27+
*
2928
* Where {@code myAppProd} is the ribbon client name and {@code myAppProd.ribbon.listOfServers}
3029
* configuration is set.
3130
*/
3231
public class RibbonClient implements Client {
3332

3433
private final Client delegate;
34+
private final LBClientFactory lbClientFactory;
3535

36+
37+
public static RibbonClient create() {
38+
return builder().build();
39+
}
40+
41+
public static Builder builder() {
42+
return new Builder();
43+
}
44+
45+
/**
46+
* @deprecated Use the {@link RibbonClient#create()}
47+
*/
48+
@Deprecated
3649
public RibbonClient() {
37-
this.delegate = new Client.Default(
50+
this(new Client.Default(
3851
new Lazy<SSLSocketFactory>() {
3952
public SSLSocketFactory get() {
4053
return (SSLSocketFactory) SSLSocketFactory.getDefault();
@@ -45,11 +58,20 @@ public HostnameVerifier get() {
4558
return HttpsURLConnection.getDefaultHostnameVerifier();
4659
}
4760
}
48-
);
61+
));
4962
}
5063

64+
/**
65+
* @deprecated Use the {@link RibbonClient#create()}
66+
*/
67+
@Deprecated
5168
public RibbonClient(Client delegate) {
69+
this(delegate, new LBClientFactory.Default());
70+
}
71+
72+
RibbonClient(Client delegate, LBClientFactory lbClientFactory) {
5273
this.delegate = delegate;
74+
this.lbClientFactory = lbClientFactory;
5375
}
5476

5577
@Override
@@ -58,7 +80,8 @@ public Response execute(Request request, Request.Options options) throws IOExcep
5880
URI asUri = URI.create(request.url());
5981
String clientName = asUri.getHost();
6082
URI uriWithoutHost = URI.create(request.url().replace(asUri.getHost(), ""));
61-
LBClient.RibbonRequest ribbonRequest = new LBClient.RibbonRequest(request, uriWithoutHost);
83+
LBClient.RibbonRequest ribbonRequest =
84+
new LBClient.RibbonRequest(delegate, request, uriWithoutHost);
6285
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
6386
new FeignOptionsClientConfig(options)).toResponse();
6487
} catch (ClientException e) {
@@ -70,9 +93,7 @@ public Response execute(Request request, Request.Options options) throws IOExcep
7093
}
7194

7295
private LBClient lbClient(String clientName) {
73-
IClientConfig config = ClientFactory.getNamedConfig(clientName);
74-
ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
75-
return new LBClient(delegate, lb, config);
96+
return lbClientFactory.create(clientName);
7697
}
7798

7899
static class FeignOptionsClientConfig extends DefaultClientConfigImpl {
@@ -94,4 +115,40 @@ public void loadDefaultValues() {
94115

95116
}
96117

118+
public static final class Builder {
119+
120+
Builder() {
121+
}
122+
123+
private Client delegate;
124+
private LBClientFactory lbClientFactory;
125+
126+
public Builder delegate(Client delegate) {
127+
this.delegate = delegate;
128+
return this;
129+
}
130+
131+
public Builder lbClientFactory(LBClientFactory lbClientFactory) {
132+
this.lbClientFactory = lbClientFactory;
133+
return this;
134+
}
135+
136+
public RibbonClient build() {
137+
return new RibbonClient(
138+
delegate != null ? delegate : new Client.Default(
139+
new Lazy<SSLSocketFactory>() {
140+
public SSLSocketFactory get() {
141+
return (SSLSocketFactory) SSLSocketFactory.getDefault();
142+
}
143+
},
144+
new Lazy<HostnameVerifier>() {
145+
public HostnameVerifier get() {
146+
return HttpsURLConnection.getDefaultHostnameVerifier();
147+
}
148+
}
149+
),
150+
lbClientFactory != null ? lbClientFactory : new LBClientFactory.Default()
151+
);
152+
}
153+
}
97154
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
@dagger.Module(overrides = true, library = true, complete = false)
3737
public class RibbonModule {
3838

39+
@Provides
40+
LBClientFactory lbClientFactory() {
41+
return new LBClientFactory.Default();
42+
}
43+
3944
@Provides
4045
@Named("delegate")
4146
Client delegate(Client.Default delegate) {
@@ -44,7 +49,7 @@ Client delegate(Client.Default delegate) {
4449

4550
@Provides
4651
@Singleton
47-
Client httpClient(@Named("delegate") Client client) {
48-
return new RibbonClient(client);
52+
Client httpClient(@Named("delegate") Client client, LBClientFactory lbClientFactory) {
53+
return new RibbonClient(client, lbClientFactory);
4954
}
5055
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package feign.ribbon;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import org.junit.Test;
6+
7+
import com.netflix.client.ClientFactory;
8+
9+
public class LBClientFactoryTest {
10+
11+
@Test
12+
public void testCreateLBClient() {
13+
LBClientFactory.Default lbClientFactory = new LBClientFactory.Default();
14+
LBClient client = lbClientFactory.create("clientName");
15+
assertEquals("clientName", client.getClientName());
16+
assertEquals(ClientFactory.getNamedLoadBalancer("clientName"), client.getLoadBalancer());
17+
}
18+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public HostnameVerifier get() {
156156
getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl("")));
157157

158158
TestInterface api =
159-
Feign.builder().client(new RibbonClient(trustSSLSockets))
159+
Feign.builder().client(RibbonClient.builder().delegate(trustSSLSockets).build())
160160
.target(TestInterface.class, "https://" + client());
161161
api.post();
162162
assertEquals(1, server1.getRequestCount());
@@ -170,9 +170,9 @@ public void ioExceptionRetryWithBuilder() throws IOException, InterruptedExcepti
170170

171171
getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl("")));
172172

173-
TestInterface api = Feign.builder().
174-
client(new RibbonClient()).
175-
target(TestInterface.class, "http://" + client());
173+
TestInterface api =
174+
Feign.builder().client(RibbonClient.create())
175+
.target(TestInterface.class, "http://" + client());
176176

177177
api.post();
178178

0 commit comments

Comments
 (0)