Skip to content

Commit 82ee889

Browse files
committed
Add httpProtocol method to WebServiceClient.Builder
1 parent 4f6b400 commit 82ee889

File tree

5 files changed

+250
-11
lines changed

5 files changed

+250
-11
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
CHANGELOG
22
=========
33

4+
4.3.2 (2025-08-22)
5+
------------------
6+
7+
* Added `httpVersion()` method on `WebServiceClient.Builder`
8+
which allows configuring HTTP protocol version (HTTP/1.1 or HTTP/2) for web
9+
service requests.
10+
* Updated README.md with comprehensive HTTP protocol configuration documentation.
11+
* Added detailed information about HTTP version settings, proxy configuration,
12+
timeout settings, authentication, and connection management.
13+
* Improved documentation for developers using the web service client.
14+
415
4.3.1 (2025-05-28)
516
------------------
617

README.md

Lines changed: 183 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ To do this, add the dependency to your pom.xml:
1717
<dependency>
1818
<groupId>com.maxmind.geoip2</groupId>
1919
<artifactId>geoip2</artifactId>
20-
<version>4.3.1</version>
20+
<version>4.3.2</version>
2121
</dependency>
2222
```
2323

@@ -30,7 +30,7 @@ repositories {
3030
mavenCentral()
3131
}
3232
dependencies {
33-
compile 'com.maxmind.geoip2:geoip2:4.3.1'
33+
compile 'com.maxmind.geoip2:geoip2:4.3.2'
3434
}
3535
```
3636

@@ -72,6 +72,187 @@ are not created for each request.
7272
See the [API documentation](https://maxmind.github.io/GeoIP2-java/) for
7373
more details.
7474

75+
## HTTP Protocol Configuration ##
76+
77+
The `WebServiceClient` provides extensive HTTP protocol configuration options
78+
through the `WebServiceClient.Builder`:
79+
80+
### HTTP Version ###
81+
82+
You can specify the HTTP protocol version to use:
83+
84+
```java
85+
import java.net.http.HttpClient;
86+
87+
// Force HTTP/1.1
88+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
89+
.httpVersion(HttpClient.Version.HTTP_1_1)
90+
.build();
91+
92+
// Force HTTP/2
93+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
94+
.httpVersion(HttpClient.Version.HTTP_2)
95+
.build();
96+
97+
// Default behavior: prefer HTTP/2 but negotiate down to HTTP/1.1 if needed
98+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
99+
.build();
100+
```
101+
102+
### HTTPS/HTTP Protocol ###
103+
104+
By default, the client uses HTTPS. You can disable HTTPS for testing or proxy scenarios:
105+
106+
```java
107+
// Disable HTTPS (use HTTP instead)
108+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
109+
.disableHttps()
110+
.build();
111+
```
112+
113+
**Note:** The minFraud Score and Insights web services require HTTPS.
114+
115+
### Timeouts ###
116+
117+
Configure connection and request timeouts:
118+
119+
```java
120+
import java.time.Duration;
121+
122+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
123+
.connectTimeout(Duration.ofSeconds(5)) // Connection timeout (default: 3 seconds)
124+
.requestTimeout(Duration.ofSeconds(30)) // Request timeout (default: 20 seconds)
125+
.build();
126+
```
127+
128+
### Proxy Configuration ###
129+
130+
Configure HTTP proxy settings:
131+
132+
```java
133+
import java.net.InetSocketAddress;
134+
import java.net.ProxySelector;
135+
136+
// Using ProxySelector (recommended)
137+
ProxySelector proxy = ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080));
138+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
139+
.proxy(proxy)
140+
.build();
141+
142+
// Using system default proxy
143+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
144+
.proxy(ProxySelector.getDefault())
145+
.build();
146+
```
147+
148+
### Custom Host and Port ###
149+
150+
Configure custom host and port:
151+
152+
```java
153+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
154+
.host("custom.geoip.server.com")
155+
.port(8443)
156+
.build();
157+
```
158+
159+
### Complete HTTP Configuration Example ###
160+
161+
```java
162+
import java.net.InetSocketAddress;
163+
import java.net.ProxySelector;
164+
import java.net.http.HttpClient;
165+
import java.time.Duration;
166+
import java.util.Arrays;
167+
168+
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
169+
.host("geoip.maxmind.com")
170+
.port(443)
171+
.httpVersion(HttpClient.Version.HTTP_2)
172+
.connectTimeout(Duration.ofSeconds(5))
173+
.requestTimeout(Duration.ofSeconds(30))
174+
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
175+
.locales(Arrays.asList("en", "fr", "es"))
176+
.build();
177+
```
178+
179+
### HTTP Headers ###
180+
181+
The client automatically sets the following HTTP headers:
182+
- `Accept: application/json`
183+
- `Authorization: Basic <base64-encoded-credentials>`
184+
- `User-Agent: GeoIP2/<version> (Java/<java-version>)`
185+
186+
### HTTP Error Handling ###
187+
188+
The client handles various HTTP status codes:
189+
- **200**: Success - response is parsed and returned
190+
- **4xx**: Client errors - throws specific exceptions like `AddressNotFoundException`, `AuthenticationException`, etc.
191+
- **5xx**: Server errors - throws `HttpException`
192+
- **Other status codes**: Throws `HttpException`
193+
194+
HTTP transport errors (network issues, timeouts, etc.) are thrown as `HttpException`, which extends `IOException`.
195+
196+
### HTTP Client Implementation ###
197+
198+
The GeoIP2 Java library uses Java's built-in `java.net.http.HttpClient` (available since Java 11) for HTTP communication. This provides:
199+
200+
- **Connection pooling**: Connections are automatically reused across requests
201+
- **HTTP/2 support**: Modern HTTP/2 protocol with fallback to HTTP/1.1
202+
- **Asynchronous capabilities**: Though the GeoIP2 client uses synchronous calls
203+
- **Built-in proxy support**: System proxy settings are respected by default
204+
- **TLS/SSL support**: Secure HTTPS connections with modern cipher suites
205+
206+
### Authentication ###
207+
208+
The client uses HTTP Basic Authentication with your MaxMind account credentials:
209+
210+
- Credentials are Base64-encoded and sent in the `Authorization` header
211+
- Authentication is sent with every request (no challenge-response)
212+
- Credentials are never logged or exposed in error messages
213+
214+
### Connection Management ###
215+
216+
- **Thread-safe**: The `WebServiceClient` can be safely shared across multiple threads
217+
- **Connection reuse**: The underlying HTTP client maintains a connection pool
218+
- **Resource cleanup**: The client automatically manages connection lifecycle
219+
- **No explicit cleanup needed**: The `close()` method is deprecated and no longer required
220+
221+
### HTTP API Endpoints ###
222+
223+
The client makes HTTP GET requests to the following endpoint pattern:
224+
```
225+
https://geoip.maxmind.com/geoip/v2.1/{service}/{ip_address}
226+
```
227+
228+
Where:
229+
- `{service}` is one of: `country`, `city`, `insights`
230+
- `{ip_address}` is the IP address to look up, or `me` for the client's IP
231+
232+
Examples:
233+
- `https://geoip.maxmind.com/geoip/v2.1/country/128.101.101.101`
234+
- `https://geoip.maxmind.com/geoip/v2.1/city/me`
235+
- `https://geolite.info/geoip/v2.1/country/192.168.1.1` (GeoLite2)
236+
- `https://sandbox.maxmind.com/geoip/v2.1/insights/8.8.8.8` (Sandbox)
237+
238+
### Request Format ###
239+
240+
All requests are HTTP GET requests with:
241+
- **Method**: GET
242+
- **Content-Type**: Not applicable (no request body)
243+
- **Accept**: `application/json`
244+
- **Authorization**: `Basic <base64-credentials>`
245+
- **User-Agent**: `GeoIP2/<version> (Java/<java-version>)`
246+
247+
### Response Format ###
248+
249+
All successful responses return JSON with HTTP 200 status. The JSON structure varies by service but typically includes:
250+
- IP address information
251+
- Geographic location data (country, city, subdivisions)
252+
- ISP and organization data
253+
- Confidence scores (for paid services)
254+
- Additional metadata
255+
75256
## Web Service Example ##
76257

77258
### Country Service ###

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>com.maxmind.geoip2</groupId>
55
<artifactId>geoip2</artifactId>
6-
<version>4.3.1</version>
6+
<version>4.3.2</version>
77
<packaging>jar</packaging>
88
<name>MaxMind GeoIP2 API</name>
99
<description>GeoIP2 webservice client and database reader</description>

src/main/java/com/maxmind/geoip2/WebServiceClient.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@
6666
* the {@code host} method on the builder to {@code geolite.info}. To use the
6767
* Sandbox GeoIP2 web services instead of the production GeoIP2 web services,
6868
* set the {@code host} method on the builder to {@code sandbox.maxmind.com}.
69-
* You may also set a {@code timeout} or set the {@code locales} fallback order
70-
* using the methods on the {@code Builder}. After you have created the {@code
71-
* WebServiceClient}, you may then call the method corresponding to a specific
72-
* service, passing it the IP address you want to look up.
69+
* You may also set a {@code timeout}, set the {@code locales} fallback order,
70+
* or specify the HTTP protocol version using the methods on the {@code Builder}.
71+
* After you have created the {@code WebServiceClient}, you may then call the
72+
* method corresponding to a specific service, passing it the IP address you want
73+
* to look up.
7374
* </p>
7475
* <p>
7576
* If the request succeeds, the method call will return a model class for the
@@ -140,10 +141,13 @@ private WebServiceClient(Builder builder) {
140141
.build();
141142

142143
requestTimeout = builder.requestTimeout;
143-
httpClient = HttpClient.newBuilder()
144+
HttpClient.Builder httpClientBuilder = HttpClient.newBuilder()
144145
.connectTimeout(builder.connectTimeout)
145-
.proxy(builder.proxy)
146-
.build();
146+
.proxy(builder.proxy);
147+
if (builder.httpVersion != null) {
148+
httpClientBuilder.version(builder.httpVersion);
149+
}
150+
httpClient = httpClientBuilder.build();
147151
}
148152

149153
/**
@@ -157,7 +161,7 @@ private WebServiceClient(Builder builder) {
157161
* </p>
158162
* <p>
159163
* {@code WebServiceClient client = new WebServiceClient.Builder(12,"licensekey")
160-
* .host("geoip.maxmind.com").build();}
164+
* .host("geoip.maxmind.com").httpVersion(HttpClient.Version.HTTP_1_1).build();}
161165
* </p>
162166
* <p>
163167
* Only the values set in the {@code Builder} constructor are required.
@@ -170,6 +174,7 @@ public static final class Builder {
170174
String host = "geoip.maxmind.com";
171175
int port = 443;
172176
boolean useHttps = true;
177+
HttpClient.Version httpVersion;
173178

174179
Duration connectTimeout = Duration.ofSeconds(3);
175180
Duration requestTimeout = Duration.ofSeconds(20);
@@ -296,6 +301,18 @@ public Builder proxy(ProxySelector val) {
296301
return this;
297302
}
298303

304+
/**
305+
* @param val the HTTP protocol version to use. If not set, the HttpClient
306+
* will prefer HTTP/2 but will negotiate down to HTTP/1.1 if needed.
307+
* Use HttpClient.Version.HTTP_1_1 to force HTTP/1.1 or
308+
* HttpClient.Version.HTTP_2 to force HTTP/2.
309+
* @return Builder object
310+
*/
311+
public Builder httpVersion(HttpClient.Version val) {
312+
this.httpVersion = val;
313+
return this;
314+
}
315+
299316
/**
300317
* @return an instance of {@code WebServiceClient} created from the
301318
* fields set on this builder.

src/test/java/com/maxmind/geoip2/WebServiceClientTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.maxmind.geoip2.record.Traits;
3737
import java.io.UnsupportedEncodingException;
3838
import java.net.InetAddress;
39+
import java.net.http.HttpClient;
3940
import java.nio.charset.StandardCharsets;
4041
import java.util.List;
4142
import org.hamcrest.CoreMatchers;
@@ -352,6 +353,35 @@ public void test500() throws Exception {
352353
assertThat(ex.getMessage(), startsWith("Received a server error (500)"));
353354
}
354355

356+
@Test
357+
public void testHttpVersionConfiguration() throws Exception {
358+
// Test that HTTP version can be configured without errors
359+
WebServiceClient clientHttp11 = new WebServiceClient.Builder(6, "0123456789")
360+
.host("localhost")
361+
.port(wireMock.getPort())
362+
.disableHttps()
363+
.httpVersion(HttpClient.Version.HTTP_1_1)
364+
.build();
365+
366+
WebServiceClient clientHttp2 = new WebServiceClient.Builder(6, "0123456789")
367+
.host("localhost")
368+
.port(wireMock.getPort())
369+
.disableHttps()
370+
.httpVersion(HttpClient.Version.HTTP_2)
371+
.build();
372+
373+
WebServiceClient clientDefault = new WebServiceClient.Builder(6, "0123456789")
374+
.host("localhost")
375+
.port(wireMock.getPort())
376+
.disableHttps()
377+
.build();
378+
379+
// Verify that clients can be created successfully
380+
assertNotNull(clientHttp11);
381+
assertNotNull(clientHttp2);
382+
assertNotNull(clientDefault);
383+
}
384+
355385
private void createInsightsError(String ip, int status, String contentType,
356386
String responseContent) throws Exception {
357387
WebServiceClient client = createClient(

0 commit comments

Comments
 (0)