diff --git a/global.json b/global.json index 72d38cd..60b4c02 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { "version": "8.0.100", - "rollForward": "latestMajor", + "rollForward": "latestMinor", "allowPrerelease": false } } \ No newline at end of file diff --git a/src/Elastic.Transport/Components/Pipeline/RequestData.cs b/src/Elastic.Transport/Components/Pipeline/RequestData.cs index eeb44c8..f8a7b2b 100644 --- a/src/Elastic.Transport/Components/Pipeline/RequestData.cs +++ b/src/Elastic.Transport/Components/Pipeline/RequestData.cs @@ -7,7 +7,6 @@ using System.Collections.Specialized; using System.Security.Cryptography.X509Certificates; using Elastic.Transport.Extensions; -using Elastic.Transport.Products; namespace Elastic.Transport; @@ -41,9 +40,6 @@ public RequestData(ITransportConfiguration global, IRequestConfiguration? local ProxyPassword = global.ProxyPassword; DisableAutomaticProxyDetection = global.DisableAutomaticProxyDetection; UserAgent = global.UserAgent; - ResponseBuilders = global.ResponseBuilders; - ProductResponseBuilders = global.ProductRegistration.ResponseBuilders; - KeepAliveInterval = (int)(global.KeepAliveInterval?.TotalMilliseconds ?? 2000); KeepAliveTime = (int)(global.KeepAliveTime?.TotalMilliseconds ?? 2000); RunAs = local?.RunAs ?? global.RunAs; @@ -90,13 +86,36 @@ public RequestData(ITransportConfiguration global, IRequestConfiguration? local Headers ??= []; Headers.Add(OpaqueIdHeader, local.OpaqueId); } - } - /// - public IReadOnlyCollection ProductResponseBuilders { get; } + // If there are builders set at the transport level and on the request config, we combine them, + // prioritising the request config response builders as most specific. + if (local is not null && local.ResponseBuilders.Count > 0 && global.ResponseBuilders.Count > 0) + { + var builders = new IResponseBuilder[local.ResponseBuilders.Count + global.ResponseBuilders.Count]; - /// - public IReadOnlyCollection ResponseBuilders { get; } + var counter = 0; + foreach (var builder in local.ResponseBuilders) + { + builders[counter++] = builder; + } + foreach (var builder in global.ResponseBuilders) + { + builders[counter++] = builder; + } + + ResponseBuilders = builders; + } + else if (local is not null && local.ResponseBuilders.Count > 0) + { + ResponseBuilders = local.ResponseBuilders; + } + else + { + ResponseBuilders = global.ResponseBuilders; + } + + ProductResponseBuilders = global.ProductRegistration.ResponseBuilders; + } /// public MemoryStreamFactory MemoryStreamFactory { get; } @@ -168,4 +187,8 @@ public RequestData(ITransportConfiguration global, IRequestConfiguration? local public bool DisableSniff { get; } /// public bool DisablePings { get; } + /// + public IReadOnlyCollection ProductResponseBuilders { get; } + /// + public IReadOnlyCollection ResponseBuilders { get; } } diff --git a/src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker.cs b/src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker.cs index afb6023..5223ba0 100644 --- a/src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker.cs +++ b/src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker.cs @@ -199,8 +199,8 @@ private async ValueTask RequestCoreAsync(bool isAsync, End } else { - responseStream.Dispose(); - receivedResponse.Dispose(); + responseStream?.Dispose(); + receivedResponse?.Dispose(); } if (!OpenTelemetry.CurrentSpanIsElasticTransportOwnedAndHasListeners || (!(Activity.Current?.IsAllDataRequested ?? false))) @@ -218,8 +218,8 @@ private async ValueTask RequestCoreAsync(bool isAsync, End catch { // if there's an exception, ensure we always release the stream and response so that the connection is freed. - responseStream.Dispose(); - receivedResponse.Dispose(); + responseStream?.Dispose(); + receivedResponse?.Dispose(); throw; } } diff --git a/src/Elastic.Transport/Components/TransportClient/HttpWebRequestInvoker.cs b/src/Elastic.Transport/Components/TransportClient/HttpWebRequestInvoker.cs index dd97e3c..8db2c22 100644 --- a/src/Elastic.Transport/Components/TransportClient/HttpWebRequestInvoker.cs +++ b/src/Elastic.Transport/Components/TransportClient/HttpWebRequestInvoker.cs @@ -190,8 +190,8 @@ private async ValueTask RequestCoreAsync(bool isAsync, End } else { - responseStream.Dispose(); - receivedResponse.Dispose(); + responseStream?.Dispose(); + receivedResponse?.Dispose(); } if (OpenTelemetry.CurrentSpanIsElasticTransportOwnedAndHasListeners && (Activity.Current?.IsAllDataRequested ?? false)) @@ -208,8 +208,8 @@ private async ValueTask RequestCoreAsync(bool isAsync, End catch { // if there's an exception, ensure we always release the stream and response so that the connection is freed. - responseStream.Dispose(); - receivedResponse.Dispose(); + responseStream?.Dispose(); + receivedResponse?.Dispose(); throw; } } diff --git a/src/Elastic.Transport/Configuration/IRequestConfiguration.cs b/src/Elastic.Transport/Configuration/IRequestConfiguration.cs new file mode 100644 index 0000000..39934db --- /dev/null +++ b/src/Elastic.Transport/Configuration/IRequestConfiguration.cs @@ -0,0 +1,155 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Security.Cryptography.X509Certificates; + +namespace Elastic.Transport; + +/// +/// Allows you to inject per request overrides to the current . +/// +public interface IRequestConfiguration +{ + /// + /// Force a different Accept header on the request + /// + string? Accept { get; } + + /// + /// Treat the following statuses (on top of the 200 range) NOT as error. + /// + IReadOnlyCollection? AllowedStatusCodes { get; } + + /// Provide an authentication header override for this request + AuthorizationHeader? Authentication { get; } + + /// + /// Use the following client certificates to authenticate this single request + /// + X509CertificateCollection? ClientCertificates { get; } + + /// + /// Force a different Content-Type header on the request + /// + string? ContentType { get; } + + /// + /// Whether to buffer the request and response bytes for the call + /// + bool? DisableDirectStreaming { get; } + + /// + /// Whether to disable the audit trail for the request. + /// + bool? DisableAuditTrail { get; } + + /// + /// Under no circumstance do a ping before the actual call. If a node was previously dead a small ping with + /// low connect timeout will be tried first in normal circumstances + /// + bool? DisablePings { get; } + + /// + /// Forces no sniffing to occur on the request no matter what configuration is in place + /// globally + /// + bool? DisableSniff { get; } + + /// + /// Whether or not this request should be pipelined. http://en.wikipedia.org/wiki/HTTP_pipelining defaults to true + /// + bool? HttpPipeliningEnabled { get; } + + /// + /// Enable gzip compressed requests and responses + /// + bool? EnableHttpCompression { get; } + + /// + /// This will force the operation on the specified node, this will bypass any configured connection pool and will no retry. + /// + Uri? ForceNode { get; } + + /// + /// When a retryable exception occurs or status code is returned this controls the maximum + /// amount of times we should retry the call to Elasticsearch + /// + int? MaxRetries { get; } + + /// + /// Limits the total runtime including retries separately from + ///
+	/// When not specified defaults to  which itself defaults to 60 seconds
+	/// 
+ ///
+ TimeSpan? MaxRetryTimeout { get; } + + /// + /// Associate an Id with this user-initiated task, such that it can be located in the cluster task list. + /// Valid only for Elasticsearch 6.2.0+ + /// + string? OpaqueId { get; } + + /// Determines whether to parse all HTTP headers in the request. + bool? ParseAllHeaders { get; } + + /// + /// The ping timeout for this specific request + /// + TimeSpan? PingTimeout { get; } + + /// + /// The timeout for this specific request, takes precedence over the global timeout init + /// + TimeSpan? RequestTimeout { get; } + + /// + /// Additional response builders to apply. + /// + IReadOnlyCollection ResponseBuilders { get; } + + /// Specifies the headers from the response that should be parsed. + HeadersList? ResponseHeadersToParse { get; } + + /// + /// Submit the request on behalf in the context of a different shield user + ///
https://www.elastic.co/guide/en/shield/current/submitting-requests-for-other-users.html
+	/// 
+ string? RunAs { get; } + + /// + /// 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 Elasticsearch server. + /// Reasons for such exceptions could be search parser errors, index missing exceptions, etc... + /// + bool? ThrowExceptions { get; } + + /// + /// Whether the request should be sent with chunked Transfer-Encoding. + /// + bool? TransferEncodingChunked { get; } + + /// + /// Try to send these headers for this single request + /// + NameValueCollection? Headers { get; } + + /// + /// Enable statistics about TCP connections to be collected when making a request + /// + bool? EnableTcpStats { get; } + + /// + /// Enable statistics about thread pools to be collected when making a request + /// + bool? EnableThreadPoolStats { get; } + + /// + /// Holds additional meta data about the request. + /// + RequestMetaData? RequestMetaData { get; } +} diff --git a/src/Elastic.Transport/Configuration/ITransportConfiguration.cs b/src/Elastic.Transport/Configuration/ITransportConfiguration.cs index 4cc35cc..4fc8c0d 100644 --- a/src/Elastic.Transport/Configuration/ITransportConfiguration.cs +++ b/src/Elastic.Transport/Configuration/ITransportConfiguration.cs @@ -210,9 +210,4 @@ public interface ITransportConfiguration : IRequestConfiguration, IDisposable /// about the client and runtime. /// bool DisableMetaHeader { get; } - - /// - /// Additional response builders to apply. - /// - IReadOnlyCollection ResponseBuilders { get; } } diff --git a/src/Elastic.Transport/Configuration/RequestConfiguration.cs b/src/Elastic.Transport/Configuration/RequestConfiguration.cs index 8506fef..940d69b 100644 --- a/src/Elastic.Transport/Configuration/RequestConfiguration.cs +++ b/src/Elastic.Transport/Configuration/RequestConfiguration.cs @@ -11,146 +11,6 @@ namespace Elastic.Transport; -/// -/// Allows you to inject per request overrides to the current . -/// -public interface IRequestConfiguration -{ - /// - /// Force a different Accept header on the request - /// - string? Accept { get; } - - /// - /// Treat the following statuses (on top of the 200 range) NOT as error. - /// - IReadOnlyCollection? AllowedStatusCodes { get; } - - /// Provide an authentication header override for this request - AuthorizationHeader? Authentication { get; } - - /// - /// Use the following client certificates to authenticate this single request - /// - X509CertificateCollection? ClientCertificates { get; } - - /// - /// Force a different Content-Type header on the request - /// - string? ContentType { get; } - - /// - /// Whether to buffer the request and response bytes for the call - /// - bool? DisableDirectStreaming { get; } - - /// - /// Whether to disable the audit trail for the request. - /// - bool? DisableAuditTrail { get; } - - /// - /// Under no circumstance do a ping before the actual call. If a node was previously dead a small ping with - /// low connect timeout will be tried first in normal circumstances - /// - bool? DisablePings { get; } - - /// - /// Forces no sniffing to occur on the request no matter what configuration is in place - /// globally - /// - bool? DisableSniff { get; } - - /// - /// Whether or not this request should be pipelined. http://en.wikipedia.org/wiki/HTTP_pipelining defaults to true - /// - bool? HttpPipeliningEnabled { get; } - - /// - /// Enable gzip compressed requests and responses - /// - bool? EnableHttpCompression { get; } - - /// - /// This will force the operation on the specified node, this will bypass any configured connection pool and will no retry. - /// - Uri? ForceNode { get; } - - /// - /// When a retryable exception occurs or status code is returned this controls the maximum - /// amount of times we should retry the call to Elasticsearch - /// - int? MaxRetries { get; } - - /// - /// Limits the total runtime including retries separately from - ///
-	/// When not specified defaults to  which itself defaults to 60 seconds
-	/// 
- ///
- TimeSpan? MaxRetryTimeout { get; } - - /// - /// Associate an Id with this user-initiated task, such that it can be located in the cluster task list. - /// Valid only for Elasticsearch 6.2.0+ - /// - string? OpaqueId { get; } - - /// Determines whether to parse all HTTP headers in the request. - bool? ParseAllHeaders { get; } - - /// - /// The ping timeout for this specific request - /// - TimeSpan? PingTimeout { get; } - - /// - /// The timeout for this specific request, takes precedence over the global timeout init - /// - TimeSpan? RequestTimeout { get; } - - /// Specifies the headers from the response that should be parsed. - HeadersList? ResponseHeadersToParse { get; } - - /// - /// Submit the request on behalf in the context of a different shield user - ///
https://www.elastic.co/guide/en/shield/current/submitting-requests-for-other-users.html
-	/// 
- string? RunAs { get; } - - /// - /// 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 Elasticsearch server. - /// Reasons for such exceptions could be search parser errors, index missing exceptions, etc... - /// - bool? ThrowExceptions { get; } - - /// - /// Whether the request should be sent with chunked Transfer-Encoding. - /// - bool? TransferEncodingChunked { get; } - - /// - /// Try to send these headers for this single request - /// - NameValueCollection? Headers { get; } - - /// - /// Enable statistics about TCP connections to be collected when making a request - /// - bool? EnableTcpStats { get; } - - /// - /// Enable statistics about thread pools to be collected when making a request - /// - bool? EnableThreadPoolStats { get; } - - /// - /// Holds additional meta data about the request. - /// - RequestMetaData? RequestMetaData { get; } -} - /// public record RequestConfiguration : IRequestConfiguration { @@ -204,6 +64,7 @@ public RequestConfiguration(IRequestConfiguration config) ResponseHeadersToParse = (config.ResponseHeadersToParse is null) ? null : new HeadersList(config.ResponseHeadersToParse); ParseAllHeaders = config.ParseAllHeaders; RequestMetaData = config.RequestMetaData; + ResponseBuilders = config.ResponseBuilders; } /// @@ -257,6 +118,9 @@ public RequestConfiguration(IRequestConfiguration config) /// public TimeSpan? RequestTimeout { get; init; } + /// + public IReadOnlyCollection ResponseBuilders { get; init; } = []; + /// public string? RunAs { get; init; } @@ -284,320 +148,3 @@ public RequestConfiguration(IRequestConfiguration config) /// public RequestMetaData? RequestMetaData { get; init; } } - -/// -public class RequestConfigurationDescriptor : IRequestConfiguration -{ - /// - public RequestConfigurationDescriptor() { } - - /// - public RequestConfigurationDescriptor(IRequestConfiguration? config) - { - if (config is null) - return; - - _accept = config.Accept; - _allowedStatusCodes= config.AllowedStatusCodes; - _authentication = config.Authentication; - _clientCertificates = (config.ClientCertificates is null) ? null : new X509CertificateCollection(config.ClientCertificates);; - _contentType = config.ContentType; - _disableDirectStreaming = config.DisableDirectStreaming; - _disableAuditTrail = config.DisableAuditTrail; - _disablePings = config.DisablePings; - _disableSniff = config.DisableSniff; - _httpPipeliningEnabled = config.HttpPipeliningEnabled; - _enableHttpCompression = config.EnableHttpCompression; - _forceNode = config.ForceNode; - _maxRetries = config.MaxRetries; - _maxRetryTimeout = config.MaxRetryTimeout; - _opaqueId = config.OpaqueId; - _pingTimeout = config.PingTimeout; - _requestTimeout = config.RequestTimeout; - _runAs = config.RunAs; - _throwExceptions = config.ThrowExceptions; - _transferEncodingChunked = config.TransferEncodingChunked; - _headers = (config.Headers is null) ? null : new NameValueCollection(config.Headers); - _enableTcpStats = config.EnableTcpStats; - _enableThreadPoolStats = config.EnableThreadPoolStats; - _responseHeadersToParse = (config.ResponseHeadersToParse is null) ? null : new HeadersList(config.ResponseHeadersToParse); - _parseAllHeaders = config.ParseAllHeaders; - _requestMetaData = config.RequestMetaData; - } - - private string? _accept; - private IReadOnlyCollection? _allowedStatusCodes; - private AuthorizationHeader? _authentication; - private X509CertificateCollection? _clientCertificates; - private string? _contentType; - private bool? _disableDirectStreaming; - private bool? _disableAuditTrail; - private bool? _disablePings; - private bool? _disableSniff; - private bool? _httpPipeliningEnabled; - private bool? _enableHttpCompression; - private Uri? _forceNode; - private int? _maxRetries; - private TimeSpan? _maxRetryTimeout; - private string? _opaqueId; - private bool? _parseAllHeaders; - private TimeSpan? _pingTimeout; - private TimeSpan? _requestTimeout; - private HeadersList? _responseHeadersToParse; - private string? _runAs; - private bool? _throwExceptions; - private bool? _transferEncodingChunked; - private NameValueCollection? _headers; - private bool? _enableTcpStats; - private bool? _enableThreadPoolStats; - private RequestMetaData? _requestMetaData; - - /// - public RequestConfigurationDescriptor RunAs(string username) - { - _runAs = username; - return this; - } - - /// - public RequestConfigurationDescriptor RequestTimeout(TimeSpan requestTimeout) - { - _requestTimeout = requestTimeout; - return this; - } - - /// - public RequestConfigurationDescriptor OpaqueId(string opaqueId) - { - _opaqueId = opaqueId; - return this; - } - - /// - public RequestConfigurationDescriptor PingTimeout(TimeSpan pingTimeout) - { - _pingTimeout = pingTimeout; - return this; - } - - /// - public RequestConfigurationDescriptor ContentType(string contentTypeHeader) - { - _contentType = contentTypeHeader; - return this; - } - - /// - public RequestConfigurationDescriptor Accept(string acceptHeader) - { - _accept = acceptHeader; - return this; - } - - /// - public RequestConfigurationDescriptor AllowedStatusCodes(IEnumerable? codes) - { - _allowedStatusCodes = codes?.ToReadOnlyCollection(); - return this; - } - - /// - public RequestConfigurationDescriptor AllowedStatusCodes(params int[] codes) - { - _allowedStatusCodes = codes.ToReadOnlyCollection(); - return this; - } - - /// - public RequestConfigurationDescriptor DisableSniffing(bool disable = true) - { - _disableSniff = disable; - return this; - } - - /// - public RequestConfigurationDescriptor DisablePing(bool disable = true) - { - _disablePings = disable; - return this; - } - - /// - public RequestConfigurationDescriptor ThrowExceptions(bool throwExceptions = true) - { - _throwExceptions = throwExceptions; - return this; - } - - /// - public RequestConfigurationDescriptor DisableDirectStreaming(bool disable = true) - { - _disableDirectStreaming = disable; - return this; - } - - /// - public RequestConfigurationDescriptor DisableAuditTrail(bool disable = true) - { - _disableAuditTrail = disable; - return this; - } - - /// - public RequestConfigurationDescriptor ForceNode(Uri uri) - { - _forceNode = uri; - return this; - } - - /// - public RequestConfigurationDescriptor MaxRetries(int retry) - { - _maxRetries = retry; - return this; - } - - /// - public RequestConfigurationDescriptor MaxRetries(TimeSpan? timeout) - { - _maxRetryTimeout = timeout; - return this; - } - - /// - public RequestConfigurationDescriptor Authentication(AuthorizationHeader authentication) - { - _authentication = authentication; - return this; - } - - /// - public RequestConfigurationDescriptor EnableHttpPipelining(bool enable = true) - { - _httpPipeliningEnabled = enable; - return this; - } - - /// - public RequestConfigurationDescriptor EnableHttpCompression(bool enable = true) - { - _enableHttpCompression = enable; - return this; - } - - /// - public RequestConfigurationDescriptor ClientCertificates(X509CertificateCollection certificates) - { - _clientCertificates = certificates; - return this; - } - - /// - public RequestConfigurationDescriptor ClientCertificate(X509Certificate certificate) => - ClientCertificates(new X509Certificate2Collection { certificate }); - - /// - public RequestConfigurationDescriptor ClientCertificate(string certificatePath) => - ClientCertificates(new X509Certificate2Collection { new X509Certificate(certificatePath) }); - - /// - public RequestConfigurationDescriptor TransferEncodingChunked(bool transferEncodingChunked = true) - { - _transferEncodingChunked = transferEncodingChunked; - return this; - } - - /// - public RequestConfigurationDescriptor GlobalHeaders(NameValueCollection headers) - { - _headers = headers; - return this; - } - - /// - public RequestConfigurationDescriptor EnableTcpStats(bool enableTcpStats = true) - { - _enableTcpStats = enableTcpStats; - return this; - } - - /// - public RequestConfigurationDescriptor EnableThreadPoolStats(bool enableThreadPoolStats = true) - { - _enableThreadPoolStats = enableThreadPoolStats; - return this; - } - - /// - public RequestConfigurationDescriptor ParseAllHeaders(bool enable = true) - { - _parseAllHeaders = enable; - return this; - } - - /// - public RequestConfigurationDescriptor ResponseHeadersToParse(IEnumerable headers) - { - _responseHeadersToParse = new HeadersList(headers); - return this; - } - - /// - public RequestConfigurationDescriptor RequestMetaData(RequestMetaData metaData) - { - _requestMetaData = metaData; - return this; - } - - string? IRequestConfiguration.Accept => _accept; - - IReadOnlyCollection? IRequestConfiguration.AllowedStatusCodes => _allowedStatusCodes; - - AuthorizationHeader? IRequestConfiguration.Authentication => _authentication; - - X509CertificateCollection? IRequestConfiguration.ClientCertificates => _clientCertificates; - - string? IRequestConfiguration.ContentType => _contentType; - - bool? IRequestConfiguration.DisableDirectStreaming => _disableDirectStreaming; - - bool? IRequestConfiguration.DisableAuditTrail => _disableAuditTrail; - - bool? IRequestConfiguration.DisablePings => _disablePings; - - bool? IRequestConfiguration.DisableSniff => _disableSniff; - - bool? IRequestConfiguration.HttpPipeliningEnabled => _httpPipeliningEnabled; - - bool? IRequestConfiguration.EnableHttpCompression => _enableHttpCompression; - - Uri? IRequestConfiguration.ForceNode => _forceNode; - - int? IRequestConfiguration.MaxRetries => _maxRetries; - - TimeSpan? IRequestConfiguration.MaxRetryTimeout => _maxRetryTimeout; - - string? IRequestConfiguration.OpaqueId => _opaqueId; - - bool? IRequestConfiguration.ParseAllHeaders => _parseAllHeaders; - - TimeSpan? IRequestConfiguration.PingTimeout => _pingTimeout; - - TimeSpan? IRequestConfiguration.RequestTimeout => _requestTimeout; - - HeadersList? IRequestConfiguration.ResponseHeadersToParse => _responseHeadersToParse; - - string? IRequestConfiguration.RunAs => _runAs; - - bool? IRequestConfiguration.ThrowExceptions => _throwExceptions; - - bool? IRequestConfiguration.TransferEncodingChunked => _transferEncodingChunked; - - NameValueCollection? IRequestConfiguration.Headers => _headers; - - bool? IRequestConfiguration.EnableTcpStats => _enableTcpStats; - - bool? IRequestConfiguration.EnableThreadPoolStats => _enableThreadPoolStats; - - RequestMetaData? IRequestConfiguration.RequestMetaData => _requestMetaData; -} diff --git a/src/Elastic.Transport/Configuration/RequestConfigurationDescriptor.cs b/src/Elastic.Transport/Configuration/RequestConfigurationDescriptor.cs new file mode 100644 index 0000000..552fbdd --- /dev/null +++ b/src/Elastic.Transport/Configuration/RequestConfigurationDescriptor.cs @@ -0,0 +1,342 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Security.Cryptography.X509Certificates; + +using Elastic.Transport.Extensions; + +namespace Elastic.Transport; + +/// +public class RequestConfigurationDescriptor : IRequestConfiguration +{ + /// + public RequestConfigurationDescriptor() { } + + /// + public RequestConfigurationDescriptor(IRequestConfiguration? config) + { + if (config is null) + return; + + _accept = config.Accept; + _allowedStatusCodes= config.AllowedStatusCodes; + _authentication = config.Authentication; + _clientCertificates = (config.ClientCertificates is null) ? null : new X509CertificateCollection(config.ClientCertificates);; + _contentType = config.ContentType; + _disableDirectStreaming = config.DisableDirectStreaming; + _disableAuditTrail = config.DisableAuditTrail; + _disablePings = config.DisablePings; + _disableSniff = config.DisableSniff; + _httpPipeliningEnabled = config.HttpPipeliningEnabled; + _enableHttpCompression = config.EnableHttpCompression; + _forceNode = config.ForceNode; + _maxRetries = config.MaxRetries; + _maxRetryTimeout = config.MaxRetryTimeout; + _opaqueId = config.OpaqueId; + _pingTimeout = config.PingTimeout; + _requestTimeout = config.RequestTimeout; + _runAs = config.RunAs; + _throwExceptions = config.ThrowExceptions; + _transferEncodingChunked = config.TransferEncodingChunked; + _headers = (config.Headers is null) ? null : new NameValueCollection(config.Headers); + _enableTcpStats = config.EnableTcpStats; + _enableThreadPoolStats = config.EnableThreadPoolStats; + _responseHeadersToParse = (config.ResponseHeadersToParse is null) ? null : new HeadersList(config.ResponseHeadersToParse); + _parseAllHeaders = config.ParseAllHeaders; + _requestMetaData = config.RequestMetaData; + _responseBuilders = [.. config.ResponseBuilders]; + } + + private string? _accept; + private IReadOnlyCollection? _allowedStatusCodes; + private AuthorizationHeader? _authentication; + private X509CertificateCollection? _clientCertificates; + private string? _contentType; + private bool? _disableDirectStreaming; + private bool? _disableAuditTrail; + private bool? _disablePings; + private bool? _disableSniff; + private bool? _httpPipeliningEnabled; + private bool? _enableHttpCompression; + private Uri? _forceNode; + private int? _maxRetries; + private TimeSpan? _maxRetryTimeout; + private string? _opaqueId; + private bool? _parseAllHeaders; + private TimeSpan? _pingTimeout; + private TimeSpan? _requestTimeout; + private HeadersList? _responseHeadersToParse; + private string? _runAs; + private bool? _throwExceptions; + private bool? _transferEncodingChunked; + private NameValueCollection? _headers; + private bool? _enableTcpStats; + private bool? _enableThreadPoolStats; + private RequestMetaData? _requestMetaData; + private List? _responseBuilders; + + /// + public RequestConfigurationDescriptor RunAs(string username) + { + _runAs = username; + return this; + } + + /// + public RequestConfigurationDescriptor RequestTimeout(TimeSpan requestTimeout) + { + _requestTimeout = requestTimeout; + return this; + } + + /// + public RequestConfigurationDescriptor OpaqueId(string opaqueId) + { + _opaqueId = opaqueId; + return this; + } + + /// + public RequestConfigurationDescriptor PingTimeout(TimeSpan pingTimeout) + { + _pingTimeout = pingTimeout; + return this; + } + + /// + public RequestConfigurationDescriptor ContentType(string contentTypeHeader) + { + _contentType = contentTypeHeader; + return this; + } + + /// + public RequestConfigurationDescriptor Accept(string acceptHeader) + { + _accept = acceptHeader; + return this; + } + + /// + public RequestConfigurationDescriptor AllowedStatusCodes(IEnumerable? codes) + { + _allowedStatusCodes = codes?.ToReadOnlyCollection(); + return this; + } + + /// + public RequestConfigurationDescriptor AllowedStatusCodes(params int[] codes) + { + _allowedStatusCodes = codes.ToReadOnlyCollection(); + return this; + } + + /// + public RequestConfigurationDescriptor DisableSniffing(bool disable = true) + { + _disableSniff = disable; + return this; + } + + /// + public RequestConfigurationDescriptor DisablePing(bool disable = true) + { + _disablePings = disable; + return this; + } + + /// + public RequestConfigurationDescriptor ThrowExceptions(bool throwExceptions = true) + { + _throwExceptions = throwExceptions; + return this; + } + + /// + public RequestConfigurationDescriptor DisableDirectStreaming(bool disable = true) + { + _disableDirectStreaming = disable; + return this; + } + + /// + public RequestConfigurationDescriptor DisableAuditTrail(bool disable = true) + { + _disableAuditTrail = disable; + return this; + } + + /// + public RequestConfigurationDescriptor ForceNode(Uri uri) + { + _forceNode = uri; + return this; + } + + /// + public RequestConfigurationDescriptor MaxRetries(int retry) + { + _maxRetries = retry; + return this; + } + + /// + public RequestConfigurationDescriptor MaxRetries(TimeSpan? timeout) + { + _maxRetryTimeout = timeout; + return this; + } + + /// + public RequestConfigurationDescriptor Authentication(AuthorizationHeader authentication) + { + _authentication = authentication; + return this; + } + + /// + public RequestConfigurationDescriptor EnableHttpPipelining(bool enable = true) + { + _httpPipeliningEnabled = enable; + return this; + } + + /// + public RequestConfigurationDescriptor EnableHttpCompression(bool enable = true) + { + _enableHttpCompression = enable; + return this; + } + + /// + public RequestConfigurationDescriptor ClientCertificates(X509CertificateCollection certificates) + { + _clientCertificates = certificates; + return this; + } + + /// + public RequestConfigurationDescriptor ClientCertificate(X509Certificate certificate) => + ClientCertificates(new X509Certificate2Collection { certificate }); + + /// + public RequestConfigurationDescriptor ClientCertificate(string certificatePath) => + ClientCertificates(new X509Certificate2Collection { new X509Certificate(certificatePath) }); + + /// + public RequestConfigurationDescriptor TransferEncodingChunked(bool transferEncodingChunked = true) + { + _transferEncodingChunked = transferEncodingChunked; + return this; + } + + /// + public RequestConfigurationDescriptor GlobalHeaders(NameValueCollection headers) + { + _headers = headers; + return this; + } + + /// + public RequestConfigurationDescriptor EnableTcpStats(bool enableTcpStats = true) + { + _enableTcpStats = enableTcpStats; + return this; + } + + /// + public RequestConfigurationDescriptor EnableThreadPoolStats(bool enableThreadPoolStats = true) + { + _enableThreadPoolStats = enableThreadPoolStats; + return this; + } + + /// + public RequestConfigurationDescriptor ParseAllHeaders(bool enable = true) + { + _parseAllHeaders = enable; + return this; + } + + /// + public RequestConfigurationDescriptor ResponseHeadersToParse(IEnumerable headers) + { + _responseHeadersToParse = new HeadersList(headers); + return this; + } + + /// + public RequestConfigurationDescriptor RequestMetaData(RequestMetaData metaData) + { + _requestMetaData = metaData; + return this; + } + + /// + public RequestConfigurationDescriptor ResponseBuilder(IResponseBuilder responseBuilder) + { + _responseBuilders ??= []; + _responseBuilders.Add(responseBuilder); + return this; + } + + string? IRequestConfiguration.Accept => _accept; + + IReadOnlyCollection? IRequestConfiguration.AllowedStatusCodes => _allowedStatusCodes; + + AuthorizationHeader? IRequestConfiguration.Authentication => _authentication; + + X509CertificateCollection? IRequestConfiguration.ClientCertificates => _clientCertificates; + + string? IRequestConfiguration.ContentType => _contentType; + + bool? IRequestConfiguration.DisableDirectStreaming => _disableDirectStreaming; + + bool? IRequestConfiguration.DisableAuditTrail => _disableAuditTrail; + + bool? IRequestConfiguration.DisablePings => _disablePings; + + bool? IRequestConfiguration.DisableSniff => _disableSniff; + + bool? IRequestConfiguration.HttpPipeliningEnabled => _httpPipeliningEnabled; + + bool? IRequestConfiguration.EnableHttpCompression => _enableHttpCompression; + + Uri? IRequestConfiguration.ForceNode => _forceNode; + + int? IRequestConfiguration.MaxRetries => _maxRetries; + + TimeSpan? IRequestConfiguration.MaxRetryTimeout => _maxRetryTimeout; + + string? IRequestConfiguration.OpaqueId => _opaqueId; + + bool? IRequestConfiguration.ParseAllHeaders => _parseAllHeaders; + + TimeSpan? IRequestConfiguration.PingTimeout => _pingTimeout; + + TimeSpan? IRequestConfiguration.RequestTimeout => _requestTimeout; + + HeadersList? IRequestConfiguration.ResponseHeadersToParse => _responseHeadersToParse; + + string? IRequestConfiguration.RunAs => _runAs; + + bool? IRequestConfiguration.ThrowExceptions => _throwExceptions; + + bool? IRequestConfiguration.TransferEncodingChunked => _transferEncodingChunked; + + NameValueCollection? IRequestConfiguration.Headers => _headers; + + bool? IRequestConfiguration.EnableTcpStats => _enableTcpStats; + + bool? IRequestConfiguration.EnableThreadPoolStats => _enableThreadPoolStats; + + RequestMetaData? IRequestConfiguration.RequestMetaData => _requestMetaData; + + IReadOnlyCollection IRequestConfiguration.ResponseBuilders => _responseBuilders ?? []; +} diff --git a/src/Elastic.Transport/Configuration/TransportConfigurationDescriptor.cs b/src/Elastic.Transport/Configuration/TransportConfigurationDescriptor.cs index f062331..86c8c7f 100644 --- a/src/Elastic.Transport/Configuration/TransportConfigurationDescriptor.cs +++ b/src/Elastic.Transport/Configuration/TransportConfigurationDescriptor.cs @@ -161,7 +161,7 @@ protected TransportConfigurationDescriptorBase(NodePool nodePool, IRequestInvoke private readonly MetaHeaderProvider? _metaHeaderProvider; private HeadersList? _responseHeadersToParse; private bool? _parseAllHeaders; - private DateTimeProvider _dateTimeProvider; + private readonly DateTimeProvider _dateTimeProvider; private RequestPipelineFactory _pipelineProvider; private List? _responseBuilders; @@ -199,8 +199,8 @@ protected TransportConfigurationDescriptorBase(NodePool nodePool, IRequestInvoke Func ITransportConfiguration.StatusCodeToResponseSuccess => _statusCodeToResponseSuccess; TimeSpan ITransportConfiguration.DnsRefreshTimeout => _dnsRefreshTimeout; bool ITransportConfiguration.PrettyJson => _prettyJson; - IReadOnlyCollection ITransportConfiguration.ResponseBuilders => _responseBuilders ?? []; + IReadOnlyCollection IRequestConfiguration.ResponseBuilders => _responseBuilders ?? []; HeadersList? IRequestConfiguration.ResponseHeadersToParse => _responseHeadersToParse; string? IRequestConfiguration.RunAs => _runAs; bool? IRequestConfiguration.ThrowExceptions => _throwExceptions; @@ -371,7 +371,7 @@ public T OnRequestDataCreated(Action handler) => /// Return true if you want the node to be used for API calls public T NodePredicate(Func predicate) => Assign(predicate, static (a, v) => a._nodePredicate = v); - /// + /// public T ResponseBuilder(IResponseBuilder responseBuilder) => Assign(responseBuilder, static (a, v) => { a._responseBuilders ??= []; diff --git a/src/Elastic.Transport/Responses/DefaultResponseFactory.cs b/src/Elastic.Transport/Responses/DefaultResponseFactory.cs index 2acd303..b5671ea 100644 --- a/src/Elastic.Transport/Responses/DefaultResponseFactory.cs +++ b/src/Elastic.Transport/Responses/DefaultResponseFactory.cs @@ -86,7 +86,7 @@ private async ValueTask CreateCoreAsync( TResponse? response = null; if (MayHaveBody(statusCode, endpoint.Method, contentLength) - && TryResolveBuilder(requestData.ProductResponseBuilders, requestData.ResponseBuilders, out var builder)) + && TryResolveBuilder(requestData.ResponseBuilders, requestData.ProductResponseBuilders, out var builder)) { var ownsStream = false; @@ -115,7 +115,7 @@ private async ValueTask CreateCoreAsync( } if (ownsStream && (response is null || !response.LeaveOpen)) - responseStream.Dispose(); + responseStream?.Dispose(); } response ??= new TResponse(); @@ -123,8 +123,8 @@ private async ValueTask CreateCoreAsync( return response; } - private bool TryResolveBuilder(IReadOnlyCollection productResponseBuilders, - IReadOnlyCollection responseBuilders, out IResponseBuilder builder + private bool TryResolveBuilder(IReadOnlyCollection responseBuilders, + IReadOnlyCollection productResponseBuilders, out IResponseBuilder builder ) where TResponse : TransportResponse, new() { if (_resolvedBuilders.TryGetValue(typeof(TResponse), out builder)) diff --git a/tests/Elastic.Transport.Tests/Configuration/RequestConfigurationTests.cs b/tests/Elastic.Transport.Tests/Configuration/RequestConfigurationTests.cs index 4380c75..bd06dd5 100644 --- a/tests/Elastic.Transport.Tests/Configuration/RequestConfigurationTests.cs +++ b/tests/Elastic.Transport.Tests/Configuration/RequestConfigurationTests.cs @@ -38,7 +38,7 @@ public void SameDefaults() public void CopiesAllProperties() { var autoFaker = new AutoFaker(); - autoFaker.RuleFor(x => x.ClientCertificates, f => new X509CertificateCollection()); + autoFaker.RuleFor(x => x.ClientCertificates, f => []); var config = autoFaker.Generate(); config.Accept.Should().NotBeEmpty();