Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
CHANGELOG
=========

4.3.2 (2025-08-22)
------------------

* Added `httpVersion()` method on `WebServiceClient.Builder`
which allows configuring HTTP protocol version (HTTP/1.1 or HTTP/2) for web
service requests.
* Updated README.md with comprehensive HTTP protocol configuration documentation.
* Added detailed information about HTTP version settings, proxy configuration,
timeout settings, authentication, and connection management.
* Improved documentation for developers using the web service client.

4.3.1 (2025-05-28)
------------------

Expand Down
185 changes: 183 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ To do this, add the dependency to your pom.xml:
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>4.3.1</version>
<version>4.3.2</version>
</dependency>
```

Expand All @@ -30,7 +30,7 @@ repositories {
mavenCentral()
}
dependencies {
compile 'com.maxmind.geoip2:geoip2:4.3.1'
compile 'com.maxmind.geoip2:geoip2:4.3.2'
}
```

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

## HTTP Protocol Configuration ##

The `WebServiceClient` provides extensive HTTP protocol configuration options
through the `WebServiceClient.Builder`:

### HTTP Version ###

You can specify the HTTP protocol version to use:

```java
import java.net.http.HttpClient;

// Force HTTP/1.1
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.httpVersion(HttpClient.Version.HTTP_1_1)
.build();

// Force HTTP/2
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.httpVersion(HttpClient.Version.HTTP_2)
.build();

// Default behavior: prefer HTTP/2 but negotiate down to HTTP/1.1 if needed
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.build();
```

### HTTPS/HTTP Protocol ###

By default, the client uses HTTPS. You can disable HTTPS for testing or proxy scenarios:

```java
// Disable HTTPS (use HTTP instead)
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.disableHttps()
.build();
```

**Note:** The minFraud Score and Insights web services require HTTPS.

### Timeouts ###

Configure connection and request timeouts:

```java
import java.time.Duration;

WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.connectTimeout(Duration.ofSeconds(5)) // Connection timeout (default: 3 seconds)
.requestTimeout(Duration.ofSeconds(30)) // Request timeout (default: 20 seconds)
.build();
```

### Proxy Configuration ###

Configure HTTP proxy settings:

```java
import java.net.InetSocketAddress;
import java.net.ProxySelector;

// Using ProxySelector (recommended)
ProxySelector proxy = ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080));
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.proxy(proxy)
.build();

// Using system default proxy
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.proxy(ProxySelector.getDefault())
.build();
```

### Custom Host and Port ###

Configure custom host and port:

```java
WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.host("custom.geoip.server.com")
.port(8443)
.build();
```

### Complete HTTP Configuration Example ###

```java
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.Arrays;

WebServiceClient client = new WebServiceClient.Builder(42, "license_key")
.host("geoip.maxmind.com")
.port(443)
.httpVersion(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(5))
.requestTimeout(Duration.ofSeconds(30))
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
.locales(Arrays.asList("en", "fr", "es"))
.build();
```

### HTTP Headers ###

The client automatically sets the following HTTP headers:
- `Accept: application/json`
- `Authorization: Basic <base64-encoded-credentials>`
- `User-Agent: GeoIP2/<version> (Java/<java-version>)`

### HTTP Error Handling ###

The client handles various HTTP status codes:
- **200**: Success - response is parsed and returned
- **4xx**: Client errors - throws specific exceptions like `AddressNotFoundException`, `AuthenticationException`, etc.
- **5xx**: Server errors - throws `HttpException`
- **Other status codes**: Throws `HttpException`

HTTP transport errors (network issues, timeouts, etc.) are thrown as `HttpException`, which extends `IOException`.

### HTTP Client Implementation ###

The GeoIP2 Java library uses Java's built-in `java.net.http.HttpClient` (available since Java 11) for HTTP communication. This provides:

- **Connection pooling**: Connections are automatically reused across requests
- **HTTP/2 support**: Modern HTTP/2 protocol with fallback to HTTP/1.1
- **Asynchronous capabilities**: Though the GeoIP2 client uses synchronous calls
- **Built-in proxy support**: System proxy settings are respected by default
- **TLS/SSL support**: Secure HTTPS connections with modern cipher suites

### Authentication ###

The client uses HTTP Basic Authentication with your MaxMind account credentials:

- Credentials are Base64-encoded and sent in the `Authorization` header
- Authentication is sent with every request (no challenge-response)
- Credentials are never logged or exposed in error messages

### Connection Management ###

- **Thread-safe**: The `WebServiceClient` can be safely shared across multiple threads
- **Connection reuse**: The underlying HTTP client maintains a connection pool
- **Resource cleanup**: The client automatically manages connection lifecycle
- **No explicit cleanup needed**: The `close()` method is deprecated and no longer required

### HTTP API Endpoints ###

The client makes HTTP GET requests to the following endpoint pattern:
```
https://geoip.maxmind.com/geoip/v2.1/{service}/{ip_address}
```

Where:
- `{service}` is one of: `country`, `city`, `insights`
- `{ip_address}` is the IP address to look up, or `me` for the client's IP

Examples:
- `https://geoip.maxmind.com/geoip/v2.1/country/128.101.101.101`
- `https://geoip.maxmind.com/geoip/v2.1/city/me`
- `https://geolite.info/geoip/v2.1/country/192.168.1.1` (GeoLite2)
- `https://sandbox.maxmind.com/geoip/v2.1/insights/8.8.8.8` (Sandbox)

### Request Format ###

All requests are HTTP GET requests with:
- **Method**: GET
- **Content-Type**: Not applicable (no request body)
- **Accept**: `application/json`
- **Authorization**: `Basic <base64-credentials>`
- **User-Agent**: `GeoIP2/<version> (Java/<java-version>)`

### Response Format ###

All successful responses return JSON with HTTP 200 status. The JSON structure varies by service but typically includes:
- IP address information
- Geographic location data (country, city, subdivisions)
- ISP and organization data
- Confidence scores (for paid services)
- Additional metadata

## Web Service Example ##

### Country Service ###
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>4.3.1</version>
<version>4.3.2</version>
<packaging>jar</packaging>
<name>MaxMind GeoIP2 API</name>
<description>GeoIP2 webservice client and database reader</description>
Expand Down
33 changes: 25 additions & 8 deletions src/main/java/com/maxmind/geoip2/WebServiceClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@
* the {@code host} method on the builder to {@code geolite.info}. To use the
* Sandbox GeoIP2 web services instead of the production GeoIP2 web services,
* set the {@code host} method on the builder to {@code sandbox.maxmind.com}.
* You may also set a {@code timeout} or set the {@code locales} fallback order
* using the methods on the {@code Builder}. After you have created the {@code
* WebServiceClient}, you may then call the method corresponding to a specific
* service, passing it the IP address you want to look up.
* You may also set a {@code timeout}, set the {@code locales} fallback order,
* or specify the HTTP protocol version using the methods on the {@code Builder}.
* After you have created the {@code WebServiceClient}, you may then call the
* method corresponding to a specific service, passing it the IP address you want
* to look up.
* </p>
* <p>
* If the request succeeds, the method call will return a model class for the
Expand Down Expand Up @@ -140,10 +141,13 @@ private WebServiceClient(Builder builder) {
.build();

requestTimeout = builder.requestTimeout;
httpClient = HttpClient.newBuilder()
HttpClient.Builder httpClientBuilder = HttpClient.newBuilder()
.connectTimeout(builder.connectTimeout)
.proxy(builder.proxy)
.build();
.proxy(builder.proxy);
if (builder.httpVersion != null) {
httpClientBuilder.version(builder.httpVersion);
}
httpClient = httpClientBuilder.build();
}

/**
Expand All @@ -157,7 +161,7 @@ private WebServiceClient(Builder builder) {
* </p>
* <p>
* {@code WebServiceClient client = new WebServiceClient.Builder(12,"licensekey")
* .host("geoip.maxmind.com").build();}
* .host("geoip.maxmind.com").httpVersion(HttpClient.Version.HTTP_1_1).build();}
* </p>
* <p>
* Only the values set in the {@code Builder} constructor are required.
Expand All @@ -170,6 +174,7 @@ public static final class Builder {
String host = "geoip.maxmind.com";
int port = 443;
boolean useHttps = true;
HttpClient.Version httpVersion;

Duration connectTimeout = Duration.ofSeconds(3);
Duration requestTimeout = Duration.ofSeconds(20);
Expand Down Expand Up @@ -296,6 +301,18 @@ public Builder proxy(ProxySelector val) {
return this;
}

/**
* @param val the HTTP protocol version to use. If not set, the HttpClient
* will prefer HTTP/2 but will negotiate down to HTTP/1.1 if needed.
* Use HttpClient.Version.HTTP_1_1 to force HTTP/1.1 or
* HttpClient.Version.HTTP_2 to force HTTP/2.
* @return Builder object
*/
public Builder httpVersion(HttpClient.Version val) {
this.httpVersion = val;
return this;
}

/**
* @return an instance of {@code WebServiceClient} created from the
* fields set on this builder.
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/com/maxmind/geoip2/WebServiceClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.maxmind.geoip2.record.Traits;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.hamcrest.CoreMatchers;
Expand Down Expand Up @@ -352,6 +353,35 @@ public void test500() throws Exception {
assertThat(ex.getMessage(), startsWith("Received a server error (500)"));
}

@Test
public void testHttpVersionConfiguration() throws Exception {
// Test that HTTP version can be configured without errors
WebServiceClient clientHttp11 = new WebServiceClient.Builder(6, "0123456789")
.host("localhost")
.port(wireMock.getPort())
.disableHttps()
.httpVersion(HttpClient.Version.HTTP_1_1)
.build();

WebServiceClient clientHttp2 = new WebServiceClient.Builder(6, "0123456789")
.host("localhost")
.port(wireMock.getPort())
.disableHttps()
.httpVersion(HttpClient.Version.HTTP_2)
.build();

WebServiceClient clientDefault = new WebServiceClient.Builder(6, "0123456789")
.host("localhost")
.port(wireMock.getPort())
.disableHttps()
.build();

// Verify that clients can be created successfully
assertNotNull(clientHttp11);
assertNotNull(clientHttp2);
assertNotNull(clientDefault);
}

private void createInsightsError(String ip, int status, String contentType,
String responseContent) throws Exception {
WebServiceClient client = createClient(
Expand Down
Loading