|
16 | 16 | */
|
17 | 17 | package com.optimizely.ab;
|
18 | 18 |
|
19 |
| -import io.javalin.Javalin; |
20 |
| -import org.apache.http.NoHttpResponseException; |
| 19 | +import org.apache.http.HttpException; |
21 | 20 | import org.apache.http.client.HttpRequestRetryHandler;
|
22 | 21 | import org.apache.http.client.ResponseHandler;
|
| 22 | +import org.apache.http.client.methods.CloseableHttpResponse; |
23 | 23 | import org.apache.http.client.methods.HttpGet;
|
24 | 24 | import org.apache.http.client.methods.HttpUriRequest;
|
25 | 25 | import org.apache.http.client.methods.RequestBuilder;
|
26 | 26 | import org.apache.http.conn.HttpHostConnectException;
|
27 | 27 | import org.apache.http.impl.client.CloseableHttpClient;
|
28 | 28 | import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
|
| 29 | +import org.apache.http.protocol.HttpContext; |
29 | 30 | import org.junit.*;
|
| 31 | +import org.mockserver.integration.ClientAndServer; |
| 32 | +import org.mockserver.model.ConnectionOptions; |
| 33 | +import org.mockserver.model.HttpError; |
| 34 | +import org.mockserver.model.HttpRequest; |
| 35 | +import org.mockserver.model.HttpResponse; |
30 | 36 |
|
31 | 37 | import java.io.IOException;
|
| 38 | +import java.util.concurrent.ExecutionException; |
32 | 39 | import java.util.concurrent.TimeUnit;
|
33 |
| -import java.util.concurrent.atomic.AtomicInteger; |
34 | 40 |
|
35 | 41 | import static com.optimizely.ab.OptimizelyHttpClient.builder;
|
36 | 42 | import static java.util.concurrent.TimeUnit.*;
|
37 | 43 | import static org.junit.Assert.*;
|
38 |
| -import static org.mockito.Matchers.any; |
39 | 44 | import static org.mockito.Mockito.*;
|
| 45 | +import static org.mockserver.model.HttpForward.forward; |
| 46 | +import static org.mockserver.model.HttpRequest.request; |
| 47 | +import static org.mockserver.model.HttpResponse.*; |
| 48 | +import static org.mockserver.model.HttpResponse.response; |
| 49 | +import static org.mockserver.verify.VerificationTimes.exactly; |
40 | 50 |
|
41 | 51 | public class OptimizelyHttpClientTest {
|
42 | 52 | @Before
|
@@ -111,74 +121,72 @@ public void testExecute() throws IOException {
|
111 | 121 | }
|
112 | 122 |
|
113 | 123 | @Test
|
114 |
| - public void testRetries() throws IOException { |
115 |
| - // Javalin intercepts before proxy, so host and port should be set correct here |
116 |
| - String host = "http://localhost"; |
117 |
| - int port = 8000; |
118 |
| - Javalin app = Javalin.create().start(port); |
119 |
| - int maxFailures = 2; |
120 |
| - |
121 |
| - AtomicInteger callTimes = new AtomicInteger(); |
122 |
| - app.get("/", ctx -> { |
123 |
| - callTimes.addAndGet(1); |
124 |
| - int count = callTimes.get(); |
125 |
| - if (count < maxFailures) { |
126 |
| - throw new NoHttpResponseException("TESTING CONNECTION FAILURE"); |
127 |
| - } else { |
128 |
| - ctx.status(200).result("Success"); |
129 |
| - } |
130 |
| - }); |
| 124 | + public void testRetriesWithCustomRetryHandler() throws IOException { |
131 | 125 |
|
132 |
| - OptimizelyHttpClient optimizelyHttpClient = spy(OptimizelyHttpClient.builder().build()); |
133 |
| - optimizelyHttpClient.execute(new HttpGet(host + ":" + String.valueOf(port))); |
134 |
| - assertEquals(3, callTimes.get()); |
135 |
| - } |
| 126 | + // [NOTE] Request retries are all handled inside HttpClient. Not easy for unit test. |
| 127 | + // - "DefaultHttpRetryHandler" in HttpClient retries only with special types of Exceptions |
| 128 | + // like "NoHttpResponseException", etc. |
| 129 | + // Other exceptions (SocketTimeout, ProtocolException, etc.) all ignored. |
| 130 | + // - Not easy to force the specific exception type in the low-level. |
| 131 | + // - This test just validates custom retry handler injected ok by validating the number of retries. |
136 | 132 |
|
137 |
| - @Test |
138 |
| - public void testRetriesWithCustom() throws IOException { |
139 |
| - // Javalin intercepts before proxy, so host and port should be set correct here |
140 |
| - String host = "http://localhost"; |
141 |
| - int port = 8000; |
142 |
| - Javalin app = Javalin.create().start(port); |
143 |
| - int maxFailures = 2; |
144 |
| - |
145 |
| - AtomicInteger callTimes = new AtomicInteger(); |
146 |
| - app.get("/", ctx -> { |
147 |
| - callTimes.addAndGet(1); |
148 |
| - int count = callTimes.get(); |
149 |
| - if (count < maxFailures) { |
150 |
| -// throw new NoHttpResponseException("TESTING CONNECTION FAILURE"); |
151 |
| - throw new IOException("TESTING CONNECTION FAILURE"); |
152 |
| - |
153 |
| -// ctx.status(500).result("TESTING Server Error"); |
154 |
| - } else { |
155 |
| - ctx.status(200).result("Success"); |
| 133 | + class CustomRetryHandler implements HttpRequestRetryHandler { |
| 134 | + private final int maxRetries; |
| 135 | + |
| 136 | + public CustomRetryHandler(int maxRetries) { |
| 137 | + this.maxRetries = maxRetries; |
156 | 138 | }
|
157 |
| - }); |
158 | 139 |
|
159 |
| - HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(3, true); |
160 |
| - OptimizelyHttpClient optimizelyHttpClient = spy(OptimizelyHttpClient.builder().withRetryHandler(retryHandler).build()); |
| 140 | + @Override |
| 141 | + public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { |
| 142 | + // override to retry for any type of exceptions |
| 143 | + return executionCount < maxRetries; |
| 144 | + } |
| 145 | + } |
161 | 146 |
|
162 |
| - optimizelyHttpClient.execute(new HttpGet(host + ":" + String.valueOf(port))); |
163 |
| - assertEquals(3, callTimes.get()); |
164 |
| - } |
| 147 | + int port = 9999; |
| 148 | + ClientAndServer mockServer; |
| 149 | + int retryCount; |
165 | 150 |
|
166 |
| -// |
167 |
| -// @Test |
168 |
| -// public void testRetriesWithCustom() throws IOException { |
169 |
| -// CloseableHttpClient httpClient = mock(CloseableHttpClient.class); |
170 |
| -// CloseableHttpResponse response = mock(CloseableHttpResponse.class); |
171 |
| -// |
172 |
| -// HttpRequestRetryHandler mockRetryHandler = spy(new DefaultHttpRequestRetryHandler(3, true)); |
173 |
| -// when(mockRetryHandler.retryRequest(any(), any(), any())).thenReturn(true); |
174 |
| -// |
175 |
| -// OptimizelyHttpClient optimizelyHttpClient = OptimizelyHttpClient.builder().withRetryHandler(mockRetryHandler).build(); |
176 |
| -// try { |
177 |
| -// optimizelyHttpClient.execute(new HttpGet("https://example.com")); |
178 |
| -// } catch(Exception e) { |
179 |
| -// assert(e instanceof IOException); |
180 |
| -// } |
181 |
| -// verify(mockRetryHandler, times(3)).retryRequest(any(), any(), any()); |
182 |
| -// } |
| 151 | + // default httpclient (retries enabled by default, but no retry for timeout connection) |
183 | 152 |
|
| 153 | + mockServer = ClientAndServer.startClientAndServer(port); |
| 154 | + mockServer |
| 155 | + .when(request().withMethod("GET").withPath("/")) |
| 156 | + .error(HttpError.error()); |
| 157 | + |
| 158 | + OptimizelyHttpClient clientDefault = OptimizelyHttpClient.builder() |
| 159 | + .setTimeoutMillis(100) |
| 160 | + .build(); |
| 161 | + |
| 162 | + try { |
| 163 | + clientDefault.execute(new HttpGet("http://localhost:" + port)); |
| 164 | + fail(); |
| 165 | + } catch (Exception e) { |
| 166 | + retryCount = mockServer.retrieveRecordedRequests(request()).length; |
| 167 | + assertEquals(1, retryCount); |
| 168 | + } |
| 169 | + mockServer.stop(); |
| 170 | + |
| 171 | + // httpclient with custom retry handler (5 times retries for any request) |
| 172 | + |
| 173 | + mockServer = ClientAndServer.startClientAndServer(port); |
| 174 | + mockServer |
| 175 | + .when(request().withMethod("GET").withPath("/")) |
| 176 | + .error(HttpError.error()); |
| 177 | + |
| 178 | + OptimizelyHttpClient clientWithRetries = OptimizelyHttpClient.builder() |
| 179 | + .withRetryHandler(new CustomRetryHandler(5)) |
| 180 | + .setTimeoutMillis(100) |
| 181 | + .build(); |
| 182 | + |
| 183 | + try { |
| 184 | + clientWithRetries.execute(new HttpGet("http://localhost:" + port)); |
| 185 | + fail(); |
| 186 | + } catch (Exception e) { |
| 187 | + retryCount = mockServer.retrieveRecordedRequests(request()).length; |
| 188 | + assertEquals(5, retryCount); |
| 189 | + } |
| 190 | + mockServer.stop(); |
| 191 | + } |
184 | 192 | }
|
0 commit comments