Skip to content

Commit 2ced72f

Browse files
committed
Add User-Agent string to API calls to Elasticsearch (#3784)
This commit adds a User-Agent to all API calls to Elasticsearch, that includes the client name and version, along with OS, Framework and assembly. Where System.Runtime.InteropServices.RuntimeInformation is available, use the properties of this type. For .NET 4.6.1 which does not support this type, use native Windows functions to determine the version of Windows, and Environment.OSVersion.VersionString for other platforms. The latter is not used for Windows because it can report incorrect version information depending on runtime version and service packs installed, and the context in which calls are executed i.e. it is not reliable. Add static readonly strings for default user agents for both the low level and high level clients (cherry picked from commit cb89735)
1 parent efb2a93 commit 2ced72f

File tree

9 files changed

+255
-15
lines changed

9 files changed

+255
-15
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
// https://raw.githubusercontent.com/dotnet/core-setup/master/src/managed/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs
5+
6+
using System.Runtime.InteropServices;
7+
8+
namespace Elasticsearch.Net
9+
{
10+
internal static class NativeMethods
11+
{
12+
public static class Windows
13+
{
14+
// This call avoids the shimming Windows does to report old versions
15+
[DllImport("ntdll")]
16+
private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation);
17+
18+
internal static string RtlGetVersion()
19+
{
20+
var osvi = new RTL_OSVERSIONINFOEX();
21+
osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi);
22+
if (RtlGetVersion(out osvi) == 0)
23+
return $"Microsoft Windows {osvi.dwMajorVersion}.{osvi.dwMinorVersion}.{osvi.dwBuildNumber}";
24+
25+
return null;
26+
}
27+
28+
[StructLayout(LayoutKind.Sequential)]
29+
internal struct RTL_OSVERSIONINFOEX
30+
{
31+
internal uint dwOSVersionInfoSize;
32+
internal uint dwMajorVersion;
33+
internal uint dwMinorVersion;
34+
internal uint dwBuildNumber;
35+
internal uint dwPlatformId;
36+
37+
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
38+
internal string szCSDVersion;
39+
}
40+
}
41+
}
42+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
#if NET461
3+
using System.Reflection;
4+
5+
namespace Elasticsearch.Net
6+
{
7+
internal static class RuntimeInformation
8+
{
9+
private static string _frameworkDescription;
10+
private static string _osDescription;
11+
12+
public static string FrameworkDescription
13+
{
14+
get
15+
{
16+
if (_frameworkDescription == null)
17+
{
18+
var assemblyFileVersionAttribute =
19+
(AssemblyFileVersionAttribute)typeof(object).GetTypeInfo().Assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute));
20+
_frameworkDescription = $".NET Framework {assemblyFileVersionAttribute.Version}";
21+
}
22+
return _frameworkDescription;
23+
}
24+
}
25+
26+
public static string OSDescription
27+
{
28+
get
29+
{
30+
if (_osDescription == null)
31+
{
32+
var platform = (int)Environment.OSVersion.Platform;
33+
var isWindows = platform != 4 && platform != 6 && platform != 128;
34+
if (isWindows)
35+
_osDescription = NativeMethods.Windows.RtlGetVersion() ?? "Microsoft Windows";
36+
else
37+
_osDescription = Environment.OSVersion.VersionString;
38+
}
39+
return _osDescription;
40+
}
41+
}
42+
}
43+
}
44+
#endif

src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,90 @@
77
using System.Diagnostics.CodeAnalysis;
88
using System.Net.Http;
99
using System.Net.Security;
10+
using System.Reflection;
1011
using System.Security;
1112
using System.Security.Cryptography.X509Certificates;
1213
using System.Threading;
13-
using Elasticsearch.Net.CrossPlatform;
14+
using System.Runtime.InteropServices;
1415
using Elasticsearch.Net.Extensions;
1516

1617
namespace Elasticsearch.Net
1718
{
1819
/// <summary>
19-
/// ConnectionConfiguration allows you to control how ElasticLowLevelClient behaves and where/how it connects
20-
/// to elasticsearch
20+
/// Allows you to control how <see cref="ElasticLowLevelClient"/> behaves and where/how it connects to Elasticsearch
2121
/// </summary>
2222
public class ConnectionConfiguration : ConnectionConfiguration<ConnectionConfiguration>
2323
{
24-
private static bool IsCurlHandler { get; } = typeof(HttpClientHandler).Assembly().GetType("System.Net.Http.CurlHandler") != null;
24+
private static bool IsCurlHandler { get; } = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.CurlHandler") != null;
2525

26+
/// <summary>
27+
/// The default ping timeout. Defaults to 2 seconds
28+
/// </summary>
2629
public static readonly TimeSpan DefaultPingTimeout = TimeSpan.FromSeconds(2);
30+
31+
/// <summary>
32+
/// The default ping timeout when the connection is over HTTPS. Defaults to
33+
/// 5 seconds
34+
/// </summary>
2735
public static readonly TimeSpan DefaultPingTimeoutOnSSL = TimeSpan.FromSeconds(5);
36+
37+
/// <summary>
38+
/// The default timeout before the client aborts a request to Elasticsearch.
39+
/// Defaults to 1 minute
40+
/// </summary>
2841
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(1);
42+
43+
/// <summary>
44+
/// The default connection limit for both Elasticsearch.Net and Nest. Defaults to <c>80</c> except for
45+
/// <see cref="HttpClientHandler"/> implementations based on curl, which defaults to
46+
/// <see cref="Environment.ProcessorCount"/>
47+
/// </summary>
2948
public static readonly int DefaultConnectionLimit = IsCurlHandler ? Environment.ProcessorCount : 80;
3049

50+
/// <summary>
51+
/// The default user agent for Elasticsearch.Net
52+
/// </summary>
53+
public static readonly string DefaultUserAgent = $"elasticsearch-net/{typeof(IConnectionConfigurationValues).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion} ({RuntimeInformation.OSDescription}; {RuntimeInformation.FrameworkDescription}; Elasticsearch.Net)";
3154

3255
/// <summary>
33-
/// ConnectionConfiguration allows you to control how ElasticLowLevelClient behaves and where/how it connects
34-
/// to elasticsearch
56+
/// Creates a new instance of <see cref="ConnectionConfiguration"/>
3557
/// </summary>
36-
/// <param name="uri">The root of the elasticsearch node we want to connect to. Defaults to http://localhost:9200</param>
58+
/// <param name="uri">The root of the Elasticsearch node we want to connect to. Defaults to http://localhost:9200</param>
3759
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
3860
public ConnectionConfiguration(Uri uri = null)
3961
: this(new SingleNodeConnectionPool(uri ?? new Uri("http://localhost:9200"))) { }
4062

4163
/// <summary>
42-
/// ConnectionConfiguration allows you to control how ElasticLowLevelClient behaves and where/how it connects
43-
/// to elasticsearch
64+
/// Creates a new instance of <see cref="ConnectionConfiguration"/>
4465
/// </summary>
45-
/// <param name="connectionPool">A connection pool implementation that'll tell the client what nodes are available</param>
66+
/// <param name="connectionPool">A connection pool implementation that tells the client what nodes are available</param>
4667
public ConnectionConfiguration(IConnectionPool connectionPool)
4768
// ReSharper disable once IntroduceOptionalParameters.Global
4869
: this(connectionPool, null, null) { }
4970

71+
/// <summary>
72+
/// Creates a new instance of <see cref="ConnectionConfiguration"/>
73+
/// </summary>
74+
/// <param name="connectionPool">A connection pool implementation that tells the client what nodes are available</param>
75+
/// <param name="connection">An connection implementation that can make API requests</param>
5076
public ConnectionConfiguration(IConnectionPool connectionPool, IConnection connection)
5177
// ReSharper disable once IntroduceOptionalParameters.Global
5278
: this(connectionPool, connection, null) { }
5379

80+
/// <summary>
81+
/// Creates a new instance of <see cref="ConnectionConfiguration"/>
82+
/// </summary>
83+
/// <param name="connectionPool">A connection pool implementation that tells the client what nodes are available</param>
84+
/// <param name="serializer">A serializer implementation used to serialize requests and deserialize responses</param>
5485
public ConnectionConfiguration(IConnectionPool connectionPool, IElasticsearchSerializer serializer)
5586
: this(connectionPool, null, serializer) { }
5687

57-
// ReSharper disable once MemberCanBePrivate.Global
58-
// eventhough we use don't use this we very much would like to expose this constructor
59-
88+
/// <summary>
89+
/// Creates a new instance of <see cref="ConnectionConfiguration"/>
90+
/// </summary>
91+
/// <param name="connectionPool">A connection pool implementation that tells the client what nodes are available</param>
92+
/// <param name="connection">An connection implementation that can make API requests</param>
93+
/// <param name="serializer">A serializer implementation used to serialize requests and deserialize responses</param>
6094
public ConnectionConfiguration(IConnectionPool connectionPool, IConnection connection, IElasticsearchSerializer serializer)
6195
: base(connectionPool, connection, serializer) { }
6296

@@ -104,6 +138,8 @@ public abstract class ConnectionConfiguration<T> : IConnectionConfigurationValue
104138
private bool _sniffOnStartup;
105139
private bool _throwExceptions;
106140

141+
private string _userAgent = ConnectionConfiguration.DefaultUserAgent;
142+
107143
protected ConnectionConfiguration(IConnectionPool connectionPool, IConnection connection, IElasticsearchSerializer requestResponseSerializer)
108144
{
109145
_connectionPool = connectionPool;
@@ -163,6 +199,7 @@ protected ConnectionConfiguration(IConnectionPool connectionPool, IConnection co
163199
bool IConnectionConfigurationValues.SniffsOnStartup => _sniffOnStartup;
164200
bool IConnectionConfigurationValues.ThrowExceptions => _throwExceptions;
165201
ElasticsearchUrlFormatter IConnectionConfigurationValues.UrlFormatter => _urlFormatter;
202+
string IConnectionConfigurationValues.UserAgent => _userAgent;
166203

167204
void IDisposable.Dispose() => DisposeManagedResources();
168205

@@ -463,6 +500,12 @@ public T ClientCertificate(string certificatePath) =>
463500
public T SkipDeserializationForStatusCodes(params int[] statusCodes) =>
464501
Assign(new ReadOnlyCollection<int>(statusCodes), (a, v) => a._skipDeserializationForStatusCodes = v);
465502

503+
/// <summary>
504+
/// The user agent string to send with requests. Useful for debugging purposes to understand client and framework
505+
/// versions that initiate requests to Elasticsearch
506+
/// </summary>
507+
public T UserAgent(string userAgent) => Assign(userAgent, (a, v) => a._userAgent = v);
508+
466509
protected virtual void DisposeManagedResources()
467510
{
468511
_connectionPool?.Dispose();

src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,5 +214,11 @@ public interface IConnectionConfigurationValues : IDisposable
214214
bool ThrowExceptions { get; }
215215

216216
ElasticsearchUrlFormatter UrlFormatter { get; }
217+
218+
/// <summary>
219+
/// The user agent string to send with requests. Useful for debugging purposes to understand client and framework
220+
/// versions that initiate requests to Elasticsearch
221+
/// </summary>
222+
string UserAgent { get; }
217223
}
218224
}

src/Elasticsearch.Net/Connection/HttpConnection.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ protected virtual HttpRequestMessage CreateRequestMessage(RequestData requestDat
221221
requestMessage.Headers.ConnectionClose = false;
222222
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(requestData.Accept));
223223

224+
if (!string.IsNullOrWhiteSpace(requestData.UserAgent))
225+
{
226+
requestMessage.Headers.UserAgent.Clear();
227+
requestMessage.Headers.UserAgent.TryParseAdd(requestData.UserAgent);
228+
}
229+
224230
if (!requestData.RunAs.IsNullOrEmpty())
225231
requestMessage.Headers.Add(RequestData.RunAsSecurityHeader, requestData.RunAs);
226232

src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ IMemoryStreamFactory memoryStreamFactory
7676
BasicAuthorizationCredentials = local?.BasicAuthenticationCredentials ?? global.BasicAuthenticationCredentials;
7777
AllowedStatusCodes = local?.AllowedStatusCodes ?? Enumerable.Empty<int>();
7878
ClientCertificates = local?.ClientCertificates ?? global.ClientCertificates;
79+
UserAgent = global.UserAgent;
7980
}
8081

8182
public string Accept { get; }
@@ -113,6 +114,7 @@ IMemoryStreamFactory memoryStreamFactory
113114
public string RunAs { get; }
114115
public IReadOnlyCollection<int> SkipDeserializationForStatusCodes { get; }
115116
public bool ThrowExceptions { get; }
117+
public string UserAgent { get; }
116118

117119
public Uri Uri => Node != null ? new Uri(Node.Uri, PathAndQuery) : null;
118120

src/Nest/CommonAbstractions/ConnectionSettings/ConnectionSettingsBase.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,21 @@
66
using System.Reflection;
77
using Elasticsearch.Net;
88

9+
#if DOTNETCORE
10+
using System.Runtime.InteropServices;
11+
#endif
12+
913
namespace Nest
1014
{
1115
/// <inheritdoc cref="IConnectionSettingsValues" />
1216
public class ConnectionSettings : ConnectionSettingsBase<ConnectionSettings>
1317
{
18+
/// <summary>
19+
/// The default user agent for Nest
20+
/// </summary>
21+
public static readonly string DefaultUserAgent =
22+
$"elasticsearch-net/{typeof(IConnectionSettingsValues).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion} ({RuntimeInformation.OSDescription}; {RuntimeInformation.FrameworkDescription}; Nest)";
23+
1424
/// <summary>
1525
/// A delegate used to construct a serializer to serialize CLR types representing documents and other types related to
1626
/// documents.
@@ -43,8 +53,7 @@ public ConnectionSettings(
4353
IConnection connection,
4454
SourceSerializerFactory sourceSerializer,
4555
IPropertyMappingProvider propertyMappingProvider
46-
)
47-
: base(connectionPool, connection, sourceSerializer, propertyMappingProvider) { }
56+
) : base(connectionPool, connection, sourceSerializer, propertyMappingProvider) { }
4857
}
4958

5059
/// <inheritdoc cref="IConnectionSettingsValues" />
@@ -93,6 +102,8 @@ IPropertyMappingProvider propertyMappingProvider
93102
_defaultIndices = new FluentDictionary<Type, string>();
94103
_defaultRelationNames = new FluentDictionary<Type, string>();
95104
_inferrer = new Inferrer(this);
105+
106+
UserAgent(ConnectionSettings.DefaultUserAgent);
96107
}
97108
bool IConnectionSettingsValues.DefaultDisableIdInference => _defaultDisableAllInference;
98109

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
// https://raw.githubusercontent.com/dotnet/core-setup/master/src/managed/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs
5+
6+
using System.Runtime.InteropServices;
7+
8+
namespace Nest
9+
{
10+
internal static class NativeMethods
11+
{
12+
public static class Windows
13+
{
14+
// This call avoids the shimming Windows does to report old versions
15+
[DllImport("ntdll")]
16+
private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation);
17+
18+
internal static string RtlGetVersion()
19+
{
20+
var osvi = new RTL_OSVERSIONINFOEX();
21+
osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi);
22+
if (RtlGetVersion(out osvi) == 0)
23+
return $"Microsoft Windows {osvi.dwMajorVersion}.{osvi.dwMinorVersion}.{osvi.dwBuildNumber}";
24+
25+
return null;
26+
}
27+
28+
[StructLayout(LayoutKind.Sequential)]
29+
internal struct RTL_OSVERSIONINFOEX
30+
{
31+
internal uint dwOSVersionInfoSize;
32+
internal uint dwMajorVersion;
33+
internal uint dwMinorVersion;
34+
internal uint dwBuildNumber;
35+
internal uint dwPlatformId;
36+
37+
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
38+
internal string szCSDVersion;
39+
}
40+
}
41+
}
42+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
#if NET461
3+
using System.Reflection;
4+
5+
namespace Nest
6+
{
7+
internal static class RuntimeInformation
8+
{
9+
private static string _frameworkDescription;
10+
private static string _osDescription;
11+
12+
public static string FrameworkDescription
13+
{
14+
get
15+
{
16+
if (_frameworkDescription == null)
17+
{
18+
var assemblyFileVersionAttribute =
19+
(AssemblyFileVersionAttribute)typeof(object).GetTypeInfo().Assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute));
20+
_frameworkDescription = $".NET Framework {assemblyFileVersionAttribute.Version}";
21+
}
22+
return _frameworkDescription;
23+
}
24+
}
25+
26+
public static string OSDescription
27+
{
28+
get
29+
{
30+
if (_osDescription == null)
31+
{
32+
var platform = (int)Environment.OSVersion.Platform;
33+
var isWindows = platform != 4 && platform != 6 && platform != 128;
34+
if (isWindows)
35+
_osDescription = NativeMethods.Windows.RtlGetVersion() ?? "Microsoft Windows";
36+
else
37+
_osDescription = Environment.OSVersion.VersionString;
38+
}
39+
return _osDescription;
40+
}
41+
}
42+
}
43+
}
44+
#endif

0 commit comments

Comments
 (0)