forked from dropwizard/dropwizard
-
Notifications
You must be signed in to change notification settings - Fork 0
/
JerseyClientBuilder.java
437 lines (384 loc) · 14.8 KB
/
JerseyClientBuilder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
package io.dropwizard.client;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.httpclient.HttpClientMetricNameStrategy;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dropwizard.jersey.gzip.ConfiguredGZipEncoder;
import io.dropwizard.jersey.gzip.GZipDecoder;
import io.dropwizard.jersey.jackson.JacksonFeature;
import io.dropwizard.jersey.validation.HibernateValidationBinder;
import io.dropwizard.jersey.validation.Validators;
import io.dropwizard.lifecycle.Managed;
import io.dropwizard.setup.Environment;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.config.Registry;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.spi.ConnectorProvider;
import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier;
import javax.validation.Validator;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.RxInvokerProvider;
import javax.ws.rs.core.Configuration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import static java.util.Objects.requireNonNull;
/**
* A convenience class for building {@link Client} instances.
* <p>
* Among other things,
* <ul>
* <li>Backed by Apache HttpClient</li>
* <li>Disables stale connection checks</li>
* <li>Disables Nagle's algorithm</li>
* <li>Disables cookie management by default</li>
* <li>Compress requests and decompress responses using GZIP</li>
* <li>Supports parsing and generating JSON data using Jackson</li>
* </ul>
* </p>
*
* @see HttpClientBuilder
*/
public class JerseyClientBuilder {
private final List<Object> singletons = new ArrayList<>();
private final List<Class<?>> providers = new ArrayList<>();
private final Map<String, Object> properties = new LinkedHashMap<>();
private JerseyClientConfiguration configuration = new JerseyClientConfiguration();
private HttpClientBuilder apacheHttpClientBuilder;
private Validator validator = Validators.newValidator();
@Nullable
private Environment environment;
@Nullable
private ObjectMapper objectMapper;
@Nullable
private ExecutorService executorService;
@Nullable
private ConnectorProvider connectorProvider;
public JerseyClientBuilder(Environment environment) {
this.apacheHttpClientBuilder = new HttpClientBuilder(environment);
this.environment = environment;
}
public JerseyClientBuilder(MetricRegistry metricRegistry) {
this.apacheHttpClientBuilder = new HttpClientBuilder(metricRegistry);
}
public void setApacheHttpClientBuilder(HttpClientBuilder apacheHttpClientBuilder) {
this.apacheHttpClientBuilder = apacheHttpClientBuilder;
}
/**
* Adds the given object as a Jersey provider.
*
* @param provider a Jersey provider
* @return {@code this}
*/
public JerseyClientBuilder withProvider(Object provider) {
singletons.add(requireNonNull(provider));
return this;
}
/**
* Adds the given class as a Jersey provider. <p/><b>N.B.:</b> This class must either have a
* no-args constructor or use Jersey's built-in dependency injection.
*
* @param klass a Jersey provider class
* @return {@code this}
*/
public JerseyClientBuilder withProvider(Class<?> klass) {
providers.add(requireNonNull(klass));
return this;
}
/**
* Sets the state of the given Jersey property.
* <p/>
* <p/><b>WARNING:</b> The default connector ignores Jersey properties.
* Use {@link JerseyClientConfiguration} instead.
*
* @param propertyName the name of the Jersey property
* @param propertyValue the state of the Jersey property
* @return {@code this}
*/
public JerseyClientBuilder withProperty(String propertyName, Object propertyValue) {
properties.put(propertyName, propertyValue);
return this;
}
/**
* Uses the given {@link JerseyClientConfiguration}.
*
* @param configuration a configuration object
* @return {@code this}
*/
public JerseyClientBuilder using(JerseyClientConfiguration configuration) {
this.configuration = configuration;
apacheHttpClientBuilder.using(configuration);
return this;
}
/**
* Uses the given {@link Environment}.
*
* @param environment a Dropwizard {@link Environment}
* @return {@code this}
* @see #using(java.util.concurrent.ExecutorService, com.fasterxml.jackson.databind.ObjectMapper)
*/
public JerseyClientBuilder using(Environment environment) {
this.environment = environment;
return this;
}
/**
* Use the given {@link Validator} instance.
*
* @param validator a {@link Validator} instance
* @return {@code this}
*/
public JerseyClientBuilder using(Validator validator) {
this.validator = validator;
return this;
}
/**
* Uses the given {@link ExecutorService} and {@link ObjectMapper}.
*
* @param executorService a thread pool
* @param objectMapper an object mapper
* @return {@code this}
* @see #using(io.dropwizard.setup.Environment)
*/
public JerseyClientBuilder using(ExecutorService executorService, ObjectMapper objectMapper) {
this.executorService = executorService;
this.objectMapper = objectMapper;
return this;
}
/**
* Uses the given {@link ExecutorService}.
*
* @param executorService a thread pool
* @return {@code this}
* @see #using(io.dropwizard.setup.Environment)
*/
public JerseyClientBuilder using(ExecutorService executorService) {
this.executorService = executorService;
return this;
}
/**
* Uses the given {@link ObjectMapper}.
*
* @param objectMapper an object mapper
* @return {@code this}
* @see #using(io.dropwizard.setup.Environment)
*/
public JerseyClientBuilder using(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
return this;
}
/**
* Use the given {@link ConnectorProvider} instance.
* <p/><b>WARNING:</b> Use it with a caution. Most of features will not
* work in a custom connection provider.
*
* @param connectorProvider a {@link ConnectorProvider} instance
* @return {@code this}
*/
public JerseyClientBuilder using(ConnectorProvider connectorProvider) {
this.connectorProvider = connectorProvider;
return this;
}
/**
* Uses the {@link org.apache.http.client.HttpRequestRetryHandler} for handling request retries.
*
* @param httpRequestRetryHandler a HttpRequestRetryHandler
* @return {@code this}
*/
public JerseyClientBuilder using(HttpRequestRetryHandler httpRequestRetryHandler) {
apacheHttpClientBuilder.using(httpRequestRetryHandler);
return this;
}
/**
* Use the given {@link DnsResolver} instance.
*
* @param resolver a {@link DnsResolver} instance
* @return {@code this}
*/
public JerseyClientBuilder using(DnsResolver resolver) {
apacheHttpClientBuilder.using(resolver);
return this;
}
/**
* Use the given {@link HostnameVerifier} instance.
*
* Note that if {@link io.dropwizard.client.ssl.TlsConfiguration#isVerifyHostname()}
* returns false, all host name verification is bypassed, including
* host name verification performed by a verifier specified
* through this interface.
*
* @param verifier a {@link HostnameVerifier} instance
* @return {@code this}
*/
public JerseyClientBuilder using(HostnameVerifier verifier) {
apacheHttpClientBuilder.using(verifier);
return this;
}
/**
* Use the given {@link Registry} instance of connection socket factories.
*
* @param registry a {@link Registry} instance of connection socket factories
* @return {@code this}
*/
public JerseyClientBuilder using(Registry<ConnectionSocketFactory> registry) {
apacheHttpClientBuilder.using(registry);
return this;
}
/**
* Use the given {@link HttpClientMetricNameStrategy} instance.
*
* @param metricNameStrategy a {@link HttpClientMetricNameStrategy} instance
* @return {@code this}
*/
public JerseyClientBuilder using(HttpClientMetricNameStrategy metricNameStrategy) {
apacheHttpClientBuilder.using(metricNameStrategy);
return this;
}
/**
* Use the given environment name. This is used in the user agent.
*
* @param environmentName an environment name to use in the user agent.
* @return {@code this}
*/
public JerseyClientBuilder name(String environmentName) {
apacheHttpClientBuilder.name(environmentName);
return this;
}
/**
* Use the given {@link HttpRoutePlanner} instance.
*
* @param routePlanner a {@link HttpRoutePlanner} instance
* @return {@code this}
*/
public JerseyClientBuilder using(HttpRoutePlanner routePlanner) {
apacheHttpClientBuilder.using(routePlanner);
return this;
}
/**
* Use the given {@link CredentialsProvider} instance.
*
* @param credentialsProvider a {@link CredentialsProvider} instance
* @return {@code this}
*/
public JerseyClientBuilder using(CredentialsProvider credentialsProvider) {
apacheHttpClientBuilder.using(credentialsProvider);
return this;
}
/**
* Use the given {@link ServiceUnavailableRetryStrategy} instance.
*
* @param serviceUnavailableRetryStrategy a {@link ServiceUnavailableRetryStrategy} instance
* @return {@code this}
* @deprecated will be combined with {@link #using(HttpRequestRetryHandler)} in {@code using(HttpRequestRetryStrategy)}
*/
@Deprecated
public JerseyClientBuilder using(ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy) {
apacheHttpClientBuilder.using(serviceUnavailableRetryStrategy);
return this;
}
/**
* Builds the {@link Client} instance with a custom reactive client provider.
*
* @return a fully-configured {@link Client}
*/
public <RX extends RxInvokerProvider<?>> Client buildRx(String name, Class<RX> invokerType) {
return build(name).register(invokerType);
}
/**
* Builds the {@link Client} instance.
*
* @return a fully-configured {@link Client}
*/
public Client build(String name) {
if ((environment == null) && ((executorService == null) || (objectMapper == null))) {
throw new IllegalStateException("Must have either an environment or both " +
"an executor service and an object mapper");
}
if (executorService == null) {
// Create an ExecutorService based on the provided
// configuration. The DisposableExecutorService decorator
// is used to ensure that the service is shut down if the
// Jersey client disposes of it.
executorService = requireNonNull(environment).lifecycle()
.executorService("jersey-client-" + name + "-%d")
.minThreads(configuration.getMinThreads())
.maxThreads(configuration.getMaxThreads())
.workQueue(new ArrayBlockingQueue<>(configuration.getWorkQueueSize()))
.build();
}
if (objectMapper == null) {
objectMapper = requireNonNull(environment).getObjectMapper();
}
if (environment != null) {
validator = environment.getValidator();
}
return build(name, executorService, objectMapper, validator);
}
private Client build(String name, ExecutorService threadPool,
ObjectMapper objectMapper,
Validator validator) {
if (!configuration.isGzipEnabled()) {
apacheHttpClientBuilder.disableContentCompression(true);
}
final Client client = ClientBuilder.newClient(buildConfig(name, threadPool, objectMapper, validator));
client.register(new JerseyIgnoreRequestUserAgentHeaderFilter());
// Tie the client to server lifecycle
if (environment != null) {
environment.lifecycle().manage(new Managed() {
@Override
public void start() throws Exception {
}
@Override
public void stop() throws Exception {
client.close();
}
});
}
if (configuration.isGzipEnabled()) {
client.register(new GZipDecoder());
client.register(new ConfiguredGZipEncoder(configuration.isGzipEnabledForRequests()));
}
return client;
}
private Configuration buildConfig(final String name, final ExecutorService threadPool,
final ObjectMapper objectMapper,
final Validator validator) {
final ClientConfig config = new ClientConfig();
for (Object singleton : this.singletons) {
config.register(singleton);
}
for (Class<?> provider : this.providers) {
config.register(provider);
}
config.register(new JacksonFeature(objectMapper));
config.register(new HibernateValidationBinder(validator));
for (Map.Entry<String, Object> property : this.properties.entrySet()) {
config.property(property.getKey(), property.getValue());
}
config.register(new DropwizardExecutorProvider(threadPool));
if (connectorProvider == null) {
final ConfiguredCloseableHttpClient apacheHttpClient =
apacheHttpClientBuilder.buildWithDefaultRequestConfiguration(name);
config.connectorProvider((client, runtimeConfig) -> createDropwizardApacheConnector(apacheHttpClient));
} else {
config.connectorProvider(connectorProvider);
}
return config;
}
/**
* Builds {@link DropwizardApacheConnector} based on the configured Apache HTTP client
* as {@link ConfiguredCloseableHttpClient} and the chunked encoding configuration set by the user.
*/
protected DropwizardApacheConnector createDropwizardApacheConnector(ConfiguredCloseableHttpClient configuredClient) {
return new DropwizardApacheConnector(configuredClient.getClient(), configuredClient.getDefaultRequestConfig(),
configuration.isChunkedEncodingEnabled());
}
}