1919import static org .hamcrest .core .IsEqual .equalTo ;
2020import static org .junit .Assert .assertEquals ;
2121import static org .junit .Assert .assertThat ;
22+ import static org .junit .Assert .assertTrue ;
23+ import static org .junit .Assert .fail ;
2224
2325import java .io .IOException ;
2426import java .net .URI ;
2527import java .net .URL ;
2628
2729import org .junit .After ;
30+ import org .junit .AfterClass ;
31+ import org .junit .BeforeClass ;
2832import org .junit .Rule ;
2933import org .junit .Test ;
3034import org .junit .rules .TestName ;
4044import feign .Param ;
4145import feign .Request ;
4246import feign .RequestLine ;
47+ import feign .RetryableException ;
48+ import feign .Retryer ;
4349import feign .client .TrustingSSLSocketFactory ;
4450
4551public class RibbonClientTest {
@@ -51,6 +57,26 @@ public class RibbonClientTest {
5157 @ Rule
5258 public final MockWebServer server2 = new MockWebServer ();
5359
60+ private static String oldRetryConfig = null ;
61+
62+ private static final String SUN_RETRY_PROPERTY = "sun.net.http.retryPost" ;
63+
64+ @ BeforeClass
65+ public static void disableSunRetry () throws Exception {
66+ // The Sun HTTP Client retries all requests once on an IOException, which makes testing retry code harder than would
67+ // be ideal. We can only disable it for post, so lets at least do that.
68+ oldRetryConfig = System .setProperty (SUN_RETRY_PROPERTY , "false" );
69+ }
70+
71+ @ AfterClass
72+ public static void resetSunRetry () throws Exception {
73+ if (oldRetryConfig == null ) {
74+ System .clearProperty (SUN_RETRY_PROPERTY );
75+ } else {
76+ System .setProperty (SUN_RETRY_PROPERTY , oldRetryConfig );
77+ }
78+ }
79+
5480 static String hostAndPort (URL url ) {
5581 // our build slaves have underscores in their hostnames which aren't permitted by ribbon
5682 return "localhost:" + url .getPort ();
@@ -98,6 +124,83 @@ public void ioExceptionRetry() throws IOException, InterruptedException {
98124 // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat())
99125 }
100126
127+ @ Test
128+ public void ioExceptionFailsAfterTooManyFailures () throws IOException , InterruptedException {
129+ server1 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
130+ server1 .enqueue (new MockResponse ().setBody ("success!" ));
131+
132+ getConfigInstance ().setProperty (serverListKey (), hostAndPort (server1 .url ("" ).url ()));
133+
134+ TestInterface
135+ api =
136+ Feign .builder ().client (RibbonClient .create ()).retryer (Retryer .NEVER_RETRY )
137+ .target (TestInterface .class , "http://" + client ());
138+
139+ try {
140+ api .post ();
141+ fail ("No exception thrown" );
142+ } catch (RetryableException ignored ) {
143+
144+ }
145+ assertEquals (1 , server1 .getRequestCount ());
146+ // TODO: verify ribbon stats match
147+ // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat())
148+ }
149+
150+ @ Test
151+ public void ribbonRetryConfigurationOnSameServer () throws IOException , InterruptedException {
152+ server1 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
153+ server1 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
154+ server2 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
155+ server2 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
156+
157+ getConfigInstance ().setProperty (serverListKey (), hostAndPort (server1 .url ("" ).url ()) + "," + hostAndPort (server2 .url ("" ).url ()));
158+ getConfigInstance ().setProperty (client () + ".ribbon.MaxAutoRetries" , 1 );
159+
160+ TestInterface
161+ api =
162+ Feign .builder ().client (RibbonClient .create ()).retryer (Retryer .NEVER_RETRY )
163+ .target (TestInterface .class , "http://" + client ());
164+
165+ try {
166+ api .post ();
167+ fail ("No exception thrown" );
168+ } catch (RetryableException ignored ) {
169+
170+ }
171+ assertTrue (server1 .getRequestCount () == 2 || server2 .getRequestCount () == 2 );
172+ assertEquals (2 , server1 .getRequestCount () + server2 .getRequestCount ());
173+ // TODO: verify ribbon stats match
174+ // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat())
175+ }
176+
177+ @ Test
178+ public void ribbonRetryConfigurationOnMultipleServers () throws IOException , InterruptedException {
179+ server1 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
180+ server1 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
181+ server2 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
182+ server2 .enqueue (new MockResponse ().setSocketPolicy (SocketPolicy .DISCONNECT_AT_START ));
183+
184+ getConfigInstance ().setProperty (serverListKey (), hostAndPort (server1 .url ("" ).url ()) + "," + hostAndPort (server2 .url ("" ).url ()));
185+ getConfigInstance ().setProperty (client () + ".ribbon.MaxAutoRetriesNextServer" , 1 );
186+
187+ TestInterface
188+ api =
189+ Feign .builder ().client (RibbonClient .create ()).retryer (Retryer .NEVER_RETRY )
190+ .target (TestInterface .class , "http://" + client ());
191+
192+ try {
193+ api .post ();
194+ fail ("No exception thrown" );
195+ } catch (RetryableException ignored ) {
196+
197+ }
198+ assertEquals (1 , server1 .getRequestCount ());
199+ assertEquals (1 , server2 .getRequestCount ());
200+ // TODO: verify ribbon stats match
201+ // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat())
202+ }
203+
101204 /*
102205 This test-case replicates a bug that occurs when using RibbonRequest with a query string.
103206
0 commit comments