/* SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. * * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Runtime.InteropServices; using System.Net.Security; using System.Reflection; using System.Security; using System.Security.Cryptography.X509Certificates; using System.Threading; using OpenSearch.Net.Extensions; namespace OpenSearch.Net { /// /// Allows you to control how behaves and where/how it connects to OpenSearch /// public class ConnectionConfiguration : ConnectionConfiguration { /// /// Detects whether we are running on .NET Core with CurlHandler. /// If this is true, we will set a very restrictive /// As the old curl based handler is known to bleed TCP connections: /// https://github.com/dotnet/runtime/issues/22366 /// private static bool UsingCurlHandler => ConnectionInfo.UsingCurlHandler; /// /// The default ping timeout. Defaults to 2 seconds /// public static readonly TimeSpan DefaultPingTimeout = TimeSpan.FromSeconds(2); /// /// The default ping timeout when the connection is over HTTPS. Defaults to /// 5 seconds /// public static readonly TimeSpan DefaultPingTimeoutOnSSL = TimeSpan.FromSeconds(5); /// /// The default timeout before the client aborts a request to OpenSearch. /// Defaults to 1 minute /// public static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(1); /// /// The default timeout before a TCP connection is forcefully recycled so that DNS updates come through /// Defaults to 5 minutes. /// public static readonly TimeSpan DefaultDnsRefreshTimeout = TimeSpan.FromMinutes(5); /// /// The default connection limit for both OpenSearch.Net and OpenSearch.Client. Defaults to 80 /// Except for implementations based on curl, which defaults to /// public static readonly int DefaultConnectionLimit = UsingCurlHandler ? Environment.ProcessorCount : 80; /// /// The default user agent for OpenSearch.Net /// public static readonly string DefaultUserAgent = $"opensearch-net/{typeof(IConnectionConfigurationValues).Assembly.GetCustomAttribute().InformationalVersion} ({RuntimeInformation.OSDescription}; {RuntimeInformation.FrameworkDescription}; OpenSearch.Net)"; /// /// Creates a new instance of /// /// The root of the OpenSearch node we want to connect to. Defaults to http://localhost:9200 /// A connection implementation that can make API requests. Defaults to [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] public ConnectionConfiguration(Uri uri = null, IConnection connection = null) : this(new SingleNodeConnectionPool(uri ?? new Uri("http://localhost:9200")), connection) { } /// /// Sets up the client to communicate to OpenSearch Cloud using , /// documentation for more information on how to obtain your Cloud Id /// public ConnectionConfiguration(string cloudId, BasicAuthenticationCredentials credentials) : this(new CloudConnectionPool(cloudId, credentials)) { } /// /// Sets up the client to communicate to OpenSearch Cloud using , /// documentation for more information on how to obtain your Cloud Id /// public ConnectionConfiguration(string cloudId, ApiKeyAuthenticationCredentials credentials) : this(new CloudConnectionPool(cloudId, credentials)) { } /// /// Creates a new instance of /// /// A connection pool implementation that tells the client what nodes are available public ConnectionConfiguration(IConnectionPool connectionPool) // ReSharper disable once IntroduceOptionalParameters.Global : this(connectionPool, null, null) { } /// /// Creates a new instance of /// /// A connection pool implementation that tells the client what nodes are available /// An connection implementation that can make API requests public ConnectionConfiguration(IConnectionPool connectionPool, IConnection connection) // ReSharper disable once IntroduceOptionalParameters.Global : this(connectionPool, connection, null) { } /// /// Creates a new instance of /// /// A connection pool implementation that tells the client what nodes are available /// A serializer implementation used to serialize requests and deserialize responses public ConnectionConfiguration(IConnectionPool connectionPool, IOpenSearchSerializer serializer) : this(connectionPool, null, serializer) { } /// /// Creates a new instance of /// /// A connection pool implementation that tells the client what nodes are available /// An connection implementation that can make API requests /// A serializer implementation used to serialize requests and deserialize responses public ConnectionConfiguration(IConnectionPool connectionPool, IConnection connection, IOpenSearchSerializer serializer) : base(connectionPool, connection, serializer) { } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public abstract class ConnectionConfiguration : IConnectionConfigurationValues where T : ConnectionConfiguration { private readonly IConnection _connection; private readonly IConnectionPool _connectionPool; private readonly NameValueCollection _headers = new NameValueCollection(); private readonly NameValueCollection _queryString = new NameValueCollection(); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly OpenSearchUrlFormatter _urlFormatter; private BasicAuthenticationCredentials _basicAuthCredentials; private ApiKeyAuthenticationCredentials _apiKeyAuthCredentials; private X509CertificateCollection _clientCertificates; private Action _completedRequestHandler = DefaultCompletedRequestHandler; private int _connectionLimit; private TimeSpan? _deadTimeout; private bool _disableAutomaticProxyDetection; private bool _disableDirectStreaming; private bool _disableMetaHeader; private bool _disablePings; private bool _enableHttpCompression; private bool _enableHttpPipelining = true; private TimeSpan? _keepAliveInterval; private TimeSpan? _keepAliveTime; private TimeSpan? _maxDeadTimeout; private int? _maxRetries; private TimeSpan? _maxRetryTimeout; private Func _nodePredicate = DefaultNodePredicate; private Action _onRequestDataCreated = DefaultRequestDataCreated; private TimeSpan? _pingTimeout; private bool _prettyJson; private string _proxyAddress; private SecureString _proxyPassword; private string _proxyUsername; private TimeSpan _requestTimeout; private TimeSpan _dnsRefreshTimeout; private Func _serverCertificateValidationCallback; private IReadOnlyCollection _skipDeserializationForStatusCodes = new ReadOnlyCollection(new int[] { }); private TimeSpan? _sniffLifeSpan; private bool _sniffOnConnectionFault; private bool _sniffOnStartup; private bool _throwExceptions; private bool _transferEncodingChunked; private IMemoryStreamFactory _memoryStreamFactory = RecyclableMemoryStreamFactory.Default; private bool _enableTcpStats; //public static IMemoryStreamFactory Default { get; } = RecyclableMemoryStreamFactory.Default; public static IMemoryStreamFactory DefaultMemoryStreamFactory { get; } = OpenSearch.Net.MemoryStreamFactory.Default; private bool _enableThreadPoolStats; private string _userAgent = ConnectionConfiguration.DefaultUserAgent; private readonly Func _statusCodeToResponseSuccess; protected ConnectionConfiguration(IConnectionPool connectionPool, IConnection connection, IOpenSearchSerializer requestResponseSerializer) { _connectionPool = connectionPool; _connection = connection ?? new HttpConnection(); var serializer = requestResponseSerializer ?? new LowLevelRequestResponseSerializer(); UseThisRequestResponseSerializer = new DiagnosticsSerializerProxy(serializer); _connectionLimit = ConnectionConfiguration.DefaultConnectionLimit; _requestTimeout = ConnectionConfiguration.DefaultTimeout; _dnsRefreshTimeout = ConnectionConfiguration.DefaultDnsRefreshTimeout; _sniffOnConnectionFault = true; _sniffOnStartup = true; _sniffLifeSpan = TimeSpan.FromHours(1); if (_connectionPool.SupportsReseeding) _nodePredicate = DefaultReseedableNodePredicate; _urlFormatter = new OpenSearchUrlFormatter(this); _statusCodeToResponseSuccess = (m, i) => HttpStatusCodeClassifier(m, i); if (connectionPool is CloudConnectionPool cloudPool) { _basicAuthCredentials = cloudPool.BasicCredentials; _apiKeyAuthCredentials = cloudPool.ApiKeyCredentials; _enableHttpCompression = true; } } protected IOpenSearchSerializer UseThisRequestResponseSerializer { get; set; } BasicAuthenticationCredentials IConnectionConfigurationValues.BasicAuthenticationCredentials => _basicAuthCredentials; ApiKeyAuthenticationCredentials IConnectionConfigurationValues.ApiKeyAuthenticationCredentials => _apiKeyAuthCredentials; SemaphoreSlim IConnectionConfigurationValues.BootstrapLock => _semaphore; X509CertificateCollection IConnectionConfigurationValues.ClientCertificates => _clientCertificates; IConnection IConnectionConfigurationValues.Connection => _connection; int IConnectionConfigurationValues.ConnectionLimit => _connectionLimit; IConnectionPool IConnectionConfigurationValues.ConnectionPool => _connectionPool; TimeSpan? IConnectionConfigurationValues.DeadTimeout => _deadTimeout; bool IConnectionConfigurationValues.DisableAutomaticProxyDetection => _disableAutomaticProxyDetection; bool IConnectionConfigurationValues.DisableDirectStreaming => _disableDirectStreaming; bool IConnectionConfigurationValues.DisableMetaHeader => _disableMetaHeader; bool IConnectionConfigurationValues.DisablePings => _disablePings; bool IConnectionConfigurationValues.EnableHttpCompression => _enableHttpCompression; NameValueCollection IConnectionConfigurationValues.Headers => _headers; bool IConnectionConfigurationValues.HttpPipeliningEnabled => _enableHttpPipelining; TimeSpan? IConnectionConfigurationValues.KeepAliveInterval => _keepAliveInterval; TimeSpan? IConnectionConfigurationValues.KeepAliveTime => _keepAliveTime; TimeSpan? IConnectionConfigurationValues.MaxDeadTimeout => _maxDeadTimeout; int? IConnectionConfigurationValues.MaxRetries => _maxRetries; TimeSpan? IConnectionConfigurationValues.MaxRetryTimeout => _maxRetryTimeout; IMemoryStreamFactory IConnectionConfigurationValues.MemoryStreamFactory => _memoryStreamFactory; Func IConnectionConfigurationValues.NodePredicate => _nodePredicate; Action IConnectionConfigurationValues.OnRequestCompleted => _completedRequestHandler; Action IConnectionConfigurationValues.OnRequestDataCreated => _onRequestDataCreated; TimeSpan? IConnectionConfigurationValues.PingTimeout => _pingTimeout; bool IConnectionConfigurationValues.PrettyJson => _prettyJson; string IConnectionConfigurationValues.ProxyAddress => _proxyAddress; SecureString IConnectionConfigurationValues.ProxyPassword => _proxyPassword; string IConnectionConfigurationValues.ProxyUsername => _proxyUsername; NameValueCollection IConnectionConfigurationValues.QueryStringParameters => _queryString; IOpenSearchSerializer IConnectionConfigurationValues.RequestResponseSerializer => UseThisRequestResponseSerializer; TimeSpan IConnectionConfigurationValues.RequestTimeout => _requestTimeout; TimeSpan IConnectionConfigurationValues.DnsRefreshTimeout => _dnsRefreshTimeout; Func IConnectionConfigurationValues.ServerCertificateValidationCallback => _serverCertificateValidationCallback; IReadOnlyCollection IConnectionConfigurationValues.SkipDeserializationForStatusCodes => _skipDeserializationForStatusCodes; TimeSpan? IConnectionConfigurationValues.SniffInformationLifeSpan => _sniffLifeSpan; bool IConnectionConfigurationValues.SniffsOnConnectionFault => _sniffOnConnectionFault; bool IConnectionConfigurationValues.SniffsOnStartup => _sniffOnStartup; bool IConnectionConfigurationValues.ThrowExceptions => _throwExceptions; OpenSearchUrlFormatter IConnectionConfigurationValues.UrlFormatter => _urlFormatter; string IConnectionConfigurationValues.UserAgent => _userAgent; Func IConnectionConfigurationValues.StatusCodeToResponseSuccess => _statusCodeToResponseSuccess; bool IConnectionConfigurationValues.TransferEncodingChunked => _transferEncodingChunked; bool IConnectionConfigurationValues.EnableTcpStats => _enableTcpStats; bool IConnectionConfigurationValues.EnableThreadPoolStats => _enableThreadPoolStats; MetaHeaderProvider IConnectionConfigurationValues.MetaHeaderProvider { get; } = new MetaHeaderProvider(); void IDisposable.Dispose() => DisposeManagedResources(); private static void DefaultCompletedRequestHandler(IApiCallDetails response) { } private static void DefaultRequestDataCreated(RequestData response) { } /// /// The default predicate for implementations that return true for /// /// in which case cluster_manager only nodes are excluded from API calls. /// private static bool DefaultReseedableNodePredicate(Node node) => !node.ClusterManagerOnlyNode; private static bool DefaultNodePredicate(Node node) => true; protected T Assign(TValue value, Action assigner) => Fluent.Assign((T)this, value, assigner); /// /// Sets the keep-alive option on a TCP connection. /// For Desktop CLR, sets ServicePointManager.SetTcpKeepAlive /// /// Specifies the timeout with no activity until the first keep-alive packet is sent. /// /// Specifies the interval between when successive keep-alive packets are sent if no acknowledgement is /// received. /// public T EnableTcpKeepAlive(TimeSpan keepAliveTime, TimeSpan keepAliveInterval) => Assign(keepAliveTime, (a, v) => a._keepAliveTime = v) .Assign(keepAliveInterval, (a, v) => a._keepAliveInterval = v); /// The maximum number of retries for a given request public T MaximumRetries(int maxRetries) => Assign(maxRetries, (a, v) => a._maxRetries = v); /// /// Limits the number of concurrent connections that can be opened to an endpoint. Defaults to 80 for all IConnection /// implementations that are not based on System.Net.Http.CurlHandler. For those based on System.Net.Http.CurlHandler, defaults /// to Environment.ProcessorCount. /// /// For Desktop CLR, this setting applies to the DefaultConnectionLimit property on the ServicePointManager object when creating /// ServicePoint objects, affecting the default implementation. /// /// /// For Core CLR, this setting applies to the MaxConnectionsPerServer property on the HttpClientHandler instances used by the HttpClient /// inside the default implementation /// /// /// The connection limit, a value lower then 0 will cause the connection limit not to be set at all public T ConnectionLimit(int connectionLimit) => Assign(connectionLimit, (a, v) => a._connectionLimit = v); /// /// Enables resniffing of the cluster when a call fails, if the connection pool supports reseeding. Defaults to true /// public T SniffOnConnectionFault(bool sniffsOnConnectionFault = true) => Assign(sniffsOnConnectionFault, (a, v) => a._sniffOnConnectionFault = v); /// /// Enables sniffing on first usage of a connection pool if that pool supports reseeding. Defaults to true /// public T SniffOnStartup(bool sniffsOnStartup = true) => Assign(sniffsOnStartup, (a, v) => a._sniffOnStartup = v); /// /// Set the duration after which a cluster state is considered stale and a sniff should be performed again. /// An has to signal it supports reseeding, otherwise sniffing will never happen. /// Defaults to 1 hour. /// Set to null to disable completely. Sniffing will only ever happen on ConnectionPools that return true for SupportsReseeding /// /// The duration a clusterstate is considered fresh, set to null to disable periodic sniffing public T SniffLifeSpan(TimeSpan? sniffLifeSpan) => Assign(sniffLifeSpan, (a, v) => a._sniffLifeSpan = v); /// /// Enables gzip compressed requests and responses. /// IMPORTANT: You need to configure http compression on OpenSearch to be able to use this /// /// public T EnableHttpCompression(bool enabled = true) => Assign(enabled, (a, v) => a._enableHttpCompression = v); /// /// Disables the automatic detection of a proxy /// public T DisableAutomaticProxyDetection(bool disable = true) => Assign(disable, (a, v) => a._disableAutomaticProxyDetection = v); /// /// Disables the meta header which is included on all requests by default. This header contains lightweight information /// about the client and runtime. /// public T DisableMetaHeader(bool disable = true) => Assign(disable, (a, v) => a._disableMetaHeader = v); /// /// Instead of following a c/go like error checking on response.IsValid do throw an exception (except when is false) /// on the client when a call resulted in an exception on either the client or the OpenSearch server. /// Reasons for such exceptions could be search parser errors, index missing exceptions, etc... /// public T ThrowExceptions(bool alwaysThrow = true) => Assign(alwaysThrow, (a, v) => a._throwExceptions = v); /// /// 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 /// public T DisablePing(bool disable = true) => Assign(disable, (a, v) => a._disablePings = v); /// /// A collection of query string parameters that will be sent with every request. Useful in situations where you always need to pass a /// parameter e.g. an API key. /// public T GlobalQueryStringParameters(NameValueCollection queryStringParameters) => Assign(queryStringParameters, (a, v) => a._queryString.Add(v)); /// /// A collection of headers that will be sent with every request. Useful in situations where you always need to pass a header e.g. a custom /// auth header /// public T GlobalHeaders(NameValueCollection headers) => Assign(headers, (a, v) => a._headers.Add(v)); /// /// Sets the default timeout in milliseconds for each request to OpenSearch. Defaults to 60 seconds. /// NOTE: You can set this to a high value here, and specify a timeout on OpenSearch's side. /// /// time out in milliseconds public T RequestTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._requestTimeout = v); /// /// Sets the default ping timeout in milliseconds for ping requests, which are used /// to determine whether a node is alive. Pings should fail as fast as possible. /// /// The ping timeout in milliseconds defaults to 1000, or 2000 if using SSL. public T PingTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._pingTimeout = v); /// /// Sets the default dead timeout factor when a node has been marked dead. /// /// Some connection pools may use a flat timeout whilst others take this factor and increase it exponentially /// public T DeadTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._deadTimeout = v); /// /// Sets the maximum time a node can be marked dead. /// Different implementations of may choose a different default. /// /// The timeout in milliseconds public T MaxDeadTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._maxDeadTimeout = v); /// /// Limits the total runtime, including retries, separately from /// /// When not specified, defaults to , which itself defaults to 60 seconds /// /// public T MaxRetryTimeout(TimeSpan maxRetryTimeout) => Assign(maxRetryTimeout, (a, v) => a._maxRetryTimeout = v); /// /// DnsRefreshTimeout for the connections. Defaults to 5 minutes. /// Will create new instances of after this timeout to force DNS updates /// public T DnsRefreshTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._dnsRefreshTimeout = v); /// /// If your connection has to go through proxy, use this method to specify the proxy url /// public T Proxy(Uri proxyAddress, string username, string password) => Assign(proxyAddress.ToString(), (a, v) => a._proxyAddress = v) .Assign(username, (a, v) => a._proxyUsername = v) .Assign(password, (a, v) => a._proxyPassword = v.CreateSecureString()); /// /// If your connection has to go through proxy, use this method to specify the proxy url /// public T Proxy(Uri proxyAddress, string username, SecureString password) => Assign(proxyAddress.ToString(), (a, v) => a._proxyAddress = v) .Assign(username, (a, v) => a._proxyUsername = v) .Assign(password, (a, v) => a._proxyPassword = v); /// /// Forces all requests to have ?pretty=true querystring parameter appended, /// causing OpenSearch to return formatted JSON. /// Defaults to false /// public T PrettyJson(bool b = true) => Assign(b, (a, v) => { a._prettyJson = v; const string key = "pretty"; if (!v && a._queryString[key] != null) a._queryString.Remove(key); else if (v && a._queryString[key] == null) a.GlobalQueryStringParameters(new NameValueCollection { { key, "true" } }); }); /// /// Forces all requests to have ?error_trace=true querystring parameter appended, /// causing OpenSearch to return stack traces as part of serialized exceptions /// Defaults to false /// public T IncludeServerStackTraceOnError(bool b = true) => Assign(b, (a, v) => { const string key = "error_trace"; if (!v && a._queryString[key] != null) a._queryString.Remove(key); else if (v && a._queryString[key] == null) a.GlobalQueryStringParameters(new NameValueCollection { { key, "true" } }); }); /// /// Ensures the response bytes are always available on the /// /// IMPORTANT: Depending on the registered serializer, /// this may cause the response to be buffered in memory first, potentially affecting performance. /// /// public T DisableDirectStreaming(bool b = true) => Assign(b, (a, v) => a._disableDirectStreaming = v); /// /// Registers an that is called when a response is received from OpenSearch. /// This can be useful for implementing custom logging. /// Multiple callbacks can be registered by calling this multiple times /// public T OnRequestCompleted(Action handler) => Assign(handler, (a, v) => a._completedRequestHandler += v ?? DefaultCompletedRequestHandler); /// /// Registers an that is called when is created. /// Multiple callbacks can be registered by calling this multiple times /// public T OnRequestDataCreated(Action handler) => Assign(handler, (a, v) => a._onRequestDataCreated += v ?? DefaultRequestDataCreated); /// /// Basic Authentication credentials to send with all requests to OpenSearch /// public T BasicAuthentication(string username, string password) => Assign(new BasicAuthenticationCredentials(username, password), (a, v) => a._basicAuthCredentials = v); /// /// Basic Authentication credentials to send with all requests to OpenSearch /// public T BasicAuthentication(string username, SecureString password) => Assign(new BasicAuthenticationCredentials(username, password), (a, v) => a._basicAuthCredentials = v); /// /// Api Key to send with all requests to OpenSearch /// public T ApiKeyAuthentication(string id, SecureString apiKey) => Assign(new ApiKeyAuthenticationCredentials(id, apiKey), (a, v) => a._apiKeyAuthCredentials = v); /// /// Api Key to send with all requests to OpenSearch /// public T ApiKeyAuthentication(string id, string apiKey) => Assign(new ApiKeyAuthenticationCredentials(id, apiKey), (a, v) => a._apiKeyAuthCredentials = v); /// /// Api Key to send with all requests to OpenSearch /// public T ApiKeyAuthentication(ApiKeyAuthenticationCredentials credentials) => Assign(credentials, (a, v) => a._apiKeyAuthCredentials = v); /// /// Allows for requests to be pipelined. http://en.wikipedia.org/wiki/HTTP_pipelining /// NOTE: HTTP pipelining must also be enabled in OpenSearch for this to work properly. /// public T EnableHttpPipelining(bool enabled = true) => Assign(enabled, (a, v) => a._enableHttpPipelining = v); /// /// Register a predicate to select which nodes that you want to execute API calls on. Note that sniffing requests omit this predicate and /// always execute on all nodes. /// When using an implementation that supports reseeding of nodes, this will default to omitting cluster_manager only /// node from regular API calls. /// When using static or single node connection pooling it is assumed the list of node you instantiate the client with should be taken /// verbatim. /// /// Return true if you want the node to be used for API calls public T NodePredicate(Func predicate) => Assign(predicate ?? DefaultNodePredicate, (a, v) => a._nodePredicate = v); /// /// Turns on settings that aid in debugging like DisableDirectStreaming() and PrettyJson() /// so that the original request and response JSON can be inspected. It also always asks the server for the full stack trace on errors /// /// /// An optional callback to be performed when the request completes. This will /// not overwrite the global OnRequestCompleted callback that is set directly on /// ConnectionSettings. If no callback is passed, DebugInformation from the response /// will be written to the debug output by default. /// public T EnableDebugMode(Action onRequestCompleted = null) => PrettyJson() .IncludeServerStackTraceOnError() .DisableDirectStreaming() .EnableTcpStats() .EnableThreadPoolStats() .Assign(onRequestCompleted, (a, v) => _completedRequestHandler += v ?? (d => Debug.WriteLine(d.DebugInformation))); /// /// Register a ServerCertificateValidationCallback, this is called per endpoint until it returns true. /// After this callback returns true that endpoint is validated for the lifetime of the ServiceEndpoint /// for that host. /// public T ServerCertificateValidationCallback(Func callback) => Assign(callback, (a, v) => a._serverCertificateValidationCallback = v); /// /// Use the following certificates to authenticate all HTTP requests. You can also set them on individual /// request using /// public T ClientCertificates(X509CertificateCollection certificates) => Assign(certificates, (a, v) => a._clientCertificates = v); /// /// Use a to authenticate all HTTP requests. You can also set them on individual request using /// public T ClientCertificate(X509Certificate certificate) => Assign(new X509Certificate2Collection { certificate }, (a, v) => a._clientCertificates = v); /// /// Use a file path to a certificate to authenticate all HTTP requests. You can also set them on individual request using /// public T ClientCertificate(string certificatePath) => Assign(new X509Certificate2Collection { new X509Certificate(certificatePath) }, (a, v) => a._clientCertificates = v); /// /// Configure the client to skip deserialization of certain status codes e.g: you run OpenSearch behind a proxy that returns a HTML for 401, /// 500 /// public T SkipDeserializationForStatusCodes(params int[] statusCodes) => Assign(new ReadOnlyCollection(statusCodes), (a, v) => a._skipDeserializationForStatusCodes = v); /// /// The user agent string to send with requests. Useful for debugging purposes to understand client and framework /// versions that initiate requests to OpenSearch /// public T UserAgent(string userAgent) => Assign(userAgent, (a, v) => a._userAgent = v); /// /// Whether the request should be sent with chunked Transfer-Encoding. Default is false /// public T TransferEncodingChunked(bool transferEncodingChunked = true) => Assign(transferEncodingChunked, (a, v) => a._transferEncodingChunked = v); /// /// The memory stream factory to use, defaults to /// public T MemoryStreamFactory(IMemoryStreamFactory memoryStreamFactory) => Assign(memoryStreamFactory, (a, v) => a._memoryStreamFactory = v); public T EnableTcpStats(bool enableTcpStats = true) => Assign(enableTcpStats, (a, v) => a._enableTcpStats = v); public T EnableThreadPoolStats(bool enableThreadPoolStats = true) => Assign(enableThreadPoolStats, (a, v) => a._enableThreadPoolStats = v); protected virtual void DisposeManagedResources() { _connectionPool?.Dispose(); _connection?.Dispose(); _semaphore?.Dispose(); _proxyPassword?.Dispose(); _basicAuthCredentials?.Dispose(); _apiKeyAuthCredentials?.Dispose(); } protected virtual bool HttpStatusCodeClassifier(HttpMethod method, int statusCode) => statusCode >= 200 && statusCode < 300; } }