-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Apache HttpClient 5.x (#1543)
https://hc.apache.org/httpcomponents-client-5.0.x/index.html https://downloads.apache.org/httpcomponents/httpclient/RELEASE_NOTES-5.0.x.txt https://ok2c.github.io/httpclient-migration-guide/
- Loading branch information
Showing
15 changed files
with
1,114 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>io.dropwizard.metrics</groupId> | ||
<artifactId>metrics-parent</artifactId> | ||
<version>4.1.4-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>metrics-httpclient5</artifactId> | ||
<name>Metrics Integration for Apache HttpClient 5.x</name> | ||
<packaging>bundle</packaging> | ||
<description> | ||
An Apache HttpClient 5.x wrapper providing Metrics instrumentation of connection pools, request | ||
durations and rates, and other useful information. | ||
</description> | ||
|
||
<properties> | ||
<javaModuleName>com.codahale.metrics.httpclient</javaModuleName> | ||
<http-client.version>5.0</http-client.version> | ||
</properties> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>io.dropwizard.metrics</groupId> | ||
<artifactId>metrics-bom</artifactId> | ||
<version>${project.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.dropwizard.metrics</groupId> | ||
<artifactId>metrics-core</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.httpcomponents.client5</groupId> | ||
<artifactId>httpclient5</artifactId> | ||
<version>${http-client.version}</version> | ||
<exclusions> | ||
<exclusion> | ||
<groupId>org.slf4j</groupId> | ||
<artifactId>slf4j-api</artifactId> | ||
</exclusion> | ||
</exclusions> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.awaitility</groupId> | ||
<artifactId>awaitility</artifactId> | ||
<version>4.0.2</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
48 changes: 48 additions & 0 deletions
48
...lient5/src/main/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategies.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.codahale.metrics.httpclient5; | ||
|
||
import org.apache.hc.client5.http.classic.HttpClient; | ||
import org.apache.hc.core5.http.HttpRequest; | ||
import org.apache.hc.core5.net.URIBuilder; | ||
|
||
import java.net.URISyntaxException; | ||
import java.util.Locale; | ||
|
||
import static com.codahale.metrics.MetricRegistry.name; | ||
|
||
public class HttpClientMetricNameStrategies { | ||
|
||
public static final HttpClientMetricNameStrategy METHOD_ONLY = | ||
(name, request) -> name(HttpClient.class, | ||
name, | ||
methodNameString(request)); | ||
|
||
public static final HttpClientMetricNameStrategy HOST_AND_METHOD = | ||
(name, request) -> { | ||
try { | ||
return name(HttpClient.class, | ||
name, | ||
request.getUri().getHost(), | ||
methodNameString(request)); | ||
} catch (URISyntaxException e) { | ||
throw new IllegalArgumentException(e); | ||
} | ||
}; | ||
|
||
public static final HttpClientMetricNameStrategy QUERYLESS_URL_AND_METHOD = | ||
(name, request) -> { | ||
try { | ||
final URIBuilder url = new URIBuilder(request.getUri()); | ||
return name(HttpClient.class, | ||
name, | ||
url.removeQuery().build().toString(), | ||
methodNameString(request)); | ||
} catch (URISyntaxException e) { | ||
throw new IllegalArgumentException(e); | ||
} | ||
}; | ||
|
||
private static String methodNameString(HttpRequest request) { | ||
return request.getMethod().toLowerCase(Locale.ROOT) + "-requests"; | ||
} | ||
|
||
} |
17 changes: 17 additions & 0 deletions
17
...pclient5/src/main/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.codahale.metrics.httpclient5; | ||
|
||
import com.codahale.metrics.MetricRegistry; | ||
import org.apache.hc.client5.http.classic.HttpClient; | ||
import org.apache.hc.core5.http.HttpRequest; | ||
|
||
@FunctionalInterface | ||
public interface HttpClientMetricNameStrategy { | ||
|
||
String getNameFor(String name, HttpRequest request); | ||
|
||
default String getNameFor(String name, Exception exception) { | ||
return MetricRegistry.name(HttpClient.class, | ||
name, | ||
exception.getClass().getSimpleName()); | ||
} | ||
} |
164 changes: 164 additions & 0 deletions
164
.../main/java/com/codahale/metrics/httpclient5/InstrumentedAsyncClientConnectionManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package com.codahale.metrics.httpclient5; | ||
|
||
import com.codahale.metrics.Gauge; | ||
import com.codahale.metrics.MetricRegistry; | ||
import org.apache.hc.client5.http.DnsResolver; | ||
import org.apache.hc.client5.http.SchemePortResolver; | ||
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; | ||
import org.apache.hc.client5.http.io.HttpClientConnectionManager; | ||
import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; | ||
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; | ||
import org.apache.hc.core5.http.URIScheme; | ||
import org.apache.hc.core5.http.config.Lookup; | ||
import org.apache.hc.core5.http.config.Registry; | ||
import org.apache.hc.core5.http.config.RegistryBuilder; | ||
import org.apache.hc.core5.http.nio.ssl.TlsStrategy; | ||
import org.apache.hc.core5.io.CloseMode; | ||
import org.apache.hc.core5.pool.PoolConcurrencyPolicy; | ||
import org.apache.hc.core5.pool.PoolReusePolicy; | ||
import org.apache.hc.core5.util.TimeValue; | ||
|
||
import static com.codahale.metrics.MetricRegistry.name; | ||
import static java.util.Objects.requireNonNull; | ||
|
||
/** | ||
* A {@link HttpClientConnectionManager} which monitors the number of open connections. | ||
*/ | ||
public class InstrumentedAsyncClientConnectionManager extends PoolingAsyncClientConnectionManager { | ||
private static final String METRICS_PREFIX = AsyncClientConnectionManager.class.getName(); | ||
|
||
protected static Registry<TlsStrategy> getDefaultTlsStrategy() { | ||
return RegistryBuilder.<TlsStrategy>create() | ||
.register(URIScheme.HTTPS.id, DefaultClientTlsStrategy.getDefault()) | ||
.build(); | ||
} | ||
|
||
private final MetricRegistry metricsRegistry; | ||
private final String name; | ||
|
||
InstrumentedAsyncClientConnectionManager(final MetricRegistry metricRegistry, | ||
final String name, | ||
final Lookup<TlsStrategy> tlsStrategyLookup, | ||
final PoolConcurrencyPolicy poolConcurrencyPolicy, | ||
final PoolReusePolicy poolReusePolicy, | ||
final TimeValue timeToLive, | ||
final SchemePortResolver schemePortResolver, | ||
final DnsResolver dnsResolver) { | ||
|
||
super(tlsStrategyLookup, poolConcurrencyPolicy, poolReusePolicy, timeToLive, schemePortResolver, dnsResolver); | ||
this.metricsRegistry = requireNonNull(metricRegistry, "metricRegistry"); | ||
this.name = name; | ||
|
||
metricRegistry.register(name(METRICS_PREFIX, name, "available-connections"), | ||
(Gauge<Integer>) () -> { | ||
// this acquires a lock on the connection pool; remove if contention sucks | ||
return getTotalStats().getAvailable(); | ||
}); | ||
metricRegistry.register(name(METRICS_PREFIX, name, "leased-connections"), | ||
(Gauge<Integer>) () -> { | ||
// this acquires a lock on the connection pool; remove if contention sucks | ||
return getTotalStats().getLeased(); | ||
}); | ||
metricRegistry.register(name(METRICS_PREFIX, name, "max-connections"), | ||
(Gauge<Integer>) () -> { | ||
// this acquires a lock on the connection pool; remove if contention sucks | ||
return getTotalStats().getMax(); | ||
}); | ||
metricRegistry.register(name(METRICS_PREFIX, name, "pending-connections"), | ||
(Gauge<Integer>) () -> { | ||
// this acquires a lock on the connection pool; remove if contention sucks | ||
return getTotalStats().getPending(); | ||
}); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public void close() { | ||
close(CloseMode.GRACEFUL); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public void close(CloseMode closeMode) { | ||
super.close(closeMode); | ||
metricsRegistry.remove(name(METRICS_PREFIX, name, "available-connections")); | ||
metricsRegistry.remove(name(METRICS_PREFIX, name, "leased-connections")); | ||
metricsRegistry.remove(name(METRICS_PREFIX, name, "max-connections")); | ||
metricsRegistry.remove(name(METRICS_PREFIX, name, "pending-connections")); | ||
} | ||
|
||
public static Builder builder(MetricRegistry metricsRegistry) { | ||
return new Builder().metricsRegistry(metricsRegistry); | ||
} | ||
|
||
public static class Builder { | ||
private MetricRegistry metricsRegistry; | ||
private String name; | ||
private Lookup<TlsStrategy> tlsStrategyLookup = getDefaultTlsStrategy(); | ||
private SchemePortResolver schemePortResolver; | ||
private DnsResolver dnsResolver; | ||
private PoolConcurrencyPolicy poolConcurrencyPolicy; | ||
private PoolReusePolicy poolReusePolicy; | ||
private TimeValue timeToLive = TimeValue.NEG_ONE_MILLISECOND; | ||
|
||
Builder() { | ||
} | ||
|
||
public Builder metricsRegistry(MetricRegistry metricRegistry) { | ||
this.metricsRegistry = requireNonNull(metricRegistry, "metricRegistry"); | ||
return this; | ||
} | ||
|
||
public Builder name(final String name) { | ||
this.name = name; | ||
return this; | ||
} | ||
|
||
public Builder schemePortResolver(SchemePortResolver schemePortResolver) { | ||
this.schemePortResolver = schemePortResolver; | ||
return this; | ||
} | ||
|
||
public Builder dnsResolver(DnsResolver dnsResolver) { | ||
this.dnsResolver = dnsResolver; | ||
return this; | ||
} | ||
|
||
public Builder timeToLive(TimeValue timeToLive) { | ||
this.timeToLive = timeToLive; | ||
return this; | ||
} | ||
|
||
public Builder tlsStrategyLookup(Lookup<TlsStrategy> tlsStrategyLookup) { | ||
this.tlsStrategyLookup = tlsStrategyLookup; | ||
return this; | ||
} | ||
|
||
public Builder poolConcurrencyPolicy(PoolConcurrencyPolicy poolConcurrencyPolicy) { | ||
this.poolConcurrencyPolicy = poolConcurrencyPolicy; | ||
return this; | ||
} | ||
|
||
public Builder poolReusePolicy(PoolReusePolicy poolReusePolicy) { | ||
this.poolReusePolicy = poolReusePolicy; | ||
return this; | ||
} | ||
|
||
public InstrumentedAsyncClientConnectionManager build() { | ||
return new InstrumentedAsyncClientConnectionManager( | ||
metricsRegistry, | ||
name, | ||
tlsStrategyLookup, | ||
poolConcurrencyPolicy, | ||
poolReusePolicy, | ||
timeToLive, | ||
schemePortResolver, | ||
dnsResolver); | ||
} | ||
} | ||
|
||
} |
99 changes: 99 additions & 0 deletions
99
...nt5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedAsyncExecChainHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package com.codahale.metrics.httpclient5; | ||
|
||
import com.codahale.metrics.Meter; | ||
import com.codahale.metrics.MetricRegistry; | ||
import com.codahale.metrics.Timer; | ||
import org.apache.hc.client5.http.async.AsyncExecCallback; | ||
import org.apache.hc.client5.http.async.AsyncExecChain; | ||
import org.apache.hc.client5.http.async.AsyncExecChainHandler; | ||
import org.apache.hc.core5.http.EntityDetails; | ||
import org.apache.hc.core5.http.HttpException; | ||
import org.apache.hc.core5.http.HttpRequest; | ||
import org.apache.hc.core5.http.HttpResponse; | ||
import org.apache.hc.core5.http.nio.AsyncDataConsumer; | ||
import org.apache.hc.core5.http.nio.AsyncEntityProducer; | ||
|
||
import java.io.IOException; | ||
|
||
import static java.util.Objects.requireNonNull; | ||
|
||
class InstrumentedAsyncExecChainHandler implements AsyncExecChainHandler { | ||
private final MetricRegistry registry; | ||
private final HttpClientMetricNameStrategy metricNameStrategy; | ||
private final String name; | ||
|
||
public InstrumentedAsyncExecChainHandler(MetricRegistry registry, HttpClientMetricNameStrategy metricNameStrategy) { | ||
this(registry, metricNameStrategy, null); | ||
} | ||
|
||
public InstrumentedAsyncExecChainHandler(MetricRegistry registry, | ||
HttpClientMetricNameStrategy metricNameStrategy, | ||
String name) { | ||
this.registry = requireNonNull(registry, "registry"); | ||
this.metricNameStrategy = requireNonNull(metricNameStrategy, "metricNameStrategy"); | ||
this.name = name; | ||
} | ||
|
||
@Override | ||
public void execute(HttpRequest request, | ||
AsyncEntityProducer entityProducer, | ||
AsyncExecChain.Scope scope, | ||
AsyncExecChain chain, | ||
AsyncExecCallback asyncExecCallback) throws HttpException, IOException { | ||
final InstrumentedAsyncExecCallback instrumentedAsyncExecCallback = | ||
new InstrumentedAsyncExecCallback(registry, metricNameStrategy, name, asyncExecCallback, request); | ||
chain.proceed(request, entityProducer, scope, instrumentedAsyncExecCallback); | ||
|
||
} | ||
|
||
final static class InstrumentedAsyncExecCallback implements AsyncExecCallback { | ||
private final MetricRegistry registry; | ||
private final HttpClientMetricNameStrategy metricNameStrategy; | ||
private final String name; | ||
private final AsyncExecCallback delegate; | ||
private final Timer.Context timerContext; | ||
|
||
public InstrumentedAsyncExecCallback(MetricRegistry registry, | ||
HttpClientMetricNameStrategy metricNameStrategy, | ||
String name, | ||
AsyncExecCallback delegate, | ||
HttpRequest request) { | ||
this.registry = registry; | ||
this.metricNameStrategy = metricNameStrategy; | ||
this.name = name; | ||
this.delegate = delegate; | ||
this.timerContext = timer(request).time(); | ||
} | ||
|
||
@Override | ||
public AsyncDataConsumer handleResponse(HttpResponse response, EntityDetails entityDetails) throws HttpException, IOException { | ||
return delegate.handleResponse(response, entityDetails); | ||
} | ||
|
||
@Override | ||
public void handleInformationResponse(HttpResponse response) throws HttpException, IOException { | ||
delegate.handleInformationResponse(response); | ||
} | ||
|
||
@Override | ||
public void completed() { | ||
delegate.completed(); | ||
timerContext.stop(); | ||
} | ||
|
||
@Override | ||
public void failed(Exception cause) { | ||
delegate.failed(cause); | ||
meter(cause).mark(); | ||
timerContext.stop(); | ||
} | ||
|
||
private Timer timer(HttpRequest request) { | ||
return registry.timer(metricNameStrategy.getNameFor(name, request)); | ||
} | ||
|
||
private Meter meter(Exception e) { | ||
return registry.meter(metricNameStrategy.getNameFor(name, e)); | ||
} | ||
} | ||
} |
Oops, something went wrong.