11package datadog .http .client ;
22
3- import static java .util .Objects .requireNonNull ;
4-
53import 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 */
3320public 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}
0 commit comments