20
20
package org .elasticsearch .client ;
21
21
22
22
import org .apache .http .Header ;
23
+ import org .apache .http .HttpRequest ;
23
24
import org .apache .http .client .config .RequestConfig ;
24
25
import org .apache .http .impl .client .CloseableHttpClient ;
25
26
import org .apache .http .impl .client .HttpClientBuilder ;
26
27
import org .apache .http .impl .nio .client .CloseableHttpAsyncClient ;
27
28
import org .apache .http .impl .nio .client .HttpAsyncClientBuilder ;
28
29
import org .apache .http .nio .conn .SchemeIOSessionStrategy ;
30
+ import org .apache .http .protocol .HttpContext ;
31
+ import org .apache .http .util .VersionInfo ;
29
32
30
33
import javax .net .ssl .SSLContext ;
34
+ import java .io .IOException ;
35
+ import java .io .InputStream ;
31
36
import java .security .AccessController ;
32
37
import java .security .NoSuchAlgorithmException ;
33
38
import java .security .PrivilegedAction ;
34
39
import java .util .List ;
40
+ import java .util .Locale ;
35
41
import java .util .Objects ;
42
+ import java .util .Properties ;
36
43
37
44
/**
38
45
* Helps creating a new {@link RestClient}. Allows to set the most common http client configuration options when internally
@@ -45,6 +52,11 @@ public final class RestClientBuilder {
45
52
public static final int DEFAULT_MAX_CONN_PER_ROUTE = 10 ;
46
53
public static final int DEFAULT_MAX_CONN_TOTAL = 30 ;
47
54
55
+ static final String VERSION ;
56
+ static final String META_HEADER_NAME = "X-Elastic-Client-Meta" ;
57
+ private static final String META_HEADER_VALUE ;
58
+ private static final String USER_AGENT_HEADER_VALUE ;
59
+
48
60
private static final Header [] EMPTY_HEADERS = new Header [0 ];
49
61
50
62
private final List <Node > nodes ;
@@ -56,6 +68,48 @@ public final class RestClientBuilder {
56
68
private NodeSelector nodeSelector = NodeSelector .ANY ;
57
69
private boolean strictDeprecationMode = false ;
58
70
private boolean compressionEnabled = false ;
71
+ private boolean metaHeaderEnabled = true ;
72
+
73
+ static {
74
+
75
+ // Never fail on unknown version, even if an environment messed up their classpath enough that we can't find it.
76
+ // Better have incomplete telemetry than crashing user applications.
77
+ String version = null ;
78
+ try (InputStream is = RestClient .class .getResourceAsStream ("version.properties" )) {
79
+ if (is != null ) {
80
+ Properties versions = new Properties ();
81
+ versions .load (is );
82
+ version = versions .getProperty ("elasticsearch-client" );
83
+ }
84
+ } catch (IOException e ) {
85
+ // Keep version unknown
86
+ }
87
+
88
+ if (version == null ) {
89
+ version = "" ; // unknown values are reported as empty strings in X-Elastic-Client-Meta
90
+ }
91
+
92
+ VERSION = version ;
93
+
94
+ USER_AGENT_HEADER_VALUE = String .format (Locale .ROOT , "elasticsearch-java/%s (Java/%s)" ,
95
+ VERSION .isEmpty () ? "Unknown" : VERSION , System .getProperty ("java.version" ));
96
+
97
+ VersionInfo httpClientVersion = null ;
98
+ try {
99
+ httpClientVersion = AccessController .doPrivileged ((PrivilegedAction <VersionInfo >)() ->
100
+ VersionInfo .loadVersionInfo ("org.apache.http.nio.client" , HttpAsyncClientBuilder .class .getClassLoader ())
101
+ );
102
+ } catch (Exception e ) {
103
+ // Keep unknown
104
+ }
105
+
106
+ // service, language, transport, followed by additional information
107
+ META_HEADER_VALUE = "es=" + VERSION +
108
+ ",jv=" + System .getProperty ("java.specification.version" ) +
109
+ ",t=" + VERSION +
110
+ ",hc=" + (httpClientVersion == null ? "" : httpClientVersion .getRelease ()) +
111
+ LanguageRuntimeVersions .getRuntimeMetadata ();
112
+ }
59
113
60
114
/**
61
115
* Creates a new builder instance and sets the hosts that the client will send requests to.
@@ -191,6 +245,17 @@ public RestClientBuilder setCompressionEnabled(boolean compressionEnabled) {
191
245
return this ;
192
246
}
193
247
248
+ /**
249
+ * Whether to send a {@code X-Elastic-Client-Meta} header that describes the runtime environment. It contains
250
+ * information that is similar to what could be found in {@code User-Agent}. Using a separate header allows
251
+ * applications to use {@code User-Agent} for their own needs, e.g. to identify application version or other
252
+ * environment information. Defaults to {@code true}.
253
+ */
254
+ public RestClientBuilder setMetaHeaderEnabled (boolean metadataEnabled ) {
255
+ this .metaHeaderEnabled = metadataEnabled ;
256
+ return this ;
257
+ }
258
+
194
259
/**
195
260
* Creates a new {@link RestClient} based on the provided configuration.
196
261
*/
@@ -220,11 +285,20 @@ private CloseableHttpAsyncClient createHttpClient() {
220
285
//default settings for connection pooling may be too constraining
221
286
.setMaxConnPerRoute (DEFAULT_MAX_CONN_PER_ROUTE ).setMaxConnTotal (DEFAULT_MAX_CONN_TOTAL )
222
287
.setSSLContext (SSLContext .getDefault ())
288
+ .setUserAgent (USER_AGENT_HEADER_VALUE )
223
289
.setTargetAuthenticationStrategy (new PersistentCredentialsAuthenticationStrategy ());
224
290
if (httpClientConfigCallback != null ) {
225
291
httpClientBuilder = httpClientConfigCallback .customizeHttpClient (httpClientBuilder );
226
292
}
227
293
294
+ // Always add metadata header last so that it's not overwritten
295
+ httpClientBuilder .addInterceptorLast ((HttpRequest request , HttpContext context ) -> {
296
+ if (metaHeaderEnabled ) {
297
+ request .setHeader (META_HEADER_NAME , META_HEADER_VALUE );
298
+ } else {
299
+ request .removeHeaders (META_HEADER_NAME );
300
+ }
301
+ });
228
302
final HttpAsyncClientBuilder finalBuilder = httpClientBuilder ;
229
303
return AccessController .doPrivileged ((PrivilegedAction <CloseableHttpAsyncClient >) finalBuilder ::build );
230
304
} catch (NoSuchAlgorithmException e ) {
0 commit comments