Service Provider Interface (SPI) for HTTP client implementations in the AWS SDK for Java v2
Built-in metrics collection capabilities for HTTP client implementations. Provides standardized metrics for HTTP/1.1 and HTTP/2 operations including connection pooling, performance monitoring, and protocol-specific statistics.
Defines standard metrics that HTTP client implementations should collect for HTTP/1.1 and HTTP/2 operations. These metrics provide insights into client performance and resource utilization.
/**
* Metrics collected by HTTP clients for HTTP/1 and HTTP/2 operations.
* Implementations should collect these metrics to provide visibility
* into client performance and resource utilization.
*/
public final class HttpMetric {
/**
* Name of the HTTP client implementation
*/
public static final SdkMetric<String> HTTP_CLIENT_NAME;
/**
* Maximum number of concurrent requests supported by the client
*/
public static final SdkMetric<Integer> MAX_CONCURRENCY;
/**
* Current number of available concurrent request slots
*/
public static final SdkMetric<Integer> AVAILABLE_CONCURRENCY;
/**
* Current number of requests actively being executed
*/
public static final SdkMetric<Integer> LEASED_CONCURRENCY;
/**
* Number of requests currently waiting to acquire concurrency
*/
public static final SdkMetric<Integer> PENDING_CONCURRENCY_ACQUIRES;
/**
* HTTP response status code for completed requests
*/
public static final SdkMetric<Integer> HTTP_STATUS_CODE;
/**
* Time taken to acquire a connection or channel from the pool
*/
public static final SdkMetric<Duration> CONCURRENCY_ACQUIRE_DURATION;
}Usage Example:
// HTTP client implementation collecting metrics
public class MyHttpClient implements SdkHttpClient {
private final MetricCollector metricCollector;
private final ConnectionPool connectionPool;
@Override
public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
return new MetricsAwareExecutableRequest(request, metricCollector, connectionPool);
}
// Collect periodic metrics
private void collectPoolMetrics() {
metricCollector.reportMetric(HttpMetric.HTTP_CLIENT_NAME, clientName());
metricCollector.reportMetric(HttpMetric.MAX_CONCURRENCY, connectionPool.maxConnections());
metricCollector.reportMetric(HttpMetric.AVAILABLE_CONCURRENCY, connectionPool.availableConnections());
metricCollector.reportMetric(HttpMetric.LEASED_CONCURRENCY, connectionPool.activeConnections());
metricCollector.reportMetric(HttpMetric.PENDING_CONCURRENCY_ACQUIRES, connectionPool.pendingAcquires());
}
}
// Metrics-aware request execution
public class MetricsAwareExecutableRequest implements ExecutableHttpRequest {
private final HttpExecuteRequest request;
private final MetricCollector metricCollector;
private final ConnectionPool connectionPool;
@Override
public HttpExecuteResponse call() throws IOException {
long acquireStart = System.nanoTime();
try (Connection connection = connectionPool.acquire()) {
long acquireDuration = System.nanoTime() - acquireStart;
metricCollector.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
Duration.ofNanos(acquireDuration));
HttpExecuteResponse response = connection.execute(request);
// Report response status
metricCollector.reportMetric(HttpMetric.HTTP_STATUS_CODE,
response.httpResponse().statusCode());
return response;
}
}
}Defines HTTP/2 specific metrics that provide insights into HTTP/2 protocol behavior, stream management, and flow control.
/**
* Metrics specific to HTTP/2 operations.
* These metrics provide insights into HTTP/2 stream management,
* flow control, and protocol-specific performance characteristics.
*/
public final class Http2Metric {
/**
* Local HTTP/2 stream window size in bytes.
* Indicates how much data the local endpoint can receive.
*/
public static final SdkMetric<Integer> LOCAL_STREAM_WINDOW_SIZE_IN_BYTES;
/**
* Remote HTTP/2 stream window size in bytes.
* Indicates how much data can be sent to the remote endpoint.
*/
public static final SdkMetric<Integer> REMOTE_STREAM_WINDOW_SIZE_IN_BYTES;
}Usage Example:
// HTTP/2 client implementation with flow control metrics
public class Http2Client implements SdkAsyncHttpClient {
private final MetricCollector metricCollector;
public void reportStreamMetrics(Http2Stream stream) {
// Report flow control window sizes
metricCollector.reportMetric(Http2Metric.LOCAL_STREAM_WINDOW_SIZE_IN_BYTES,
stream.getLocalWindowSize());
metricCollector.reportMetric(Http2Metric.REMOTE_STREAM_WINDOW_SIZE_IN_BYTES,
stream.getRemoteWindowSize());
}
@Override
public CompletableFuture<Void> execute(AsyncExecuteRequest request) {
return http2Connection.createStream()
.thenCompose(stream -> {
// Report initial window sizes
reportStreamMetrics(stream);
// Execute request and monitor flow control
return stream.sendRequest(request)
.thenRun(() -> {
// Report final window sizes
reportStreamMetrics(stream);
});
});
}
}The metric system uses type-safe metric definitions:
/**
* Type-safe metric definition
*/
public interface SdkMetric<T> {
/**
* @return Metric name for identification
*/
String name();
/**
* @return Expected value type for this metric
*/
Class<T> valueType();
}HTTP clients should integrate with the AWS SDK metric collection system:
// Metric collection during request execution
public class MetricsIntegratedClient implements SdkHttpClient {
@Override
public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
Optional<MetricCollector> collector = request.metricCollector();
return new ExecutableHttpRequest() {
@Override
public HttpExecuteResponse call() throws IOException {
long startTime = System.nanoTime();
try {
// Report client name
collector.ifPresent(c ->
c.reportMetric(HttpMetric.HTTP_CLIENT_NAME, clientName()));
// Execute request
HttpExecuteResponse response = executeHttpRequest(request);
// Report success metrics
collector.ifPresent(c -> {
c.reportMetric(HttpMetric.HTTP_STATUS_CODE,
response.httpResponse().statusCode());
long duration = System.nanoTime() - startTime;
c.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
Duration.ofNanos(duration));
});
return response;
} catch (IOException e) {
// Report error metrics
collector.ifPresent(c -> {
// Could report error-specific metrics here
});
throw e;
}
}
};
}
}HTTP clients should provide real-time visibility into connection pool state:
public class MonitoredConnectionPool implements ConnectionPool {
private final AtomicInteger maxConnections;
private final AtomicInteger activeConnections;
private final AtomicInteger availableConnections;
private final AtomicInteger pendingAcquires;
private final MetricCollector metricCollector;
public MonitoredConnectionPool(int maxConnections, MetricCollector metricCollector) {
this.maxConnections = new AtomicInteger(maxConnections);
this.activeConnections = new AtomicInteger(0);
this.availableConnections = new AtomicInteger(maxConnections);
this.pendingAcquires = new AtomicInteger(0);
this.metricCollector = metricCollector;
// Start periodic metric reporting
startMetricReporting();
}
@Override
public Connection acquire() throws IOException {
pendingAcquires.incrementAndGet();
long acquireStart = System.nanoTime();
try {
Connection connection = doAcquire();
// Update counters
activeConnections.incrementAndGet();
availableConnections.decrementAndGet();
pendingAcquires.decrementAndGet();
// Report acquire duration
long acquireDuration = System.nanoTime() - acquireStart;
metricCollector.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
Duration.ofNanos(acquireDuration));
return new MetricsAwareConnection(connection, this);
} catch (IOException e) {
pendingAcquires.decrementAndGet();
throw e;
}
}
public void releaseConnection(Connection connection) {
activeConnections.decrementAndGet();
availableConnections.incrementAndGet();
doRelease(connection);
}
private void startMetricReporting() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
metricCollector.reportMetric(HttpMetric.MAX_CONCURRENCY, maxConnections.get());
metricCollector.reportMetric(HttpMetric.AVAILABLE_CONCURRENCY, availableConnections.get());
metricCollector.reportMetric(HttpMetric.LEASED_CONCURRENCY, activeConnections.get());
metricCollector.reportMetric(HttpMetric.PENDING_CONCURRENCY_ACQUIRES, pendingAcquires.get());
}, 0, 10, TimeUnit.SECONDS);
}
}
// Connection wrapper that reports metrics on release
public class MetricsAwareConnection implements Connection {
private final Connection delegate;
private final MonitoredConnectionPool pool;
@Override
public void close() {
delegate.close();
pool.releaseConnection(delegate);
}
// Delegate all other methods to the wrapped connection
}For HTTP/2 implementations, track stream-specific metrics:
public class Http2StreamManager {
private final MetricCollector metricCollector;
private final Map<Integer, Http2Stream> activeStreams = new ConcurrentHashMap<>();
public Http2Stream createStream() {
Http2Stream stream = new Http2Stream();
activeStreams.put(stream.getId(), stream);
// Report initial flow control state
reportStreamFlowControl(stream);
return stream;
}
public void reportStreamFlowControl(Http2Stream stream) {
metricCollector.reportMetric(Http2Metric.LOCAL_STREAM_WINDOW_SIZE_IN_BYTES,
stream.getLocalFlowControlWindow());
metricCollector.reportMetric(Http2Metric.REMOTE_STREAM_WINDOW_SIZE_IN_BYTES,
stream.getRemoteFlowControlWindow());
}
public void onWindowUpdate(int streamId, int windowSizeIncrement) {
Http2Stream stream = activeStreams.get(streamId);
if (stream != null) {
stream.updateRemoteWindow(windowSizeIncrement);
reportStreamFlowControl(stream);
}
}
public void onStreamData(int streamId, int dataSize) {
Http2Stream stream = activeStreams.get(streamId);
if (stream != null) {
stream.consumeLocalWindow(dataSize);
reportStreamFlowControl(stream);
}
}
}Avoid High-Frequency Metrics:
// Don't report metrics for every byte transferred
// Instead, report periodically or on significant events
public class EfficientMetricReporting {
private volatile long lastMetricReport = 0;
private static final long METRIC_REPORT_INTERVAL_MS = 10_000; // 10 seconds
public void maybeReportMetrics() {
long now = System.currentTimeMillis();
if (now - lastMetricReport > METRIC_REPORT_INTERVAL_MS) {
reportCurrentMetrics();
lastMetricReport = now;
}
}
}Batch Metric Updates:
// Collect multiple metrics in a single call
public void reportBatchMetrics(MetricCollector collector) {
collector.reportMetrics(Map.of(
HttpMetric.AVAILABLE_CONCURRENCY, connectionPool.available(),
HttpMetric.LEASED_CONCURRENCY, connectionPool.active(),
HttpMetric.PENDING_CONCURRENCY_ACQUIRES, connectionPool.pending()
));
}Conditional Metric Collection:
public HttpExecuteResponse call() throws IOException {
Optional<MetricCollector> collector = request.metricCollector();
// Only collect metrics if a collector is provided
if (collector.isPresent()) {
return executeWithMetrics(collector.get());
} else {
return executeWithoutMetrics();
}
}Track both success and failure scenarios:
public class ComprehensiveMetricsClient implements SdkHttpClient {
@Override
public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
return new ExecutableHttpRequest() {
@Override
public HttpExecuteResponse call() throws IOException {
Optional<MetricCollector> collector = request.metricCollector();
long startTime = System.nanoTime();
try {
HttpExecuteResponse response = executeRequest(request);
// Report success metrics
collector.ifPresent(c -> {
c.reportMetric(HttpMetric.HTTP_STATUS_CODE,
response.httpResponse().statusCode());
// Report timing
long duration = System.nanoTime() - startTime;
c.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
Duration.ofNanos(duration));
});
return response;
} catch (IOException e) {
// Report error metrics
collector.ifPresent(c -> {
// Report error count or type
c.reportMetric(createErrorMetric(e.getClass().getSimpleName()), 1);
// Still report timing for failed requests
long duration = System.nanoTime() - startTime;
c.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
Duration.ofNanos(duration));
});
throw e;
}
}
};
}
private SdkMetric<Integer> createErrorMetric(String errorType) {
// Create custom metrics for different error types
return new SdkMetric<Integer>() {
@Override
public String name() {
return "http.client.error." + errorType.toLowerCase();
}
@Override
public Class<Integer> valueType() {
return Integer.class;
}
};
}
}HTTP client metrics can be integrated with various monitoring systems:
// Example integration with Micrometer/Prometheus
public class MicrometerIntegratedClient implements SdkHttpClient {
private final MeterRegistry meterRegistry;
private final Counter requestCounter;
private final Timer requestTimer;
private final Gauge connectionGauge;
public MicrometerIntegratedClient(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.requestCounter = Counter.builder("http.client.requests")
.description("Total HTTP requests")
.register(meterRegistry);
this.requestTimer = Timer.builder("http.client.request.duration")
.description("HTTP request duration")
.register(meterRegistry);
this.connectionGauge = Gauge.builder("http.client.connections.active")
.description("Active HTTP connections")
.register(meterRegistry, this, client -> getActiveConnectionCount());
}
// Implementation integrates AWS SDK metrics with Micrometer
}The metrics system provides comprehensive visibility into HTTP client behavior, enabling effective monitoring, troubleshooting, and performance optimization of AWS SDK HTTP operations.
Install with Tessl CLI
npx tessl i tessl/maven-software-amazon-awssdk--http-client-spi