|
9 | 9 | import com.clickhouse.client.ClickHouseResponse; |
10 | 10 | import com.clickhouse.client.ClickHouseSocketFactory; |
11 | 11 | import com.clickhouse.client.config.ClickHouseClientOption; |
| 12 | +import com.clickhouse.client.config.ClickHouseDefaults; |
| 13 | +import com.clickhouse.client.config.ClickHouseProxyType; |
12 | 14 | import com.clickhouse.client.http.config.ClickHouseHttpOption; |
13 | 15 | import com.clickhouse.client.http.config.HttpConnectionProvider; |
14 | 16 | import com.clickhouse.config.ClickHouseOption; |
15 | 17 | import com.clickhouse.data.ClickHouseUtils; |
16 | | - |
17 | | -import java.io.IOException; |
18 | | -import java.io.Serializable; |
19 | | -import java.net.ConnectException; |
20 | | -import java.util.Collections; |
21 | | -import java.util.HashMap; |
22 | | -import java.util.List; |
23 | | -import java.util.Map; |
24 | | -import java.util.TreeMap; |
25 | | -import java.util.concurrent.atomic.AtomicBoolean; |
26 | | - |
27 | 18 | import com.github.tomakehurst.wiremock.WireMockServer; |
28 | 19 | import com.github.tomakehurst.wiremock.client.WireMock; |
| 20 | +import com.github.tomakehurst.wiremock.common.Slf4jNotifier; |
| 21 | +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; |
29 | 22 | import com.github.tomakehurst.wiremock.http.Fault; |
| 23 | +import com.github.tomakehurst.wiremock.http.trafficlistener.WiremockNetworkTrafficListener; |
30 | 24 | import com.github.tomakehurst.wiremock.stubbing.Scenario; |
31 | 25 | import com.github.tomakehurst.wiremock.stubbing.StubMapping; |
32 | 26 | import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; |
33 | 27 | import org.apache.hc.core5.http.HttpStatus; |
| 28 | +import org.apache.hc.core5.net.URIBuilder; |
34 | 29 | import org.testng.Assert; |
35 | 30 | import org.testng.annotations.DataProvider; |
36 | 31 | import org.testng.annotations.Ignore; |
37 | 32 | import org.testng.annotations.Test; |
38 | 33 |
|
| 34 | +import java.io.IOException; |
| 35 | +import java.io.Serializable; |
| 36 | +import java.net.ConnectException; |
| 37 | +import java.net.Socket; |
| 38 | +import java.nio.ByteBuffer; |
| 39 | +import java.util.Collections; |
| 40 | +import java.util.HashMap; |
| 41 | +import java.util.List; |
| 42 | +import java.util.Map; |
| 43 | +import java.util.Random; |
| 44 | +import java.util.TreeMap; |
| 45 | +import java.util.concurrent.atomic.AtomicBoolean; |
| 46 | +import java.util.concurrent.atomic.AtomicInteger; |
| 47 | + |
39 | 48 | public class ApacheHttpConnectionImplTest extends ClickHouseHttpClientTest { |
40 | 49 | public static class CustomSocketFactory implements ClickHouseSocketFactory { |
41 | 50 | private static final AtomicBoolean created = new AtomicBoolean(); |
@@ -267,4 +276,102 @@ public void testNoHttpResponseExceptionWithValidation(long validationTimeout) { |
267 | 276 | public static Object[] validationTimeoutProvider() { |
268 | 277 | return new Long[] {-1L , 100L }; |
269 | 278 | } |
| 279 | + |
| 280 | + @Test(groups = {"integration"},dataProvider = "testConnectionTTLProvider") |
| 281 | + @SuppressWarnings("java:S2925") |
| 282 | + public void testConnectionTTL(Map<ClickHouseOption, Serializable> options, int openSockets) throws Exception { |
| 283 | + if (isCloud()) { |
| 284 | + // skip for cloud because wiremock proxy need extra configuration. TODO: need to fix it |
| 285 | + return; |
| 286 | + } |
| 287 | + ClickHouseNode server = getServer(ClickHouseProtocol.HTTP); |
| 288 | + |
| 289 | + int proxyPort = new Random().nextInt(1000) + 10000; |
| 290 | + System.out.println("proxyPort: " + proxyPort); |
| 291 | + ConnectionCounterListener connectionCounter = new ConnectionCounterListener(); |
| 292 | + WireMockServer proxy = new WireMockServer(WireMockConfiguration |
| 293 | + .options().port(proxyPort) |
| 294 | + .networkTrafficListener(connectionCounter) |
| 295 | + .notifier(new Slf4jNotifier(true))); |
| 296 | + proxy.start(); |
| 297 | + URIBuilder targetURI = new URIBuilder(server.getBaseUri()) |
| 298 | + .setPath(""); |
| 299 | + proxy.addStubMapping(WireMock.post(WireMock.anyUrl()) |
| 300 | + .willReturn(WireMock.aResponse().proxiedFrom(targetURI.build().toString())).build()); |
| 301 | + |
| 302 | + Map<ClickHouseOption, Serializable> baseOptions = new HashMap<>(); |
| 303 | + baseOptions.put(ClickHouseClientOption.PROXY_PORT, proxyPort); |
| 304 | + baseOptions.put(ClickHouseClientOption.PROXY_HOST, "localhost"); |
| 305 | + baseOptions.put(ClickHouseClientOption.PROXY_TYPE, ClickHouseProxyType.HTTP); |
| 306 | + baseOptions.put(ClickHouseDefaults.PASSWORD, getPassword()); |
| 307 | + baseOptions.put(ClickHouseDefaults.USER, "default"); |
| 308 | + baseOptions.putAll(options); |
| 309 | + |
| 310 | + ClickHouseConfig config = new ClickHouseConfig(baseOptions); |
| 311 | + try (ClickHouseClient client = ClickHouseClient.builder().config(config).build()) { |
| 312 | + try (ClickHouseResponse resp = client.read(server).query("select 1").executeAndWait()) { |
| 313 | + Assert.assertEquals(resp.firstRecord().getValue(0).asString(), "1"); |
| 314 | + } |
| 315 | + try { |
| 316 | + Thread.sleep(1000L); |
| 317 | + } catch (InterruptedException e) { |
| 318 | + Assert.fail("Unexpected exception", e); |
| 319 | + } |
| 320 | + |
| 321 | + try (ClickHouseResponse resp = client.read(server).query("select 1").executeAndWait()) { |
| 322 | + Assert.assertEquals(resp.firstRecord().getValue(0).asString(), "1"); |
| 323 | + } |
| 324 | + } catch (Exception e) { |
| 325 | + Assert.fail("Unexpected exception", e); |
| 326 | + } finally { |
| 327 | + Assert.assertEquals(connectionCounter.opened.get(), openSockets); |
| 328 | + proxy.stop(); |
| 329 | + } |
| 330 | + } |
| 331 | + |
| 332 | + @DataProvider(name = "testConnectionTTLProvider") |
| 333 | + public static Object[][] testConnectionTTLProvider() { |
| 334 | + HashMap<ClickHouseOption, Serializable> disabledKeepAlive = new HashMap<>(); |
| 335 | + disabledKeepAlive.put(ClickHouseHttpOption.KEEP_ALIVE_TIMEOUT, 1000L); |
| 336 | + disabledKeepAlive.put(ClickHouseHttpOption.KEEP_ALIVE, false); |
| 337 | + HashMap<ClickHouseOption, Serializable> fifoOption = new HashMap<>(); |
| 338 | + fifoOption.put(ClickHouseClientOption.CONNECTION_TTL, 1000L); |
| 339 | + fifoOption.put(ClickHouseHttpOption.CONNECTION_REUSE_STRATEGY, "FIFO"); |
| 340 | + return new Object[][] { |
| 341 | + { Collections.singletonMap(ClickHouseClientOption.CONNECTION_TTL, 1000L), 2 }, |
| 342 | + { Collections.singletonMap(ClickHouseClientOption.CONNECTION_TTL, 2000L), 1 }, |
| 343 | + { Collections.singletonMap(ClickHouseHttpOption.KEEP_ALIVE_TIMEOUT, 2000L), 1 }, |
| 344 | + { Collections.singletonMap(ClickHouseHttpOption.KEEP_ALIVE_TIMEOUT, 500L), 2 }, |
| 345 | + { disabledKeepAlive, 2 }, |
| 346 | + { fifoOption, 2 } |
| 347 | + }; |
| 348 | + } |
| 349 | + |
| 350 | + private static class ConnectionCounterListener implements WiremockNetworkTrafficListener { |
| 351 | + |
| 352 | + private AtomicInteger opened = new AtomicInteger(0); |
| 353 | + private AtomicInteger closed = new AtomicInteger(0); |
| 354 | + |
| 355 | + @Override |
| 356 | + public void opened(Socket socket) { |
| 357 | + opened.incrementAndGet(); |
| 358 | + System.out.println("Opened: " + socket); |
| 359 | + } |
| 360 | + |
| 361 | + @Override |
| 362 | + public void incoming(Socket socket, ByteBuffer bytes) { |
| 363 | + // ignore |
| 364 | + } |
| 365 | + |
| 366 | + @Override |
| 367 | + public void outgoing(Socket socket, ByteBuffer bytes) { |
| 368 | + // ignore |
| 369 | + } |
| 370 | + |
| 371 | + @Override |
| 372 | + public void closed(Socket socket) { |
| 373 | + closed.incrementAndGet(); |
| 374 | + System.out.println("Closed: " + socket); |
| 375 | + } |
| 376 | + } |
270 | 377 | } |
0 commit comments