diff --git a/CHANGELOG.md b/CHANGELOG.md index ec2ae5e3..f7ea3b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.4.0 +------------------ + +* `WebServiceClient.Builder` now has an `httpClient()` method to allow + passing in a custom `HttpClient`. + 4.3.1 (2025-05-28) ------------------ diff --git a/src/main/java/com/maxmind/geoip2/WebServiceClient.java b/src/main/java/com/maxmind/geoip2/WebServiceClient.java index 2fbad5a9..c612b917 100644 --- a/src/main/java/com/maxmind/geoip2/WebServiceClient.java +++ b/src/main/java/com/maxmind/geoip2/WebServiceClient.java @@ -140,10 +140,15 @@ private WebServiceClient(Builder builder) { .build(); requestTimeout = builder.requestTimeout; - httpClient = HttpClient.newBuilder() - .connectTimeout(builder.connectTimeout) - .proxy(builder.proxy) - .build(); + + if (builder.httpClient != null) { + httpClient = builder.httpClient; + } else { + httpClient = HttpClient.newBuilder() + .connectTimeout(builder.connectTimeout) + .proxy(builder.proxy) + .build(); + } } /** @@ -171,11 +176,12 @@ public static final class Builder { int port = 443; boolean useHttps = true; - Duration connectTimeout = Duration.ofSeconds(3); + Duration connectTimeout = null; Duration requestTimeout = Duration.ofSeconds(20); List locales = Collections.singletonList("en"); - private ProxySelector proxy = ProxySelector.getDefault(); + private ProxySelector proxy = null; + private HttpClient httpClient = null; /** * @param accountId Your MaxMind account ID. @@ -296,11 +302,43 @@ public Builder proxy(ProxySelector val) { return this; } + /** + * @param val the custom HttpClient to use for requests. When providing a + * custom HttpClient, you cannot also set connectTimeout or proxy + * parameters as these should be configured on the provided client. + * @return Builder object + */ + public Builder httpClient(HttpClient val) { + this.httpClient = val; + return this; + } + /** * @return an instance of {@code WebServiceClient} created from the * fields set on this builder. + * @throws IllegalArgumentException if httpClient is provided along with + * connectTimeout or proxy settings */ public WebServiceClient build() { + if (httpClient != null) { + if (connectTimeout != null) { + throw new IllegalArgumentException( + "Cannot set both httpClient and connectTimeout. " + + "Configure timeout on the provided HttpClient instead."); + } + if (proxy != null) { + throw new IllegalArgumentException( + "Cannot set both httpClient and proxy. " + + "Configure proxy on the provided HttpClient instead."); + } + } else { + if (connectTimeout == null) { + connectTimeout = Duration.ofSeconds(3); + } + if (proxy == null) { + proxy = ProxySelector.getDefault(); + } + } return new WebServiceClient(this); } } diff --git a/src/test/java/com/maxmind/geoip2/WebServiceClientTest.java b/src/test/java/com/maxmind/geoip2/WebServiceClientTest.java index d2ee3075..9d79a37b 100644 --- a/src/test/java/com/maxmind/geoip2/WebServiceClientTest.java +++ b/src/test/java/com/maxmind/geoip2/WebServiceClientTest.java @@ -36,7 +36,11 @@ import com.maxmind.geoip2.record.Traits; import java.io.UnsupportedEncodingException; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.http.HttpClient; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.List; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; @@ -407,4 +411,53 @@ private WebServiceClient createClient(String service, String ip, int status, Str .disableHttps() .build(); } + + @Test + public void testHttpClientWithConnectTimeoutThrowsException() { + HttpClient customClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) + .build(); + + WebServiceClient.Builder builder = new WebServiceClient.Builder(6, "0123456789") + .httpClient(customClient) + .connectTimeout(Duration.ofSeconds(5)); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, builder::build); + assertEquals("Cannot set both httpClient and connectTimeout. Configure timeout on the provided HttpClient instead.", + ex.getMessage()); + } + + @Test + public void testHttpClientWithProxyThrowsException() { + HttpClient customClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) + .build(); + + ProxySelector proxySelector = ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)); + WebServiceClient.Builder builder = new WebServiceClient.Builder(6, "0123456789") + .httpClient(customClient) + .proxy(proxySelector); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, builder::build); + assertEquals("Cannot set both httpClient and proxy. Configure proxy on the provided HttpClient instead.", + ex.getMessage()); + } + + @Test + public void testHttpClientWithDefaultSettingsDoesNotThrow() throws Exception { + HttpClient customClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) + .build(); + + // Should not throw because we're not setting connectTimeout or proxy + WebServiceClient client = new WebServiceClient.Builder(6, "0123456789") + .host("localhost") + .port(8080) + .disableHttps() + .httpClient(customClient) + .build(); + + assertNotNull(client); + } + }