Skip to content

Commit 260907b

Browse files
committed
Reuse HttpClient
The primary motivation for this is so that connections can be reused. Also support setting a proxy and reuse ObjectMapper.
1 parent 167157d commit 260907b

3 files changed

Lines changed: 102 additions & 66 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ CHANGELOG
55
------------------
66

77
* First production release.
8+
* Connections will now be reused between requests made with the same
9+
`WebServiceClient` object.
10+
* `WebServiceClient` now implements `Closeable`.
11+
* You are now able to set a proxy to use via the `WebServiceClient.Builder`
12+
`proxy(Proxy)` method.
813
* Updated dependencies.
914
* Added the following new values to the `Payment.Processor` enum:
1015
`CONCEPT_PAYMENTS`, `ECOMM365`, `ORANGEPAY`, and `PACNET_SERVICES`.

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

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.maxmind.minfraud.response.InsightsResponse;
1313
import com.maxmind.minfraud.response.ScoreResponse;
1414
import org.apache.http.HttpEntity;
15+
import org.apache.http.HttpHost;
1516
import org.apache.http.HttpResponse;
1617
import org.apache.http.auth.Credentials;
1718
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -25,36 +26,55 @@
2526
import org.apache.http.impl.client.HttpClientBuilder;
2627
import org.apache.http.util.EntityUtils;
2728

29+
import java.io.Closeable;
2830
import java.io.IOException;
29-
import java.net.MalformedURLException;
30-
import java.net.URISyntaxException;
31-
import java.net.URL;
31+
import java.net.*;
3232
import java.util.*;
3333

3434
/**
3535
* Client for MaxMind minFraud Score, Insights, and Factors
3636
*/
37-
public final class WebServiceClient {
37+
public final class WebServiceClient implements Closeable {
3838
private static final String pathBase = "/minfraud/v2.0/";
3939

4040
private final String host;
4141
private final int port;
4242
private final boolean useHttps;
4343
private final List<String> locales;
4444
private final String licenseKey;
45-
private final int connectTimeout;
46-
private final int readTimeout;
4745
private final int userId;
4846

47+
48+
private final ObjectMapper mapper;
49+
private final CloseableHttpClient httpClient;
50+
4951
private WebServiceClient(WebServiceClient.Builder builder) {
5052
host = builder.host;
5153
port = builder.port;
5254
useHttps = builder.useHttps;
5355
locales = builder.locales;
5456
licenseKey = builder.licenseKey;
55-
connectTimeout = builder.connectTimeout;
56-
readTimeout = builder.readTimeout;
5757
userId = builder.userId;
58+
59+
mapper = new ObjectMapper();
60+
mapper.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS);
61+
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
62+
63+
RequestConfig.Builder configBuilder = RequestConfig.custom()
64+
.setConnectTimeout(builder.connectTimeout)
65+
.setSocketTimeout(builder.readTimeout);
66+
67+
if (builder.proxy != null) {
68+
InetSocketAddress address = (InetSocketAddress) builder.proxy.address();
69+
HttpHost proxyHost = new HttpHost(address.getHostName(), address.getPort());
70+
configBuilder.setProxy(proxyHost);
71+
}
72+
73+
RequestConfig config = configBuilder.build();
74+
httpClient =
75+
HttpClientBuilder.create()
76+
.setUserAgent(userAgent())
77+
.setDefaultRequestConfig(config).build();
5878
}
5979

6080
/**
@@ -87,6 +107,7 @@ public static final class Builder {
87107
int readTimeout = -1;
88108

89109
List<String> locales = Collections.singletonList("en");
110+
private Proxy proxy;
90111

91112
/**
92113
* @param userId Your MaxMind user ID.
@@ -162,6 +183,15 @@ public WebServiceClient.Builder readTimeout(int val) {
162183
return this;
163184
}
164185

186+
/**
187+
* @param val the proxy to use when making this request.
188+
* @return Builder object
189+
*/
190+
public Builder proxy(Proxy val) {
191+
this.proxy = val;
192+
return this;
193+
}
194+
165195
/**
166196
* @return an instance of {@code WebServiceClient} created from the
167197
* fields set on this builder.
@@ -254,16 +284,7 @@ private <T> T responseFor(String service, Transaction transaction, Class<T> cls)
254284
URL url = createUrl(WebServiceClient.pathBase + service);
255285
HttpPost request = requestFor(transaction, url);
256286

257-
RequestConfig config = RequestConfig.custom()
258-
.setConnectTimeout(this.connectTimeout)
259-
.setSocketTimeout(this.readTimeout)
260-
.build();
261-
262-
try (
263-
CloseableHttpClient httpClient =
264-
HttpClientBuilder.create().setDefaultRequestConfig(config).build();
265-
CloseableHttpResponse response = httpClient.execute(request)
266-
) {
287+
try (CloseableHttpResponse response = httpClient.execute(request)) {
267288
return handleResponse(response, url, cls);
268289
}
269290
}
@@ -315,9 +336,6 @@ private <T> T handleResponse(CloseableHttpResponse response, URL url, Class<T> c
315336
+ " but there was no message body.", 200, url);
316337
}
317338

318-
ObjectMapper mapper = new ObjectMapper();
319-
mapper.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS);
320-
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
321339
InjectableValues inject = new Std().addValue(
322340
"locales", locales);
323341

@@ -348,8 +366,6 @@ private void handle4xxStatus(HttpResponse response, URL url)
348366

349367
Map<String, String> content;
350368
try {
351-
ObjectMapper mapper = new ObjectMapper();
352-
mapper.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS);
353369
content = mapper.readValue(body,
354370
new TypeReference<HashMap<String, String>>() {
355371
});
@@ -410,16 +426,26 @@ private String userAgent() {
410426
+ " Java/" + System.getProperty("java.version");
411427
}
412428

429+
430+
/**
431+
* Close any open connections and return resources to the system.
432+
*/
433+
@Override
434+
public void close() throws IOException {
435+
httpClient.close();
436+
}
437+
413438
@Override
414439
public String toString() {
415-
return "WebServiceClient{" + "host='" + this.host + '\'' +
416-
", port=" + this.port +
417-
", useHttps=" + this.useHttps +
418-
", locales=" + this.locales +
419-
", licenseKey='" + this.licenseKey + '\'' +
420-
", connectTimeout=" + this.connectTimeout +
421-
", readTimeout=" + this.readTimeout +
422-
", userId=" + this.userId +
440+
return "WebServiceClient{" +
441+
"host='" + host + '\'' +
442+
", port=" + port +
443+
", useHttps=" + useHttps +
444+
", locales=" + locales +
445+
", licenseKey='" + licenseKey + '\'' +
446+
", userId=" + userId +
447+
", mapper=" + mapper +
448+
", httpClient=" + httpClient +
423449
'}';
424450
}
425451
}

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

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,60 +31,65 @@ public class WebServiceClientTest {
3131
@Test
3232
public void testFullScoreTransaction() throws Exception {
3333
String responseContent = readJsonFile("score-response");
34-
WebServiceClient client = createSuccessClient("score", responseContent);
35-
Transaction request = fullTransaction();
36-
ScoreResponse response = client.score(request);
34+
try (WebServiceClient client = createSuccessClient("score", responseContent)) {
35+
Transaction request = fullTransaction();
36+
ScoreResponse response = client.score(request);
3737

38-
JSONAssert.assertEquals(responseContent, response.toJson(), true);
39-
verifyRequestFor("score");
38+
JSONAssert.assertEquals(responseContent, response.toJson(), true);
39+
verifyRequestFor("score");
40+
}
4041
}
4142

4243
@Test
4344
public void testFullInsightsTransaction() throws Exception {
4445
String responseContent = readJsonFile("insights-response");
45-
WebServiceClient client = createSuccessClient("insights", responseContent);
46-
Transaction request = fullTransaction();
47-
InsightsResponse response = client.insights(request);
46+
try (WebServiceClient client = createSuccessClient("insights", responseContent)) {
47+
Transaction request = fullTransaction();
48+
InsightsResponse response = client.insights(request);
4849

49-
// We use non-strict checking as there is some extra stuff in the serialized
50-
// object, most notably the "name" field in the GeoIP2 InsightsResponse subobjects.
51-
// We cannot change this as it would be a breaking change to the GeoIP2 API.
52-
JSONAssert.assertEquals(responseContent, response.toJson(), false);
53-
verifyRequestFor("insights");
50+
// We use non-strict checking as there is some extra stuff in the serialized
51+
// object, most notably the "name" field in the GeoIP2 InsightsResponse subobjects.
52+
// We cannot change this as it would be a breaking change to the GeoIP2 API.
53+
JSONAssert.assertEquals(responseContent, response.toJson(), false);
54+
verifyRequestFor("insights");
55+
}
5456
}
5557

5658
@Test
5759
public void testFullFactorsTransaction() throws Exception {
5860
String responseContent = readJsonFile("factors-response");
59-
WebServiceClient client = createSuccessClient("factors", responseContent);
60-
Transaction request = fullTransaction();
61-
FactorsResponse response = client.factors(request);
61+
try (WebServiceClient client = createSuccessClient("factors", responseContent)) {
62+
Transaction request = fullTransaction();
63+
FactorsResponse response = client.factors(request);
6264

63-
// We use non-strict checking as there is some extra stuff in the serialized
64-
// object, most notably the "name" field in the GeoIP2 InsightsResponse subobjects.
65-
// We cannot change this as it would be a breaking change to the GeoIP2 API.
66-
JSONAssert.assertEquals(responseContent, response.toJson(), false);
67-
verifyRequestFor("factors");
65+
// We use non-strict checking as there is some extra stuff in the serialized
66+
// object, most notably the "name" field in the GeoIP2 InsightsResponse subobjects.
67+
// We cannot change this as it would be a breaking change to the GeoIP2 API.
68+
JSONAssert.assertEquals(responseContent, response.toJson(), false);
69+
verifyRequestFor("factors");
70+
}
6871
}
6972

7073
@Test
7174
public void test200WithNoBody() throws Exception {
72-
WebServiceClient client = createSuccessClient("insights", "");
73-
Transaction request = fullTransaction();
75+
try (WebServiceClient client = createSuccessClient("insights", "")) {
76+
Transaction request = fullTransaction();
7477

75-
thrown.expect(HttpException.class);
76-
thrown.expectMessage(matchesPattern("Received a 200 response for .*/minfraud/v2.0/insights but there was no message body\\."));
77-
client.insights(request);
78+
thrown.expect(HttpException.class);
79+
thrown.expectMessage(matchesPattern("Received a 200 response for .*/minfraud/v2.0/insights but there was no message body\\."));
80+
client.insights(request);
81+
}
7882
}
7983

8084
@Test
8185
public void test200WithInvalidJson() throws Exception {
82-
WebServiceClient client = createSuccessClient("insights", "{");
83-
Transaction request = fullTransaction();
86+
try (WebServiceClient client = createSuccessClient("insights", "{")) {
87+
Transaction request = fullTransaction();
8488

85-
thrown.expect(MinFraudException.class);
86-
thrown.expectMessage("Received a 200 response but could not decode it as JSON");
87-
client.insights(request);
89+
thrown.expect(MinFraudException.class);
90+
thrown.expectMessage("Received a 200 response but could not decode it as JSON");
91+
client.insights(request);
92+
}
8893
}
8994

9095
@Test
@@ -201,7 +206,6 @@ public void test500() throws Exception {
201206
}
202207

203208
private WebServiceClient createSuccessClient(String service, String responseContent) {
204-
205209
return createClient(
206210
service,
207211
200,
@@ -211,13 +215,14 @@ private WebServiceClient createSuccessClient(String service, String responseCont
211215
}
212216

213217
private void createInsightsError(int status, String contentType, String responseContent) throws Exception {
214-
WebServiceClient client = createClient(
218+
try (WebServiceClient client = createClient(
215219
"insights",
216220
status,
217221
contentType,
218222
responseContent
219-
);
220-
client.insights(fullTransaction());
223+
)) {
224+
client.insights(fullTransaction());
225+
}
221226
}
222227

223228
private WebServiceClient createClient(String service, int status, String contentType, String responseContent) {

0 commit comments

Comments
 (0)