Skip to content

Commit 79fbbef

Browse files
feat(http): Add Provider per implementation to limit reflection (#10870)
feat(http): Add Provider per implementation to limit reflection Co-authored-by: bruce.bujon <bruce.bujon@datadoghq.com>
1 parent cfeaf23 commit 79fbbef

File tree

9 files changed

+89
-354
lines changed

9 files changed

+89
-354
lines changed

components/http/http-api/src/main/java/datadog/http/client/HttpClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public interface HttpClient {
4141
* @return a new http client builder
4242
*/
4343
static Builder newBuilder() {
44-
return HttpProviders.newClientBuilder();
44+
return HttpProviders.get().newClientBuilder();
4545
}
4646

4747
/** Builder for constructing {@link HttpClient} instances. */
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package datadog.http.client;
2+
3+
import java.net.URI;
4+
import java.nio.ByteBuffer;
5+
import java.util.List;
6+
7+
/** Factory class providing the various HTTP client class implementations. */
8+
public abstract class HttpProvider {
9+
public abstract HttpClient.Builder newClientBuilder();
10+
11+
public abstract HttpRequest.Builder newRequestBuilder();
12+
13+
public abstract HttpUrl.Builder newUrlBuilder();
14+
15+
public abstract HttpUrl httpUrlParse(String url);
16+
17+
public abstract HttpUrl httpUrlFrom(URI uri);
18+
19+
public abstract HttpRequestBody requestBodyOfString(String content);
20+
21+
public abstract HttpRequestBody requestBodyOfBytes(byte[] bytes);
22+
23+
public abstract HttpRequestBody requestBodyOfByteBuffers(List<ByteBuffer> buffers);
24+
25+
public abstract HttpRequestBody requestBodyGzip(HttpRequestBody body);
26+
27+
public abstract HttpRequestBody.MultipartBuilder requestBodyMultipart();
28+
}
Lines changed: 28 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,29 @@
11
package datadog.http.client;
22

3-
import static java.util.Objects.requireNonNull;
4-
53
import de.thetaphi.forbiddenapis.SuppressForbidden;
6-
import edu.umd.cs.findbugs.annotations.NonNull;
7-
import java.lang.reflect.Constructor;
8-
import java.lang.reflect.Method;
9-
import java.net.URI;
10-
import java.nio.ByteBuffer;
11-
import java.util.List;
124

135
/**
14-
* Factory class providing HTTP client implementations with automatic fallback support.
15-
*
16-
* <p>This class acts as a provider abstraction layer that dynamically discovers and instantiates
17-
* HTTP client implementations at runtime using reflection. It supports two modes of operation: a
18-
* default mode using JDK-based HTTP clients and a compatibility mode using OkHttp-based clients.
6+
* Static factory class for obtaining HTTP provider implementations.
197
*
20-
* <p>The provider uses lazy initialization and caching of reflection metadata (constructors and
21-
* methods) to minimize performance overhead. All cached references are stored in volatile fields to
22-
* ensure thread-safe access in concurrent environments.
8+
* <p>This class provides a singleton access point to an {@link HttpProvider} instance, which serves
9+
* as a factory for creating HTTP client components such as clients, requests, URLs, and request
10+
* bodies.
2311
*
24-
* <p>When a component is requested (e.g., client builder, request builder, URL parser), the class
25-
* attempts to locate the appropriate implementation by searching for specific class names in the
26-
* classpath. If the default implementation is unavailable or if compatibility mode is enabled, it
27-
* falls back to alternative implementations.
12+
* <p>The provider selection follows a hierarchical fallback strategy: - First attempts to load the
13+
* JDK-based HTTP provider implementation - Falls back to the OkHttp-based provider if the JDK
14+
* version is not available or incompatible - Can be forced into compatibility mode to skip the JDK
15+
* provider and use OkHttp directly
2816
*
29-
* <p>Thread Safety: This class is thread-safe. The volatile fields ensure visibility of cached
30-
* reflection metadata across threads, and the lazy initialization pattern is safe for concurrent
31-
* access.
17+
* <p>The selected provider is cached after the first access for performance. This class is
18+
* thread-safe and all methods can be safely called from multiple threads.
3219
*/
3320
public final class HttpProviders {
21+
private static final String JDK_HTTP_PROVIDER_CLASS_NAME =
22+
"datadog.http.client.jdk.JdkHttpProvider";
23+
private static final String OKHTTP_PROVIDER_CLASS_NAME =
24+
"datadog.http.client.okhttp.OkHttpProvider";
3425
private static volatile boolean compatibilityMode = false;
35-
36-
private static volatile Constructor<?> HTTP_CLIENT_BUILDER_CONSTRUCTOR;
37-
private static volatile Constructor<?> HTTP_REQUEST_BUILDER_CONSTRUCTOR;
38-
private static volatile Constructor<?> HTTP_URL_BUILDER_CONSTRUCTOR;
39-
private static volatile Method HTTP_URL_PARSE_METHOD;
40-
private static volatile Method HTTP_URL_FROM_METHOD;
41-
private static volatile Method HTTP_REQUEST_BODY_OF_STRING_METHOD;
42-
private static volatile Method HTTP_REQUEST_BODY_OF_BYTES_METHOD;
43-
private static volatile Method HTTP_REQUEST_BODY_OF_BYTE_BUFFERS_METHOD;
44-
private static volatile Method HTTP_REQUEST_BODY_GZIP_METHOD;
45-
private static volatile Constructor<?> HTTP_MULTIPART_BUILDER_CONSTRUCTOR;
26+
private static HttpProvider provider;
4627

4728
private HttpProviders() {}
4829

@@ -52,224 +33,42 @@ public static void forceCompatClient() {
5233
return;
5334
}
5435
compatibilityMode = true;
55-
// Clear all references to make sure to reload them
56-
HTTP_CLIENT_BUILDER_CONSTRUCTOR = null;
57-
HTTP_REQUEST_BUILDER_CONSTRUCTOR = null;
58-
HTTP_URL_BUILDER_CONSTRUCTOR = null;
59-
HTTP_URL_PARSE_METHOD = null;
60-
HTTP_URL_FROM_METHOD = null;
61-
HTTP_REQUEST_BODY_OF_STRING_METHOD = null;
62-
HTTP_REQUEST_BODY_OF_BYTES_METHOD = null;
63-
HTTP_REQUEST_BODY_OF_BYTE_BUFFERS_METHOD = null;
64-
HTTP_REQUEST_BODY_GZIP_METHOD = null;
65-
HTTP_MULTIPART_BUILDER_CONSTRUCTOR = null;
66-
}
67-
68-
static HttpClient.Builder newClientBuilder() {
69-
if (HTTP_CLIENT_BUILDER_CONSTRUCTOR == null) {
70-
HTTP_CLIENT_BUILDER_CONSTRUCTOR =
71-
findConstructor(
72-
"datadog.http.client.jdk.JdkHttpClient$Builder",
73-
"datadog.http.client.okhttp.OkHttpClient$Builder");
74-
}
75-
try {
76-
return (HttpClient.Builder) HTTP_CLIENT_BUILDER_CONSTRUCTOR.newInstance();
77-
} catch (ReflectiveOperationException e) {
78-
throw new RuntimeException("Failed to call constructor", e);
79-
}
80-
}
81-
82-
static HttpRequest.Builder newRequestBuilder() {
83-
if (HTTP_REQUEST_BUILDER_CONSTRUCTOR == null) {
84-
HTTP_REQUEST_BUILDER_CONSTRUCTOR =
85-
findConstructor(
86-
"datadog.http.client.jdk.JdkHttpRequest$Builder",
87-
"datadog.http.client.okhttp.OkHttpRequest$Builder");
88-
}
89-
try {
90-
return (HttpRequest.Builder) HTTP_REQUEST_BUILDER_CONSTRUCTOR.newInstance();
91-
} catch (ReflectiveOperationException e) {
92-
throw new RuntimeException("Failed to call constructor", e);
93-
}
94-
}
95-
96-
static HttpUrl.Builder newUrlBuilder() {
97-
if (HTTP_URL_BUILDER_CONSTRUCTOR == null) {
98-
HTTP_URL_BUILDER_CONSTRUCTOR =
99-
findConstructor(
100-
"datadog.http.client.jdk.JdkHttpUrl$Builder",
101-
"datadog.http.client.okhttp.OkHttpUrl$Builder");
102-
}
103-
try {
104-
return (HttpUrl.Builder) HTTP_URL_BUILDER_CONSTRUCTOR.newInstance();
105-
} catch (ReflectiveOperationException e) {
106-
throw new RuntimeException("Failed to call constructor", e);
107-
}
108-
}
109-
110-
static HttpUrl httpUrlParse(String url) {
111-
if (HTTP_URL_PARSE_METHOD == null) {
112-
HTTP_URL_PARSE_METHOD =
113-
findMethod(
114-
"datadog.http.client.jdk.JdkHttpUrl",
115-
"datadog.http.client.okhttp.OkHttpUrl",
116-
"parse",
117-
String.class);
118-
}
119-
try {
120-
return (HttpUrl) HTTP_URL_PARSE_METHOD.invoke(null, url);
121-
} catch (ReflectiveOperationException e) {
122-
if (e.getCause() instanceof IllegalArgumentException) {
123-
throw (IllegalArgumentException) e.getCause();
124-
}
125-
throw new RuntimeException("Failed to call parse method", e);
126-
}
127-
}
128-
129-
static HttpUrl httpUrlFrom(URI uri) {
130-
if (HTTP_URL_FROM_METHOD == null) {
131-
HTTP_URL_FROM_METHOD =
132-
findMethod(
133-
"datadog.http.client.jdk.JdkHttpUrl",
134-
"datadog.http.client.okhttp.OkHttpUrl",
135-
"from",
136-
URI.class);
137-
}
138-
try {
139-
return (HttpUrl) HTTP_URL_FROM_METHOD.invoke(null, uri);
140-
} catch (ReflectiveOperationException e) {
141-
throw new RuntimeException("Failed to call from method", e);
142-
}
143-
}
144-
145-
static HttpRequestBody requestBodyOfString(String content) {
146-
requireNonNull(content, "content");
147-
if (HTTP_REQUEST_BODY_OF_STRING_METHOD == null) {
148-
HTTP_REQUEST_BODY_OF_STRING_METHOD =
149-
findMethod(
150-
"datadog.http.client.jdk.JdkHttpRequestBody",
151-
"datadog.http.client.okhttp.OkHttpRequestBody",
152-
"ofString",
153-
String.class);
154-
}
155-
try {
156-
return (HttpRequestBody) HTTP_REQUEST_BODY_OF_STRING_METHOD.invoke(null, content);
157-
} catch (ReflectiveOperationException e) {
158-
throw new RuntimeException("Failed to call ofString method", e);
159-
}
160-
}
161-
162-
static HttpRequestBody requestBodyOfBytes(byte[] bytes) {
163-
requireNonNull(bytes, "bytes");
164-
if (HTTP_REQUEST_BODY_OF_BYTES_METHOD == null) {
165-
HTTP_REQUEST_BODY_OF_BYTES_METHOD =
166-
findMethod(
167-
"datadog.http.client.jdk.JdkHttpRequestBody",
168-
"datadog.http.client.okhttp.OkHttpRequestBody",
169-
"ofBytes",
170-
byte[].class);
171-
}
172-
try {
173-
return (HttpRequestBody) HTTP_REQUEST_BODY_OF_BYTES_METHOD.invoke(null, (Object) bytes);
174-
} catch (ReflectiveOperationException e) {
175-
throw new RuntimeException("Failed to call ofBytes method", e);
176-
}
177-
}
178-
179-
static HttpRequestBody requestBodyOfByteBuffers(List<ByteBuffer> buffers) {
180-
requireNonNull(buffers, "buffers");
181-
if (HTTP_REQUEST_BODY_OF_BYTE_BUFFERS_METHOD == null) {
182-
HTTP_REQUEST_BODY_OF_BYTE_BUFFERS_METHOD =
183-
findMethod(
184-
"datadog.http.client.jdk.JdkHttpRequestBody",
185-
"datadog.http.client.okhttp.OkHttpRequestBody",
186-
"ofByteBuffers",
187-
List.class);
188-
}
189-
try {
190-
return (HttpRequestBody) HTTP_REQUEST_BODY_OF_BYTE_BUFFERS_METHOD.invoke(null, buffers);
191-
} catch (ReflectiveOperationException e) {
192-
throw new RuntimeException("Failed to call ofByteBuffers method", e);
193-
}
194-
}
195-
196-
static HttpRequestBody requestBodyGzip(HttpRequestBody body) {
197-
requireNonNull(body, "body");
198-
if (HTTP_REQUEST_BODY_GZIP_METHOD == null) {
199-
HTTP_REQUEST_BODY_GZIP_METHOD =
200-
findMethod(
201-
"datadog.http.client.jdk.JdkHttpRequestBody",
202-
"datadog.http.client.okhttp.OkHttpRequestBody",
203-
"ofGzip",
204-
HttpRequestBody.class);
205-
}
206-
try {
207-
return (HttpRequestBody) HTTP_REQUEST_BODY_GZIP_METHOD.invoke(null, body);
208-
} catch (ReflectiveOperationException e) {
209-
throw new RuntimeException("Failed to call ofGzip method", e);
210-
}
211-
}
212-
213-
static HttpRequestBody.MultipartBuilder requestBodyMultipart() {
214-
if (HTTP_MULTIPART_BUILDER_CONSTRUCTOR == null) {
215-
HTTP_MULTIPART_BUILDER_CONSTRUCTOR =
216-
findConstructor(
217-
"datadog.http.client.jdk.JdkHttpRequestBody$MultipartBuilder",
218-
"datadog.http.client.okhttp.OkHttpRequestBody$MultipartBuilder");
219-
}
220-
try {
221-
return (HttpRequestBody.MultipartBuilder) HTTP_MULTIPART_BUILDER_CONSTRUCTOR.newInstance();
222-
} catch (ReflectiveOperationException e) {
223-
throw new RuntimeException("Failed to call multipart builder constructor", e);
224-
}
36+
provider = null;
22537
}
22638

227-
private static Method findMethod(
228-
String defaultClientClass,
229-
String compatClientClass,
230-
String name,
231-
Class<?>... parameterTypes) {
232-
Class<?> clientClass = findClientClass(defaultClientClass, compatClientClass);
233-
try {
234-
return clientClass.getMethod(name, parameterTypes);
235-
} catch (NoSuchMethodException e) {
236-
throw new RuntimeException("Failed to find " + name + " method", e);
237-
}
238-
}
239-
240-
private static Constructor<?> findConstructor(
241-
String defaultClientClass, String compatClientClass) {
242-
Class<?> clientClass = findClientClass(defaultClientClass, compatClientClass);
243-
try {
244-
return clientClass.getConstructor();
245-
} catch (NoSuchMethodException e) {
246-
throw new RuntimeException("Failed to find constructor", e);
39+
public static HttpProvider get() {
40+
if (provider == null) {
41+
provider = findProvider();
24742
}
43+
return provider;
24844
}
24945

25046
@SuppressForbidden // Class#forName(String) used to dynamically load the http API implementation
251-
@NonNull
252-
private static Class<?> findClientClass(String defaultClientClass, String compatClientClass) {
47+
private static HttpProvider findProvider() {
25348
Class<?> clazz = null;
25449
// Load the default client class
25550
if (!compatibilityMode) {
25651
try {
257-
clazz = Class.forName(defaultClientClass);
52+
clazz = Class.forName(JDK_HTTP_PROVIDER_CLASS_NAME);
25853
} catch (ClassNotFoundException | UnsupportedClassVersionError ignored) {
25954
compatibilityMode = true;
26055
}
26156
}
26257
// If not loaded, load the compat client class
26358
if (clazz == null) {
26459
try {
265-
clazz = Class.forName(compatClientClass);
60+
clazz = Class.forName(OKHTTP_PROVIDER_CLASS_NAME);
26661
} catch (ClassNotFoundException ignored) {
26762
}
26863
}
26964
// If no class loaded, raise the illegal state
27065
if (clazz == null) {
27166
throw new IllegalStateException("No http client implementation found");
27267
}
273-
return clazz;
68+
try {
69+
return (HttpProvider) clazz.getDeclaredConstructor().newInstance();
70+
} catch (ReflectiveOperationException e) {
71+
throw new IllegalStateException("No http client implementation found", e);
72+
}
27473
}
27574
}

components/http/http-api/src/main/java/datadog/http/client/HttpRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public interface HttpRequest {
5757
* @return a new builder
5858
*/
5959
static Builder newBuilder() {
60-
return HttpProviders.newRequestBuilder();
60+
return HttpProviders.get().newRequestBuilder();
6161
}
6262

6363
/** Builder for constructing {@link HttpRequest} instances. */

0 commit comments

Comments
 (0)