All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.maxmind.geoip2.WebServiceClient Maven / Gradle / Ivy

There is a newer version: 4.2.1
Show newest version
package com.maxmind.geoip2;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.maxmind.geoip2.exception.*;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.model.CountryResponse;
import com.maxmind.geoip2.model.InsightsResponse;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

import java.io.Closeable;
import java.io.IOException;
import java.net.*;
import java.util.*;

/**
 * 

* The {@code WebServiceClient} class provides a client API for all the GeoIP2 * Precision web service end points. The end points are Country, City, and * Insights. Each end point returns a different set of data about an IP * address, with Country returning the least data and Insights the most. *

*

* Each web service end point is represented by a different model class, and * these model classes in turn contain multiple Record classes. The record * classes have attributes which contain data about the IP address. *

*

* If the web service does not return a particular piece of data for an IP * address, the associated attribute is not populated. *

*

* The web service may not return any information for an entire record, in which * case all of the attributes for that record class will be empty. *

*

Usage

*

* To use the web service API, you must create a new {@code WebServiceClient} * using the {@code WebServiceClient.Builder}. You must provide the * {@code Builder} constructor your MaxMind {@code accountId} and * {@code licenseKey}. You may also set a {@code timeout}, specify a specific * {@code host}, 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 end point, passing * it the IP address you want to look up. *

*

* If the request succeeds, the method call will return a model class for the * end point you called. This model in turn contains multiple record classes, * each of which represents part of the data returned by the web service. *

*

* If the request fails, the client class throws an exception. *

*

* The {@code WebServiceClient} object is safe to share across threads. If you * are making multiple requests, the object should be reused so that new * connections are not created for each request. Once you have finished making * requests, you should close the object to ensure the connections are closed * and any resources are promptly returned to the system. *

*

Exceptions

*

* For details on the possible errors returned by the web service itself, see the GeoIP2 web * service documentation. *

*

* If the web service returns an explicit error document, this is thrown as a * {@link InvalidRequestException}. If some other sort of transport error * occurs, this is thrown as a {@link HttpException}. The difference is that the * web service error includes an error message and error code delivered by the * web service. The latter is thrown when some sort of unanticipated error * occurs, such as the web service returning a 500 or an invalid error document. *

*

* If the web service returns any status code besides 200, 4xx, or 5xx, this * also becomes a {@link HttpException}. *

*

* Finally, if the web service returns a 200 but the body is invalid, the client * throws a {@link GeoIp2Exception}. *

*/ public class WebServiceClient implements GeoIp2Provider, Closeable { private final String host; private final List locales; private final String licenseKey; private final int accountId; private final boolean useHttps; private final int port; private final ObjectMapper mapper; private final CloseableHttpClient httpClient; private WebServiceClient(Builder builder) { this.host = builder.host; this.port = builder.port; this.useHttps = builder.useHttps; this.locales = builder.locales; this.licenseKey = builder.licenseKey; this.accountId = builder.accountId; mapper = new ObjectMapper(); mapper.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); RequestConfig.Builder configBuilder = RequestConfig.custom() .setConnectTimeout(builder.connectTimeout) .setSocketTimeout(builder.readTimeout); if (builder.proxy != null && builder.proxy != Proxy.NO_PROXY) { InetSocketAddress address = (InetSocketAddress) builder.proxy.address(); HttpHost proxyHost = new HttpHost(address.getHostName(), address.getPort()); configBuilder.setProxy(proxyHost); } RequestConfig config = configBuilder.build(); httpClient = HttpClientBuilder.create() .setMaxConnPerRoute(20) .setUserAgent(userAgent()) .setDefaultRequestConfig(config).build(); } /** *

* {@code Builder} creates instances of {@code WebServiceClient} * from values set by the methods. *

*

* This example shows how to create a {@code WebServiceClient} object * with the {@code Builder}: *

*

* WebServiceClient client = new * WebServiceClient.Builder(12,"licensekey").host * ("geoip.maxmind.com").build(); *

*

* Only the values set in the {@code Builder} constructor are required. *

*/ public static final class Builder { final int accountId; final String licenseKey; String host = "geoip.maxmind.com"; int port = 443; boolean useHttps = true; int connectTimeout = 3000; int readTimeout = 20000; List locales = Collections.singletonList("en"); private Proxy proxy; /** * @param accountId Your MaxMind account ID. * @param licenseKey Your MaxMind license key. */ public Builder(int accountId, String licenseKey) { this.accountId = accountId; this.licenseKey = licenseKey; } /** * @param val Timeout in milliseconds to establish a connection to the * web service. The default is 3000 (3 seconds). * @return Builder object */ public Builder connectTimeout(int val) { this.connectTimeout = val; return this; } /** * Disables HTTPS to connect to a test server or proxy. The minFraud ScoreResponse and InsightsResponse web services require * HTTPS. * * @return Builder object */ public WebServiceClient.Builder disableHttps() { useHttps = false; return this; } /** * @param val The host to use. * @return Builder object */ public Builder host(String val) { this.host = val; return this; } /** * @param val The port to use. * @return Builder object */ public WebServiceClient.Builder port(int val) { port = val; return this; } /** * @param val List of locale codes to use in name property from most * preferred to least preferred. * @return Builder object */ public Builder locales(List val) { this.locales = new ArrayList<>(val); return this; } /** * @param val readTimeout in milliseconds to read data from an * established connection to the web service. The default is * 20000 (20 seconds). * @return Builder object */ public Builder readTimeout(int val) { this.readTimeout = val; return this; } /** * @param val the proxy to use when making this request. * @return Builder object */ public Builder proxy(Proxy val) { this.proxy = val; return this; } /** * @return an instance of {@code WebServiceClient} created from the * fields set on this builder. */ public WebServiceClient build() { return new WebServiceClient(this); } } /** * @return A Country model for the requesting IP address * @throws GeoIp2Exception if there is an error from the web service * @throws IOException if an IO error happens during the request */ public CountryResponse country() throws IOException, GeoIp2Exception { return this.country(null); } @Override public CountryResponse country(InetAddress ipAddress) throws IOException, GeoIp2Exception { return this.responseFor("country", ipAddress, CountryResponse.class); } /** * @return A City model for the requesting IP address * @throws GeoIp2Exception if there is an error from the web service * @throws IOException if an IO error happens during the request */ public CityResponse city() throws IOException, GeoIp2Exception { return this.city(null); } @Override public CityResponse city(InetAddress ipAddress) throws IOException, GeoIp2Exception { return this.responseFor("city", ipAddress, CityResponse.class); } /** * @return An Insights model for the requesting IP address * @throws GeoIp2Exception if there is an error from the web service * @throws IOException if an IO error happens during the request */ public InsightsResponse insights() throws IOException, GeoIp2Exception { return this.insights(null); } /** * @param ipAddress IPv4 or IPv6 address to lookup. * @return A Insight model for the requested IP address. * @throws GeoIp2Exception if there is an error looking up the IP * @throws IOException if there is an IO error */ public InsightsResponse insights(InetAddress ipAddress) throws IOException, GeoIp2Exception { return this.responseFor("insights", ipAddress, InsightsResponse.class); } private T responseFor(String path, InetAddress ipAddress, Class cls) throws IOException, GeoIp2Exception { URL url = createUri(path, ipAddress); HttpGet request = getResponse(url); try (CloseableHttpResponse response = httpClient.execute(request)) { return handleResponse(response, url, cls); } } private HttpGet getResponse(URL url) throws GeoIp2Exception, IOException { Credentials credentials = new UsernamePasswordCredentials(Integer.toString(accountId), licenseKey); HttpGet request; try { request = new HttpGet(url.toURI()); } catch (URISyntaxException e) { throw new GeoIp2Exception("Error parsing request URL", e); } try { request.addHeader(new BasicScheme().authenticate(credentials, request, null)); } catch (org.apache.http.auth.AuthenticationException e) { throw new AuthenticationException("Error setting up request authentication", e); } request.addHeader("Accept", "application/json"); return request; } private T handleResponse(CloseableHttpResponse response, URL url, Class cls) throws GeoIp2Exception, IOException { int status = response.getStatusLine().getStatusCode(); if (status >= 400 && status < 500) { this.handle4xxStatus(response, url); } else if (status >= 500 && status < 600) { throw new HttpException("Received a server error (" + status + ") for " + url, status, url); } else if (status != 200) { throw new HttpException("Received an unexpected HTTP status (" + status + ") for " + url, status, url); } InjectableValues inject = new JsonInjector(locales, null, null); HttpEntity entity = response.getEntity(); try { return mapper.readerFor(cls).with(inject).readValue(entity.getContent()); } catch (IOException e) { throw new GeoIp2Exception( "Received a 200 response but could not decode it as JSON", e); } finally { EntityUtils.consume(entity); } } private void handle4xxStatus(HttpResponse response, URL url) throws GeoIp2Exception, IOException { HttpEntity entity = response.getEntity(); int status = response.getStatusLine().getStatusCode(); if (entity.getContentLength() == 0L) { throw new HttpException("Received a " + status + " error for " + url + " with no body", status, url); } String body = EntityUtils.toString(entity, "UTF-8"); try { Map content = mapper.readValue(body, new TypeReference>() { }); handleErrorWithJsonBody(content, body, status, url); } catch (HttpException e) { throw e; } catch (IOException e) { throw new HttpException("Received a " + status + " error for " + url + " but it did not include the expected JSON body: " + body, status, url); } } private static void handleErrorWithJsonBody(Map content, String body, int status, URL url) throws GeoIp2Exception, HttpException { String error = content.get("error"); String code = content.get("code"); if (error == null || code == null) { throw new HttpException( "Error response contains JSON but it does not specify code or error keys: " + body, status, url); } switch (code) { case "IP_ADDRESS_NOT_FOUND": case "IP_ADDRESS_RESERVED": throw new AddressNotFoundException(error); case "ACCOUNT_ID_REQUIRED": case "ACCOUNT_ID_UNKNOWN": case "AUTHORIZATION_INVALID": case "LICENSE_KEY_REQUIRED": case "USER_ID_REQUIRED": case "USER_ID_UNKNOWN": throw new AuthenticationException(error); case "INSUFFICIENT_FUNDS": case "OUT_OF_QUERIES": throw new OutOfQueriesException(error); case "PERMISSION_REQUIRED": throw new PermissionRequiredException(error); } // These should be fairly rare throw new InvalidRequestException(error, code, url); } private URL createUri(String service, InetAddress ipAddress) throws GeoIp2Exception { try { return new URIBuilder() .setScheme(useHttps ? "https" : "http") .setHost(host) .setPort(this.port) .setPath("/geoip/v2.1/" + service + "/" + (ipAddress == null ? "me" : ipAddress.getHostAddress())) .build().toURL(); } catch (MalformedURLException e) { throw new GeoIp2Exception("Malformed service URL", e); } catch (URISyntaxException e) { throw new GeoIp2Exception("Syntax error creating service URL", e); } } private String userAgent() { return "GeoIP2/" + getClass().getPackage().getImplementationVersion() + " (Java/" + System.getProperty("java.version") + ")"; } /** * Close any open connections and return resources to the system. */ @Override public void close() throws IOException { httpClient.close(); } @Override public String toString() { return "WebServiceClient{" + ", host='" + host + '\'' + ", locales=" + locales + ", licenseKey='" + licenseKey + '\'' + ", accountId=" + accountId + ", useHttps=" + useHttps + ", port=" + port + ", mapper=" + mapper + ", httpClient=" + httpClient + '}'; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy