Skip to content

Commit fec16ef

Browse files
authored
Add metadata header on requests (#5190)
Implement meta header for client requests
1 parent b125288 commit fec16ef

31 files changed

+1303
-78
lines changed

docs/client-concepts/connection/configuration-options.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ Ensures the response bytes are always available on the `ElasticsearchResponse<T>
6464
+
6565
IMPORTANT: Depending on the registered serializer, this may cause the response to be buffered in memory first, potentially affecting performance.
6666

67+
`DisableMetaHeader`::
68+
69+
Disables the meta header which is included on all requests by default. This header contains lightweight information about the client and runtime.
70+
6771
`DisablePing`::
6872

6973
When a node is used for the very first time or when it's used for the first time after it has been marked dead a ping with a very low timeout is send to the node to make sure that when it's still dead it reports it as fast as possible. You can disable these pings globally here if you rather have it fail on the possible slower original request
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
7+
namespace Elasticsearch.Net
8+
{
9+
internal static class RequestParametersExtensions
10+
{
11+
internal static void SetRequestMetaData(this IRequestParameters parameters, RequestMetaData requestMetaData)
12+
{
13+
if (parameters is null)
14+
throw new ArgumentNullException(nameof(parameters));
15+
16+
if (requestMetaData is null)
17+
throw new ArgumentNullException(nameof(requestMetaData));
18+
19+
parameters.RequestConfiguration ??= new RequestConfiguration();
20+
21+
parameters.RequestConfiguration.RequestMetaData = requestMetaData;
22+
}
23+
}
24+
}

src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,36 +33,7 @@ public class ConnectionConfiguration : ConnectionConfiguration<ConnectionConfigu
3333
/// As the old curl based handler is known to bleed TCP connections:
3434
/// <para>https://github.com/dotnet/runtime/issues/22366</para>
3535
/// </summary>
36-
private static bool UsingCurlHandler
37-
{
38-
get
39-
{
40-
#if !DOTNETCORE
41-
return false;
42-
#else
43-
var curlHandlerExists = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.CurlHandler") != null;
44-
if (!curlHandlerExists) return false;
45-
46-
var socketsHandlerExists = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.SocketsHttpHandler") != null;
47-
// running on a .NET core version with CurlHandler, before the existence of SocketsHttpHandler.
48-
// Must be using CurlHandler.
49-
if (!socketsHandlerExists) return true;
50-
51-
if (AppContext.TryGetSwitch("System.Net.Http.UseSocketsHttpHandler", out var isEnabled))
52-
return !isEnabled;
53-
54-
var environmentVariable =
55-
Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER");
56-
57-
// SocketsHandler exists and no environment variable exists to disable it.
58-
// Must be using SocketsHandler and not CurlHandler
59-
if (environmentVariable == null) return false;
60-
61-
return environmentVariable.Equals("false", StringComparison.OrdinalIgnoreCase) ||
62-
environmentVariable.Equals("0");
63-
#endif
64-
}
65-
}
36+
private static bool UsingCurlHandler => ConnectionInfo.UsingCurlHandler;
6637

6738
/// <summary>
6839
/// The default ping timeout. Defaults to 2 seconds
@@ -153,7 +124,6 @@ public ConnectionConfiguration(IConnectionPool connectionPool, IElasticsearchSer
153124
/// <param name="serializer">A serializer implementation used to serialize requests and deserialize responses</param>
154125
public ConnectionConfiguration(IConnectionPool connectionPool, IConnection connection, IElasticsearchSerializer serializer)
155126
: base(connectionPool, connection, serializer) { }
156-
157127
}
158128

159129
[Browsable(false)]
@@ -176,6 +146,7 @@ public abstract class ConnectionConfiguration<T> : IConnectionConfigurationValue
176146
private TimeSpan? _deadTimeout;
177147
private bool _disableAutomaticProxyDetection;
178148
private bool _disableDirectStreaming;
149+
private bool _disableMetaHeader;
179150
private bool _disablePings;
180151
private bool _enableHttpCompression;
181152
private bool _enableHttpPipelining = true;
@@ -207,7 +178,7 @@ public abstract class ConnectionConfiguration<T> : IConnectionConfigurationValue
207178
private bool _enableThreadPoolStats;
208179

209180
private string _userAgent = ConnectionConfiguration.DefaultUserAgent;
210-
private Func<HttpMethod, int, bool> _statusCodeToResponseSuccess;
181+
private readonly Func<HttpMethod, int, bool> _statusCodeToResponseSuccess;
211182

212183
protected ConnectionConfiguration(IConnectionPool connectionPool, IConnection connection, IElasticsearchSerializer requestResponseSerializer)
213184
{
@@ -234,7 +205,6 @@ protected ConnectionConfiguration(IConnectionPool connectionPool, IConnection co
234205
_apiKeyAuthCredentials = cloudPool.ApiKeyCredentials;
235206
_enableHttpCompression = true;
236207
}
237-
238208
}
239209

240210
protected IElasticsearchSerializer UseThisRequestResponseSerializer { get; set; }
@@ -248,6 +218,7 @@ protected ConnectionConfiguration(IConnectionPool connectionPool, IConnection co
248218
TimeSpan? IConnectionConfigurationValues.DeadTimeout => _deadTimeout;
249219
bool IConnectionConfigurationValues.DisableAutomaticProxyDetection => _disableAutomaticProxyDetection;
250220
bool IConnectionConfigurationValues.DisableDirectStreaming => _disableDirectStreaming;
221+
bool IConnectionConfigurationValues.DisableMetaHeader => _disableMetaHeader;
251222
bool IConnectionConfigurationValues.DisablePings => _disablePings;
252223
bool IConnectionConfigurationValues.EnableHttpCompression => _enableHttpCompression;
253224
NameValueCollection IConnectionConfigurationValues.Headers => _headers;
@@ -286,6 +257,8 @@ protected ConnectionConfiguration(IConnectionPool connectionPool, IConnection co
286257
bool IConnectionConfigurationValues.TransferEncodingChunked => _transferEncodingChunked;
287258
bool IConnectionConfigurationValues.EnableTcpStats => _enableTcpStats;
288259
bool IConnectionConfigurationValues.EnableThreadPoolStats => _enableThreadPoolStats;
260+
261+
MetaHeaderProvider IConnectionConfigurationValues.MetaHeaderProvider { get; } = new MetaHeaderProvider();
289262

290263
void IDisposable.Dispose() => DisposeManagedResources();
291264

@@ -368,6 +341,12 @@ public T SniffOnConnectionFault(bool sniffsOnConnectionFault = true) =>
368341
/// </summary>
369342
public T DisableAutomaticProxyDetection(bool disable = true) => Assign(disable, (a, v) => a._disableAutomaticProxyDetection = v);
370343

344+
/// <summary>
345+
/// Disables the meta header which is included on all requests by default. This header contains lightweight information
346+
/// about the client and runtime.
347+
/// </summary>
348+
public T DisableMetaHeader(bool disable = true) => Assign(disable, (a, v) => a._disableMetaHeader = v);
349+
371350
/// <summary>
372351
/// Instead of following a c/go like error checking on response.IsValid do throw an exception (except when <see cref="IApiCallDetails.SuccessOrKnownError"/> is false)
373352
/// on the client when a call resulted in an exception on either the client or the Elasticsearch server.
@@ -432,11 +411,11 @@ public T SniffOnConnectionFault(bool sniffsOnConnectionFault = true) =>
432411

433412
/// <summary>
434413
/// DnsRefreshTimeout for the connections. Defaults to 5 minutes.
435-
#if DOTNETCORE
414+
#if DOTNETCORE
436415
/// <para>Will create new instances of <see cref="System.Net.Http.HttpClient"/> after this timeout to force DNS updates</para>
437-
#else
416+
#else
438417
/// <para>Will set both <see cref="System.Net.ServicePointManager.DnsRefreshTimeout"/> and <see cref="System.Net.ServicePointManager.ConnectionLeaseTimeout "/>
439-
#endif
418+
#endif
440419
/// </summary>
441420
public T DnsRefreshTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._dnsRefreshTimeout = v);
442421

src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public interface IConnectionConfigurationValues : IDisposable
7777
/// </summary>
7878
bool DisableDirectStreaming { get; }
7979

80+
bool DisableMetaHeader { get; }
81+
8082
/// <summary>
8183
/// This signals that we do not want to send initial pings to unknown/previously dead nodes
8284
/// and just send the call straightaway
@@ -273,5 +275,10 @@ public interface IConnectionConfigurationValues : IDisposable
273275
/// Enable statistics about thread pools to be collected when making a request
274276
/// </summary>
275277
bool EnableThreadPoolStats { get; }
278+
279+
/// <summary>
280+
/// Produces the client meta header for a request.
281+
/// </summary>
282+
MetaHeaderProvider MetaHeaderProvider { get; }
276283
}
277284
}

src/Elasticsearch.Net/Configuration/RequestConfiguration.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ public interface IRequestConfiguration
126126

127127
/// <inheritdoc cref="IConnectionConfigurationValues.EnableThreadPoolStats"/>
128128
bool? EnableThreadPoolStats { get; set; }
129+
130+
/// <summary>
131+
/// Holds additional meta data about the request.
132+
/// </summary>
133+
RequestMetaData RequestMetaData { get; set; }
129134
}
130135

131136
public class RequestConfiguration : IRequestConfiguration
@@ -172,6 +177,8 @@ public class RequestConfiguration : IRequestConfiguration
172177
public bool? EnableTcpStats { get; set; }
173178
/// <inheritdoc />
174179
public bool? EnableThreadPoolStats { get; set; }
180+
/// <inheritdoc />
181+
public RequestMetaData RequestMetaData { get; set; }
175182
}
176183

177184
public class RequestConfigurationDescriptor : IRequestConfiguration
@@ -223,6 +230,7 @@ public RequestConfigurationDescriptor(IRequestConfiguration config)
223230
NameValueCollection IRequestConfiguration.Headers { get; set; }
224231
bool? IRequestConfiguration.EnableTcpStats { get; set; }
225232
bool? IRequestConfiguration.EnableThreadPoolStats { get; set; }
233+
RequestMetaData IRequestConfiguration.RequestMetaData { get; set; }
226234

227235
/// <summary>
228236
/// Submit the request on behalf in the context of a different shield user
@@ -406,5 +414,12 @@ public RequestConfigurationDescriptor EnableThreadPoolStats(bool? enableThreadPo
406414
Self.EnableThreadPoolStats = enableThreadPoolStats;
407415
return this;
408416
}
417+
418+
/// <inheritdoc cref="IRequestConfiguration.RequestMetaData" />
419+
internal RequestConfigurationDescriptor RequestMetaData(RequestMetaData metaData)
420+
{
421+
Self.RequestMetaData = metaData;
422+
return this;
423+
}
409424
}
410425
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
7+
namespace Elasticsearch.Net
8+
{
9+
internal static class RequestConfigurationExtensions
10+
{
11+
internal static void SetRequestMetaData(this IRequestConfiguration requestConfiguration, RequestMetaData requestMetaData)
12+
{
13+
if (requestConfiguration is null)
14+
throw new ArgumentNullException(nameof(requestConfiguration));
15+
16+
if (requestMetaData is null)
17+
throw new ArgumentNullException(nameof(requestMetaData));
18+
19+
requestConfiguration.RequestMetaData = requestMetaData;
20+
}
21+
}
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.Collections.Generic;
2+
3+
namespace Elasticsearch.Net
4+
{
5+
/// <summary>
6+
/// Holds meta data about a client request.
7+
/// </summary>
8+
public sealed class RequestMetaData
9+
{
10+
/// <summary>
11+
/// Reserved key for a meta data entry which identifies the helper which produced the request.
12+
/// </summary>
13+
internal const string HelperKey = "helper";
14+
15+
private Dictionary<string, string> _metaDataItems;
16+
17+
internal bool TryAddMetaData (string key, string value)
18+
{
19+
_metaDataItems ??= new Dictionary<string, string>();
20+
21+
#if NETSTANDARD2_1
22+
return _metaDataItems.TryAdd(key, value);
23+
#else
24+
if (_metaDataItems.ContainsKey(key))
25+
return false;
26+
27+
_metaDataItems.Add(key, value);
28+
return true;
29+
#endif
30+
}
31+
32+
public IReadOnlyDictionary<string, string> Items => _metaDataItems ?? EmptyReadOnly<string, string>.Dictionary;
33+
}
34+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
#if DOTNETCORE
7+
using System.Net.Http;
8+
#endif
9+
10+
namespace Elasticsearch.Net
11+
{
12+
public static class ConnectionInfo
13+
{
14+
public static bool UsingCurlHandler
15+
{
16+
get
17+
{
18+
#if !DOTNETCORE
19+
return false;
20+
#else
21+
var curlHandlerExists = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.CurlHandler") != null;
22+
if (!curlHandlerExists)
23+
return false;
24+
25+
var socketsHandlerExists = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.SocketsHttpHandler") != null;
26+
// running on a .NET core version with CurlHandler, before the existence of SocketsHttpHandler.
27+
// Must be using CurlHandler.
28+
if (!socketsHandlerExists)
29+
return true;
30+
31+
if (AppContext.TryGetSwitch("System.Net.Http.UseSocketsHttpHandler", out var isEnabled))
32+
return !isEnabled;
33+
34+
var environmentVariable =
35+
Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER");
36+
37+
// SocketsHandler exists and no environment variable exists to disable it.
38+
// Must be using SocketsHandler and not CurlHandler
39+
if (environmentVariable == null)
40+
return false;
41+
42+
return environmentVariable.Equals("false", StringComparison.OrdinalIgnoreCase) ||
43+
environmentVariable.Equals("0");
44+
#endif
45+
}
46+
}
47+
}
48+
}

src/Elasticsearch.Net/Connection/HttpConnection.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ internal class WebProxy : IWebProxy
3737
public bool IsBypassed(Uri host) => host.IsLoopback;
3838
}
3939

40-
4140
/// <summary> The default IConnection implementation. Uses <see cref="HttpClient" />.</summary>
4241
public class HttpConnection : IConnection
4342
{
@@ -139,6 +138,7 @@ public virtual async Task<TResponse> RequestAsync<TResponse>(RequestData request
139138
IDisposable receive = DiagnosticSources.SingletonDisposable;
140139
ReadOnlyDictionary<TcpState, int> tcpStats = null;
141140
ReadOnlyDictionary<string, ThreadPoolStatistics> threadPoolStats = null;
141+
requestData.IsAsync = true;
142142

143143
try
144144
{
@@ -333,6 +333,14 @@ protected virtual HttpRequestMessage CreateRequestMessage(RequestData requestDat
333333
if (!requestData.RunAs.IsNullOrEmpty())
334334
requestMessage.Headers.Add(RequestData.RunAsSecurityHeader, requestData.RunAs);
335335

336+
if (requestData.MetaHeaderProvider is object)
337+
{
338+
var value = requestData.MetaHeaderProvider.ProduceHeaderValue(requestData);
339+
340+
if (!string.IsNullOrEmpty(value))
341+
requestMessage.Headers.TryAddWithoutValidation(requestData.MetaHeaderProvider.HeaderName, value);
342+
}
343+
336344
return requestMessage;
337345
}
338346

src/Elasticsearch.Net/Connection/HttpWebRequestConnection.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ CancellationToken cancellationToken
111111

112112
try
113113
{
114+
requestData.IsAsync = true;
114115
var data = requestData.PostData;
115116
var request = CreateHttpWebRequest(requestData);
116117
using (cancellationToken.Register(() => request.Abort()))
@@ -238,6 +239,14 @@ protected virtual HttpWebRequest CreateWebRequest(RequestData requestData)
238239
if (requestData.Headers != null && requestData.Headers.HasKeys())
239240
request.Headers.Add(requestData.Headers);
240241

242+
if (requestData.MetaHeaderProvider is object)
243+
{
244+
var value = requestData.MetaHeaderProvider.ProduceHeaderValue(requestData);
245+
246+
if (!string.IsNullOrEmpty(value))
247+
request.Headers.Add(requestData.MetaHeaderProvider.HeaderName, requestData.MetaHeaderProvider.ProduceHeaderValue(requestData));
248+
}
249+
241250
var timeout = (int)requestData.RequestTimeout.TotalMilliseconds;
242251
request.Timeout = timeout;
243252
request.ReadWriteTimeout = timeout;

0 commit comments

Comments
 (0)