Give Every Service a Shared HTTP Client
Every team configures HTTP clients differently, and two incidents last quarter came from calls with no timeout. This adds platform-http, a shared OkHttp client with a 30 second timeout, retries with exponential backoff on IOExceptions and 5xx responses, and a metrics interceptor. platform-core 5.0 registers it as the default client, so the BOM bump moves all 41 services onto it at their next build with no code changes.
Aplatform-http/src/main/java/com/acme/platform/http/PlatformHttpClient.java1 of 3
Files Changed
platform-http/src/main/java/com/acme/platform/http/
platform-bom/
platform-http/src/main/java/com/acme/platform/http/PlatformHttpClient.java
Java · Click a line number to comment · Tap a line to comment@@ -0,0 +1,46 @@
Added: package com.acme.platform.http;
Added:
Added: import java.io.IOException;
Added: import java.time.Duration;
Added: import okhttp3.Interceptor;
Added: import okhttp3.OkHttpClient;
Added: import okhttp3.Response;
Added:
Added: /** The HTTP client every service gets from the platform BOM. */
Added: public final class PlatformHttpClient {
Added: static final int MAX_RETRIES = 5;
Added: static final Duration TIMEOUT = Duration.ofSeconds(30);
Added:
Added: public static OkHttpClient create() {
Added: return new OkHttpClient.Builder()
Added: .callTimeout(TIMEOUT)
Added: .addInterceptor(PlatformHttpClient::retry)
Added: .addInterceptor(new MetricsInterceptor())
Added: .build();
Added: }
Added:
Added: static Response retry(Interceptor.Chain chain) throws IOException {
Added: IOException last = null;
Added: for (int attempt = 0; attempt <= MAX_RETRIES; attempt++) {
Added: try {
Added: Response response = chain.proceed(chain.request());
Added: if (response.code() < 500) {
Added: return response;
Added: }
Added: response.close();
Added: } catch (IOException e) {
Added: last = e;
Added: }
Added: sleep(100L << attempt);
Added: }
Added: throw last != null ? last : new IOException("retries exhausted");
Added: }
Added:
Added: private static void sleep(long millis) {
Added: try {
Added: Thread.sleep(millis);
Added: } catch (InterruptedException e) {
Added: Thread.currentThread().interrupt();
Added: }
Added: }
Added: }
platform-http/src/main/java/com/acme/platform/http/MetricsInterceptor.java
Java · Click a line number to comment · Tap a line to comment@@ -0,0 +1,20 @@
Added: package com.acme.platform.http;
Added:
Added: import io.micrometer.core.instrument.Metrics;
Added: import java.io.IOException;
Added: import java.util.concurrent.TimeUnit;
Added: import okhttp3.Interceptor;
Added: import okhttp3.Response;
Added:
Added: final class MetricsInterceptor implements Interceptor {
Added: @Override
Added: public Response intercept(Chain chain) throws IOException {
Added: long start = System.nanoTime();
Added: Response response = chain.proceed(chain.request());
Added: Metrics.timer("http.client.requests",
Added: "url", chain.request().url().toString(),
Added: "status", String.valueOf(response.code()))
Added: .record(System.nanoTime() - start, TimeUnit.NANOSECONDS);
Added: return response;
Added: }
Added: }
platform-bom/pom.xml
XML · Click a line number to comment · Tap a line to comment@@ -14,8 +14,13 @@
<dependencies>
<dependency>
<groupId>com.acme.platform</groupId>
<artifactId>platform-core</artifactId>
Removed: <version>4.12.0</version>
Added: <version>5.0.0</version>
Added: </dependency>
Added: <dependency>
Added: <groupId>com.acme.platform</groupId>
Added: <artifactId>platform-http</artifactId>
Added: <version>1.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>