diff --git a/Elastic.Transport.sln.DotSettings b/Elastic.Transport.sln.DotSettings index ae0ae0b..fa6ec13 100644 --- a/Elastic.Transport.sln.DotSettings +++ b/Elastic.Transport.sln.DotSettings @@ -104,7 +104,7 @@ See the LICENSE file in the project root for more information </Entry.Match> <Entry.SortBy> <Kind Is="Member" /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="Fields"> @@ -119,7 +119,7 @@ See the LICENSE file in the project root for more information <Entry.SortBy> <Access /> <Readonly /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="Constructors"> @@ -139,7 +139,7 @@ See the LICENSE file in the project root for more information </Entry.Match> <Entry.SortBy> <Access /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="Setup/Teardown Methods" Priority="100"> @@ -203,7 +203,7 @@ See the LICENSE file in the project root for more information </Entry.Match> <Entry.SortBy> <Kind Is="Member" /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="Fields"> @@ -218,7 +218,7 @@ See the LICENSE file in the project root for more information <Entry.SortBy> <Access /> <Readonly /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="Constructors"> @@ -238,7 +238,7 @@ See the LICENSE file in the project root for more information </Entry.Match> <Entry.SortBy> <Access /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="Interface Implementations"> @@ -251,7 +251,7 @@ See the LICENSE file in the project root for more information <Entry.SortBy> <ImplementsInterface Name="IDisposable" /> <Access /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="All other members" /> @@ -505,6 +505,7 @@ See the LICENSE file in the project root for more information True True True + True True True False diff --git a/src/Elastic.Transport.VirtualizedCluster/Components/VirtualClusterConnection.cs b/src/Elastic.Transport.VirtualizedCluster/Components/VirtualClusterConnection.cs index e357c1f..873c12e 100644 --- a/src/Elastic.Transport.VirtualizedCluster/Components/VirtualClusterConnection.cs +++ b/src/Elastic.Transport.VirtualizedCluster/Components/VirtualClusterConnection.cs @@ -2,6 +2,7 @@ // 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 +#nullable enable using System; using System.Collections.Generic; using System.IO; @@ -34,7 +35,7 @@ public class VirtualClusterRequestInvoker : IRequestInvoker { private static readonly object Lock = new(); - private static byte[] _defaultResponseBytes; + private static byte[]? _defaultResponseBytes; private VirtualCluster _cluster; private readonly TestableDateTimeProvider _dateTimeProvider; @@ -45,7 +46,9 @@ public class VirtualClusterRequestInvoker : IRequestInvoker internal VirtualClusterRequestInvoker(VirtualCluster cluster, TestableDateTimeProvider dateTimeProvider) { - UpdateCluster(cluster); + _cluster = cluster; + _calls = cluster.Nodes.ToDictionary(n => n.Uri.Port, v => new State()); + _productRegistration = cluster.ProductRegistration; _dateTimeProvider = dateTimeProvider; _productRegistration = cluster.ProductRegistration; _inMemoryRequestInvoker = new InMemoryRequestInvoker(); @@ -100,40 +103,41 @@ private static object DefaultResponse private void UpdateCluster(VirtualCluster cluster) { - if (cluster == null) return; - lock (Lock) { _cluster = cluster; _calls = cluster.Nodes.ToDictionary(n => n.Uri.Port, v => new State()); _productRegistration = cluster.ProductRegistration; } + } - private bool IsSniffRequest(RequestData requestData) => _productRegistration.IsSniffRequest(requestData); + private bool IsSniffRequest(Endpoint endpoint) => _productRegistration.IsSniffRequest(endpoint); - private bool IsPingRequest(RequestData requestData) => _productRegistration.IsPingRequest(requestData); + private bool IsPingRequest(Endpoint endpoint) => _productRegistration.IsPingRequest(endpoint); /// > - public Task RequestAsync(RequestData requestData, CancellationToken cancellationToken) + public Task RequestAsync(Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken) where TResponse : TransportResponse, new() => - Task.FromResult(Request(requestData)); + Task.FromResult(Request(endpoint, requestData, postData)); /// > - public TResponse Request(RequestData requestData) + public TResponse Request(Endpoint endpoint, RequestData requestData, PostData? postData) where TResponse : TransportResponse, new() { - if (!_calls.ContainsKey(requestData.Uri.Port)) - throw new Exception($"Expected a call to happen on port {requestData.Uri.Port} but received none"); + if (!_calls.ContainsKey(endpoint.Uri.Port)) + throw new Exception($"Expected a call to happen on port {endpoint.Uri.Port} but received none"); try { - var state = _calls[requestData.Uri.Port]; - if (IsSniffRequest(requestData)) + var state = _calls[endpoint.Uri.Port]; + if (IsSniffRequest(endpoint)) { _ = Interlocked.Increment(ref state.Sniffed); return HandleRules( + endpoint, requestData, + postData, nameof(VirtualCluster.Sniff), _cluster.SniffingRules, requestData.RequestTimeout, @@ -141,11 +145,13 @@ public TResponse Request(RequestData requestData) (r) => _productRegistration.CreateSniffResponseBytes(_cluster.Nodes, _cluster.ElasticsearchVersion, _cluster.PublishAddressOverride, _cluster.SniffShouldReturnFqnd) ); } - if (IsPingRequest(requestData)) + if (IsPingRequest(endpoint)) { _ = Interlocked.Increment(ref state.Pinged); return HandleRules( + endpoint, requestData, + postData, nameof(VirtualCluster.Ping), _cluster.PingingRules, requestData.PingTimeout, @@ -155,7 +161,9 @@ public TResponse Request(RequestData requestData) } _ = Interlocked.Increment(ref state.Called); return HandleRules( + endpoint, requestData, + postData, nameof(VirtualCluster.ClientCalls), _cluster.ClientCallRules, requestData.RequestTimeout, @@ -165,22 +173,23 @@ public TResponse Request(RequestData requestData) } catch (TheException e) { - return requestData.ConnectionSettings.ProductRegistration.ResponseBuilder.ToResponse(requestData, e, null, null, Stream.Null, null, -1, null, null); + return requestData.ConnectionSettings.ProductRegistration.ResponseBuilder.ToResponse(endpoint, requestData, postData, e, null, null, Stream.Null, null, -1, null, null); } } private TResponse HandleRules( + Endpoint endpoint, RequestData requestData, + PostData? postData, string origin, IList rules, TimeSpan timeout, Action beforeReturn, - Func successResponse + Func successResponse ) where TResponse : TransportResponse, new() where TRule : IRule { - requestData.MadeItToResponse = true; if (rules.Count == 0) throw new Exception($"No {origin} defined for the current VirtualCluster, so we do not know how to respond"); @@ -189,32 +198,31 @@ Func successResponse var always = rule.Times.Match(t => true, t => false); var times = rule.Times.Match(t => -1, t => t); - if (rule.OnPort == null || rule.OnPort.Value != requestData.Uri.Port) continue; + if (rule.OnPort == null || rule.OnPort.Value != endpoint.Uri.Port) continue; if (always) - return Always(requestData, timeout, beforeReturn, successResponse, rule); + return Always(endpoint, requestData, postData, timeout, beforeReturn, successResponse, rule); if (rule.ExecuteCount > times) continue; - return Sometimes(requestData, timeout, beforeReturn, successResponse, rule); + return Sometimes(endpoint, requestData, postData, timeout, beforeReturn, successResponse, rule); } foreach (var rule in rules.Where(s => !s.OnPort.HasValue)) { var always = rule.Times.Match(t => true, t => false); var times = rule.Times.Match(t => -1, t => t); if (always) - return Always(requestData, timeout, beforeReturn, successResponse, rule); + return Always(endpoint, requestData, postData, timeout, beforeReturn, successResponse, rule); if (rule.ExecuteCount > times) continue; - return Sometimes(requestData, timeout, beforeReturn, successResponse, rule); + return Sometimes(endpoint, requestData, postData, timeout, beforeReturn, successResponse, rule); } var count = _calls.Select(kv => kv.Value.Called).Sum(); - throw new Exception($@"No global or port specific {origin} rule ({requestData.Uri.Port}) matches any longer after {count} calls in to the cluster"); + throw new Exception($@"No global or port specific {origin} rule ({endpoint.Uri.Port}) matches any longer after {count} calls in to the cluster"); } - private TResponse Always(RequestData requestData, TimeSpan timeout, Action beforeReturn, - Func successResponse, TRule rule + private TResponse Always(Endpoint endpoint, RequestData requestData, PostData? postData, TimeSpan timeout, Action beforeReturn, Func successResponse, TRule rule ) where TResponse : TransportResponse, new() where TRule : IRule @@ -231,12 +239,12 @@ private TResponse Always(RequestData requestData, TimeSpan tim } return rule.Succeeds - ? Success(requestData, beforeReturn, successResponse, rule) - : Fail(requestData, rule); + ? Success(endpoint, requestData, postData, beforeReturn, successResponse, rule) + : Fail(endpoint, requestData, postData, rule); } private TResponse Sometimes( - RequestData requestData, TimeSpan timeout, Action beforeReturn, Func successResponse, TRule rule + Endpoint endpoint, RequestData requestData, PostData? postData, TimeSpan timeout, Action beforeReturn, Func successResponse, TRule rule ) where TResponse : TransportResponse, new() where TRule : IRule @@ -253,16 +261,16 @@ private TResponse Sometimes( } if (rule.Succeeds) - return Success(requestData, beforeReturn, successResponse, rule); + return Success(endpoint, requestData, postData, beforeReturn, successResponse, rule); - return Fail(requestData, rule); + return Fail(endpoint, requestData, postData, rule); } - private TResponse Fail(RequestData requestData, TRule rule, RuleOption returnOverride = null) + private TResponse Fail(Endpoint endpoint, RequestData requestData, PostData? postData, TRule rule, RuleOption? returnOverride = null) where TResponse : TransportResponse, new() where TRule : IRule { - var state = _calls[requestData.Uri.Port]; + var state = _calls[endpoint.Uri.Port]; _ = Interlocked.Increment(ref state.Failures); var ret = returnOverride ?? rule.Return; rule.RecordExecuted(); @@ -271,25 +279,25 @@ private TResponse Fail(RequestData requestData, TRule rule, Ru throw new TheException(); return ret.Match( - (e) => throw e, - (statusCode) => _inMemoryRequestInvoker.BuildResponse(requestData, CallResponse(rule), + e => throw e, + statusCode => _inMemoryRequestInvoker.BuildResponse(endpoint, requestData, postData, CallResponse(rule), //make sure we never return a valid status code in Fail responses because of a bad rule. statusCode >= 200 && statusCode < 300 ? 502 : statusCode, rule.ReturnContentType) ); } - private TResponse Success(RequestData requestData, Action beforeReturn, Func successResponse, + private TResponse Success(Endpoint endpoint, RequestData requestData, PostData? postData, Action beforeReturn, Func successResponse, TRule rule ) where TResponse : TransportResponse, new() where TRule : IRule { - var state = _calls[requestData.Uri.Port]; + var state = _calls[endpoint.Uri.Port]; _ = Interlocked.Increment(ref state.Successes); rule.RecordExecuted(); beforeReturn?.Invoke(rule); - return _inMemoryRequestInvoker.BuildResponse(requestData, successResponse(rule), contentType: rule.ReturnContentType); + return _inMemoryRequestInvoker.BuildResponse(endpoint, requestData, postData, successResponse(rule), contentType: rule.ReturnContentType); } private static byte[] CallResponse(TRule rule) diff --git a/src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs b/src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs index 7ede512..6ab83c4 100644 --- a/src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs +++ b/src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs @@ -18,7 +18,9 @@ public class VirtualizedCluster private Func, Func, Task> _asyncCall; private Func, Func, TransportResponse> _syncCall; - private class VirtualResponse : TransportResponse { } + private class VirtualResponse : TransportResponse; + + private static readonly EndpointPath RootPath = new(HttpMethod.GET, "/"); internal VirtualizedCluster(TestableDateTimeProvider dateTimeProvider, TransportConfiguration settings) { @@ -27,24 +29,20 @@ internal VirtualizedCluster(TestableDateTimeProvider dateTimeProvider, Transport _exposingRequestPipeline = new ExposingPipelineFactory(settings, _dateTimeProvider); _syncCall = (t, r) => t.Request( - method: HttpMethod.GET, - path: "/", + path: RootPath, postData: PostData.Serializable(new { }), - requestParameters: new DefaultRequestParameters(), openTelemetryData: default, - localConfiguration: r?.Invoke(new RequestConfigurationDescriptor(null)), + localConfiguration: r?.Invoke(new RequestConfigurationDescriptor()), responseBuilder: null ); _asyncCall = async (t, r) => { var res = await t.RequestAsync ( - method: HttpMethod.GET, - path: "/", + path: RootPath, postData: PostData.Serializable(new { }), - requestParameters: new DefaultRequestParameters(), openTelemetryData: default, - localConfiguration: r?.Invoke(new RequestConfigurationDescriptor(null)), + localConfiguration: r?.Invoke(new RequestConfigurationDescriptor()), responseBuilder: null, CancellationToken.None ).ConfigureAwait(false); diff --git a/src/Elastic.Transport.VirtualizedCluster/Products/Elasticsearch/ElasticsearchMockProductRegistration.cs b/src/Elastic.Transport.VirtualizedCluster/Products/Elasticsearch/ElasticsearchMockProductRegistration.cs index 5f3c3f5..7e92e1c 100644 --- a/src/Elastic.Transport.VirtualizedCluster/Products/Elasticsearch/ElasticsearchMockProductRegistration.cs +++ b/src/Elastic.Transport.VirtualizedCluster/Products/Elasticsearch/ElasticsearchMockProductRegistration.cs @@ -22,10 +22,9 @@ public sealed class ElasticsearchMockProductRegistration : MockProductRegistrati public override byte[] CreateSniffResponseBytes(IReadOnlyList nodes, string stackVersion, string publishAddressOverride, bool returnFullyQualifiedDomainNames) => ElasticsearchSniffResponseFactory.Create(nodes, stackVersion, publishAddressOverride, returnFullyQualifiedDomainNames); - public override bool IsSniffRequest(RequestData requestData) => - requestData.PathAndQuery.StartsWith(ElasticsearchProductRegistration.SniffPath, StringComparison.Ordinal); + public override bool IsSniffRequest(Endpoint endpoint) => + endpoint.PathAndQuery.StartsWith(ElasticsearchProductRegistration.SniffPath, StringComparison.Ordinal); - public override bool IsPingRequest(RequestData requestData) => - requestData.Method == HttpMethod.HEAD && - (requestData.PathAndQuery == string.Empty || requestData.PathAndQuery.StartsWith("?")); + public override bool IsPingRequest(Endpoint endpoint) => + endpoint.Method == HttpMethod.HEAD && (endpoint.PathAndQuery == string.Empty || endpoint.PathAndQuery.StartsWith("?")); } diff --git a/src/Elastic.Transport.VirtualizedCluster/Products/MockProductRegistration.cs b/src/Elastic.Transport.VirtualizedCluster/Products/MockProductRegistration.cs index 63df510..a738634 100644 --- a/src/Elastic.Transport.VirtualizedCluster/Products/MockProductRegistration.cs +++ b/src/Elastic.Transport.VirtualizedCluster/Products/MockProductRegistration.cs @@ -31,7 +31,7 @@ public abstract class MockProductRegistration /// see uses this to determine if the current request is a sniff request and should follow /// the sniffing rules /// - public abstract bool IsSniffRequest(RequestData requestData); + public abstract bool IsSniffRequest(Endpoint endpoint); - public abstract bool IsPingRequest(RequestData requestData); + public abstract bool IsPingRequest(Endpoint endpoint); } diff --git a/src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs b/src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs index d1cf207..af31cad 100644 --- a/src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs +++ b/src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs @@ -47,7 +47,7 @@ internal DefaultRequestPipeline( _productRegistration = configurationValues.ProductRegistration; _responseBuilder = _productRegistration.ResponseBuilder; _nodePredicate = _settings.NodePredicate ?? _productRegistration.NodePredicate; - RequestConfiguration = requestConfiguration; + RequestConfig = requestConfiguration; StartedOn = dateTimeProvider.Now(); } @@ -66,9 +66,9 @@ private RequestConfiguration PingAndSniffRequestConfiguration { PingTimeout = PingTimeout, RequestTimeout = PingTimeout, - AuthenticationHeader = RequestConfiguration?.AuthenticationHeader ?? _settings.Authentication, - EnableHttpPipelining = RequestConfiguration?.EnableHttpPipelining ?? _settings.HttpPipeliningEnabled, - ForceNode = RequestConfiguration?.ForceNode + Authentication = RequestConfig?.Authentication ?? _settings.Authentication, + EnableHttpPipelining = RequestConfig?.HttpPipeliningEnabled ?? _settings.HttpPipeliningEnabled, + ForceNode = RequestConfig?.ForceNode }; return _pingAndSniffRequestConfiguration; @@ -100,9 +100,9 @@ public override bool IsTakingTooLong } public override int MaxRetries => - RequestConfiguration?.ForceNode != null + RequestConfig?.ForceNode != null ? 0 - : Math.Min(RequestConfiguration?.MaxRetries ?? _settings.MaxRetries.GetValueOrDefault(int.MaxValue), _nodePool.MaxRetries); + : Math.Min(RequestConfig?.MaxRetries ?? _settings.MaxRetries.GetValueOrDefault(int.MaxValue), _nodePool.MaxRetries); public bool Refresh { get; private set; } @@ -141,77 +141,86 @@ public override bool StaleClusterState public override DateTimeOffset StartedOn { get; } private TimeSpan PingTimeout => - RequestConfiguration?.PingTimeout + RequestConfig?.PingTimeout ?? _settings.PingTimeout - ?? (_nodePool.UsingSsl ? TransportConfiguration.DefaultPingTimeoutOnSsl : TransportConfiguration.DefaultPingTimeout); + ?? (_nodePool.UsingSsl ? RequestConfiguration.DefaultPingTimeoutOnSsl : RequestConfiguration.DefaultPingTimeout); - private IRequestConfiguration RequestConfiguration { get; } + private IRequestConfiguration RequestConfig { get; } - private bool RequestDisabledSniff => RequestConfiguration != null && (RequestConfiguration.DisableSniff ?? false); + private bool RequestDisabledSniff => RequestConfig != null && (RequestConfig.DisableSniff ?? false); - private TimeSpan RequestTimeout => RequestConfiguration?.RequestTimeout ?? _settings.RequestTimeout; + private TimeSpan RequestTimeout => RequestConfig?.RequestTimeout ?? _settings.RequestTimeout ?? RequestConfiguration.DefaultRequestTimeout; public override void AuditCancellationRequested() => Audit(CancellationRequested).Dispose(); - public override void BadResponse(ref TResponse response, ApiCallDetails callDetails, RequestData data, TransportException exception) + public override void BadResponse(ref TResponse response, ApiCallDetails callDetails, Endpoint endpoint, RequestData data, PostData? postData, TransportException exception) { if (response == null) { //make sure we copy over the error body in case we disabled direct streaming. var s = callDetails?.ResponseBodyInBytes == null ? Stream.Null : _memoryStreamFactory.Create(callDetails.ResponseBodyInBytes); var m = callDetails?.ResponseMimeType ?? RequestData.DefaultMimeType; - response = _responseBuilder.ToResponse(data, exception, callDetails?.HttpStatusCode, null, s, m, callDetails?.ResponseBodyInBytes?.Length ?? -1, null, null); + response = _responseBuilder.ToResponse(endpoint, data, postData, exception, callDetails?.HttpStatusCode, null, s, m, callDetails?.ResponseBodyInBytes?.Length ?? -1, null, null); } response.ApiCallDetails.AuditTrail = AuditTrail; } - public override TResponse CallProductEndpoint(RequestData requestData) - => CallProductEndpointCoreAsync(false, requestData).EnsureCompleted(); + public override TResponse CallProductEndpoint(Endpoint endpoint, RequestData requestData, PostData? postData) + => CallProductEndpointCoreAsync(false, endpoint, requestData, postData).EnsureCompleted(); - public override Task CallProductEndpointAsync(RequestData requestData, CancellationToken cancellationToken = default) - => CallProductEndpointCoreAsync(true, requestData, cancellationToken).AsTask(); + public override Task CallProductEndpointAsync(Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken = default) + => CallProductEndpointCoreAsync(true, endpoint, requestData, postData, cancellationToken).AsTask(); - private async ValueTask CallProductEndpointCoreAsync(bool isAsync, RequestData requestData, CancellationToken cancellationToken = default) + private async ValueTask CallProductEndpointCoreAsync(bool isAsync, Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() { - using var audit = Audit(HealthyResponse, requestData.Node); + using var audit = Audit(HealthyResponse, endpoint.Node); if (audit is not null) - audit.PathAndQuery = requestData.PathAndQuery; + audit.PathAndQuery = endpoint.PathAndQuery; try { TResponse response; if (isAsync) - response = await _requestInvoker.RequestAsync(requestData, cancellationToken).ConfigureAwait(false); + response = await _requestInvoker.RequestAsync(endpoint, requestData, postData, cancellationToken).ConfigureAwait(false); else - response = _requestInvoker.Request(requestData); + response = _requestInvoker.Request(endpoint, requestData, postData); response.ApiCallDetails.AuditTrail = AuditTrail; ThrowBadAuthPipelineExceptionWhenNeeded(response.ApiCallDetails, response); if (!response.ApiCallDetails.HasSuccessfulStatusCodeAndExpectedContentType && audit is not null) - audit.Event = requestData.OnFailureAuditEvent; + { + var @event = response.ApiCallDetails.HttpStatusCode != null ? AuditEvent.BadResponse : BadRequest; + audit.Event = @event; + } return response; } catch (Exception e) when (audit is not null) { - audit.Event = requestData.OnFailureAuditEvent; + var @event = e is TransportException t && t.ApiCallDetails.HttpStatusCode != null ? AuditEvent.BadResponse : BadRequest; + audit.Event = @event; audit.Exception = e; throw; } } - public override TransportException? CreateClientException(TResponse response, ApiCallDetails? callDetails, - RequestData data, List? seenExceptions) + public override TransportException? CreateClientException( + TResponse response, + ApiCallDetails? callDetails, + Endpoint endpoint, + RequestData data, + List? seenExceptions + ) { if (callDetails?.HasSuccessfulStatusCodeAndExpectedContentType ?? false) return null; - var pipelineFailure = data.OnFailurePipelineFailure; + var pipelineFailure = callDetails?.HttpStatusCode != null ? PipelineFailure.BadResponse : PipelineFailure.BadRequest; var innerException = callDetails?.OriginalException; if (seenExceptions is not null && seenExceptions.HasAny(out var exs)) { @@ -253,7 +262,7 @@ private async ValueTask CallProductEndpointCoreAsync(bool var clientException = new TransportException(pipelineFailure, exceptionMessage, innerException) { - Request = data, + Endpoint = endpoint, ApiCallDetails = callDetails, AuditTrail = AuditTrail }; @@ -265,7 +274,7 @@ public override void FirstPoolUsage(SemaphoreSlim semaphore) { if (!FirstPoolUsageNeedsSniffing) return; - if (!semaphore.Wait(_settings.RequestTimeout)) + if (!semaphore.Wait(RequestTimeout)) { if (FirstPoolUsageNeedsSniffing) throw new PipelineException(PipelineFailure.CouldNotStartSniffOnStartup, null); @@ -299,7 +308,7 @@ public override async Task FirstPoolUsageAsync(SemaphoreSlim semaphore, Cancella // TODO cancellationToken could throw here and will bubble out as OperationCancelledException // everywhere else it would bubble out wrapped in a `UnexpectedTransportException` - var success = await semaphore.WaitAsync(_settings.RequestTimeout, cancellationToken).ConfigureAwait(false); + var success = await semaphore.WaitAsync(RequestTimeout, cancellationToken).ConfigureAwait(false); if (!success) { if (FirstPoolUsageNeedsSniffing) @@ -353,9 +362,9 @@ public override bool TryGetSingleNode(out Node node) public override IEnumerable NextNode() { - if (RequestConfiguration?.ForceNode != null) + if (RequestConfig?.ForceNode != null) { - yield return new Node(RequestConfiguration.ForceNode); + yield return new Node(RequestConfig.ForceNode); yield break; } @@ -398,27 +407,33 @@ public async ValueTask PingCoreAsync(bool isAsync, Node node, CancellationToken if (!_productRegistration.SupportsPing) return; if (PingDisabled(node)) return; - var pingData = _productRegistration.CreatePingRequestData(node, PingAndSniffRequestConfiguration, _settings, _memoryStreamFactory); + var pingEndpoint = _productRegistration.CreatePingEndpoint(node, PingAndSniffRequestConfiguration); using var audit = Audit(PingSuccess, node); if (audit is not null) - audit.PathAndQuery = pingData.PathAndQuery; + audit.PathAndQuery = pingEndpoint.PathAndQuery; TransportResponse response; + //TODO remove + var requestData = new RequestData(_settings, null, null, _memoryStreamFactory); + try { if (isAsync) - response = await _productRegistration.PingAsync(_requestInvoker, pingData, cancellationToken).ConfigureAwait(false); + response = await _productRegistration.PingAsync(_requestInvoker, pingEndpoint, requestData, cancellationToken).ConfigureAwait(false); else - response = _productRegistration.Ping(_requestInvoker, pingData); + response = _productRegistration.Ping(_requestInvoker, pingEndpoint, requestData); ThrowBadAuthPipelineExceptionWhenNeeded(response.ApiCallDetails); //ping should not silently accept bad but valid http responses if (!response.ApiCallDetails.HasSuccessfulStatusCodeAndExpectedContentType) - throw new PipelineException(pingData.OnFailurePipelineFailure, response.ApiCallDetails.OriginalException) { Response = response }; + { + var pipelineFailure = response.ApiCallDetails.HttpStatusCode != null ? PipelineFailure.BadResponse : PipelineFailure.BadRequest; + throw new PipelineException(pipelineFailure, response.ApiCallDetails.OriginalException) { Response = response }; + } } catch (Exception e) { @@ -445,13 +460,14 @@ public async ValueTask SniffCoreAsync(bool isAsync, CancellationToken cancellati foreach (var node in SniffNodes) { - var requestData = - _productRegistration.CreateSniffRequestData(node, PingAndSniffRequestConfiguration, _settings, _memoryStreamFactory); + var sniffEndpoint = _productRegistration.CreateSniffEndpoint(node, PingAndSniffRequestConfiguration, _settings); + //TODO remove + var requestData = new RequestData(_settings, null, null, _memoryStreamFactory); using var audit = Audit(SniffSuccess, node); if (audit is not null) - audit.PathAndQuery = requestData.PathAndQuery; + audit.PathAndQuery = sniffEndpoint.PathAndQuery; Tuple> result; @@ -459,17 +475,20 @@ public async ValueTask SniffCoreAsync(bool isAsync, CancellationToken cancellati { if (isAsync) result = await _productRegistration - .SniffAsync(_requestInvoker, _nodePool.UsingSsl, requestData, cancellationToken) + .SniffAsync(_requestInvoker, _nodePool.UsingSsl, sniffEndpoint, requestData, cancellationToken) .ConfigureAwait(false); else result = _productRegistration - .Sniff(_requestInvoker, _nodePool.UsingSsl, requestData); + .Sniff(_requestInvoker, _nodePool.UsingSsl, sniffEndpoint, requestData); ThrowBadAuthPipelineExceptionWhenNeeded(result.Item1.ApiCallDetails); //sniff should not silently accept bad but valid http responses if (!result.Item1.ApiCallDetails.HasSuccessfulStatusCodeAndExpectedContentType) - throw new PipelineException(requestData.OnFailurePipelineFailure, result.Item1.ApiCallDetails.OriginalException) { Response = result.Item1 }; + { + var pipelineFailure = result.Item1.ApiCallDetails.HttpStatusCode != null ? PipelineFailure.BadResponse : PipelineFailure.BadRequest; + throw new PipelineException(pipelineFailure, result.Item1.ApiCallDetails.OriginalException) { Response = result.Item1 }; + } _nodePool.Reseed(result.Item2); Refresh = true; @@ -528,20 +547,19 @@ public override async Task SniffOnStaleClusterAsync(CancellationToken cancellati } } - public override void ThrowNoNodesAttempted(RequestData requestData, List? seenExceptions) + public override void ThrowNoNodesAttempted(Endpoint endpoint, List? seenExceptions) { - var clientException = new TransportException(PipelineFailure.NoNodesAttempted, RequestPipelineStatics.NoNodesAttemptedMessage, - (Exception)null); + var clientException = new TransportException(PipelineFailure.NoNodesAttempted, RequestPipelineStatics.NoNodesAttemptedMessage, (Exception)null); using (Audit(NoNodesAttempted)) - throw new UnexpectedTransportException(clientException, seenExceptions) { Request = requestData, AuditTrail = AuditTrail }; + throw new UnexpectedTransportException(clientException, seenExceptions) { Endpoint = endpoint, AuditTrail = AuditTrail }; } private bool PingDisabled(Node node) => - (RequestConfiguration?.DisablePing).GetValueOrDefault(false) - || _settings.DisablePings || !_nodePool.SupportsPinging || !node.IsResurrected; + (RequestConfig?.DisablePings).GetValueOrDefault(false) + || (_settings.DisablePings ?? false) || !_nodePool.SupportsPinging || !node.IsResurrected; - private Auditable Audit(AuditEvent type, Node node = null) => - !_settings.DisableAuditTrail ? (new(type, ref _auditTrail, _dateTimeProvider, node)) : null; + private Auditable? Audit(AuditEvent type, Node node = null) => + !_settings.DisableAuditTrail ?? true ? new(type, ref _auditTrail, _dateTimeProvider, node) : null; private static void ThrowBadAuthPipelineExceptionWhenNeeded(ApiCallDetails details, TransportResponse response = null) { diff --git a/src/Elastic.Transport/Components/Pipeline/DefaultResponseBuilder.cs b/src/Elastic.Transport/Components/Pipeline/DefaultResponseBuilder.cs index e7b2a77..0bb90c3 100644 --- a/src/Elastic.Transport/Components/Pipeline/DefaultResponseBuilder.cs +++ b/src/Elastic.Transport/Components/Pipeline/DefaultResponseBuilder.cs @@ -33,7 +33,7 @@ internal static class ResponseBuilderDefaults /// /// A helper class that deals with handling how a is transformed to the requested /// implementation. This includes handling optionally buffering based on -/// . And handling short circuiting special responses +/// . And handling short circuiting special responses /// such as , and /// internal class DefaultResponseBuilder : ResponseBuilder where TError : ErrorResponse, new() @@ -46,7 +46,9 @@ internal static class ResponseBuilderDefaults /// Create an instance of from /// public override TResponse ToResponse( + Endpoint endpoint, RequestData requestData, + PostData postData, Exception ex, int? statusCode, Dictionary> headers, @@ -59,12 +61,12 @@ IReadOnlyDictionary tcpStats { responseStream.ThrowIfNull(nameof(responseStream)); - var details = Initialize(requestData, ex, statusCode, headers, mimeType, threadPoolStats, tcpStats, contentLength); + var details = Initialize(endpoint, requestData, postData, ex, statusCode, headers, mimeType, threadPoolStats, tcpStats, contentLength); TResponse response = null; // Only attempt to set the body if the response may have content - if (MayHaveBody(statusCode, requestData.Method, contentLength)) + if (MayHaveBody(statusCode, endpoint.Method, contentLength)) response = SetBody(details, requestData, responseStream, mimeType); response ??= new TResponse(); @@ -76,7 +78,9 @@ IReadOnlyDictionary tcpStats /// Create an instance of from /// public override async Task ToResponseAsync( + Endpoint endpoint, RequestData requestData, + PostData postData, Exception ex, int? statusCode, Dictionary> headers, @@ -90,12 +94,12 @@ public override async Task ToResponseAsync( { responseStream.ThrowIfNull(nameof(responseStream)); - var details = Initialize(requestData, ex, statusCode, headers, mimeType, threadPoolStats, tcpStats, contentLength); + var details = Initialize(endpoint, requestData, postData, ex, statusCode, headers, mimeType, threadPoolStats, tcpStats, contentLength); TResponse response = null; // Only attempt to set the body if the response may have content - if (MayHaveBody(statusCode, requestData.Method, contentLength)) + if (MayHaveBody(statusCode, endpoint.Method, contentLength)) response = await SetBodyAsync(details, requestData, responseStream, mimeType, cancellationToken).ConfigureAwait(false); @@ -104,50 +108,6 @@ public override async Task ToResponseAsync( return response; } - // A helper which returns true if the response could potentially have a body. - // We check for content-length != 0 rather than > 0 as we may not have a content-length header and the length may be -1. - // In that case, we may have a body and can only use the status code and method conditions to rule out a potential body. - private static bool MayHaveBody(int? statusCode, HttpMethod httpMethod, long contentLength) => - contentLength != 0 && (!statusCode.HasValue || statusCode.Value != 204 && httpMethod != HttpMethod.HEAD); - - private static ApiCallDetails Initialize(RequestData requestData, Exception exception, int? statusCode, Dictionary> headers, string mimeType, IReadOnlyDictionary threadPoolStats, IReadOnlyDictionary tcpStats, long contentLength) - { - var hasSuccessfulStatusCode = false; - var allowedStatusCodes = requestData.AllowedStatusCodes; - if (statusCode.HasValue) - { - if (allowedStatusCodes.Contains(-1) || allowedStatusCodes.Contains(statusCode.Value)) - hasSuccessfulStatusCode = true; - else - hasSuccessfulStatusCode = requestData.ConnectionSettings - .StatusCodeToResponseSuccess(requestData.Method, statusCode.Value); - } - - // We don't validate the content-type (MIME type) for HEAD requests or responses that have no content (204 status code). - // Elastic Cloud responses to HEAD requests strip the content-type header so we want to avoid validation in that case. - var hasExpectedContentType = !MayHaveBody(statusCode, requestData.Method, contentLength) || requestData.ValidateResponseContentType(mimeType); - - var details = new ApiCallDetails - { - HasSuccessfulStatusCode = hasSuccessfulStatusCode, - HasExpectedContentType = hasExpectedContentType, - OriginalException = exception, - HttpStatusCode = statusCode, - RequestBodyInBytes = requestData.PostData?.WrittenBytes, - Uri = requestData.Uri, - HttpMethod = requestData.Method, - TcpStats = tcpStats, - ThreadPoolStats = threadPoolStats, - ResponseMimeType = mimeType, - TransportConfiguration = requestData.ConnectionSettings - }; - - if (headers is not null) - details.ParsedHeaders = new ReadOnlyDictionary>(headers); - - return details; - } /// /// @@ -202,7 +162,7 @@ private async ValueTask SetBodyCoreAsync(bool isAsync, where TResponse : TransportResponse, new() { byte[] bytes = null; - var disableDirectStreaming = requestData.PostData?.DisableDirectStreaming ?? requestData.ConnectionSettings.DisableDirectStreaming; + var disableDirectStreaming = requestData.DisableDirectStreaming; var requiresErrorDeserialization = RequiresErrorDeserialization(details, requestData); var ownsStream = false; diff --git a/src/Elastic.Transport/Components/Pipeline/PipelineException.cs b/src/Elastic.Transport/Components/Pipeline/PipelineException.cs index 3d7044c..ba035a3 100644 --- a/src/Elastic.Transport/Components/Pipeline/PipelineException.cs +++ b/src/Elastic.Transport/Components/Pipeline/PipelineException.cs @@ -17,7 +17,7 @@ public PipelineException(PipelineFailure failure) : base(GetMessage(failure)) => FailureReason = failure; /// - public PipelineException(PipelineFailure failure, Exception innerException) + public PipelineException(PipelineFailure failure, Exception? innerException) : base(GetMessage(failure), innerException) => FailureReason = failure; /// diff --git a/src/Elastic.Transport/Components/Pipeline/PipelineFailure.cs b/src/Elastic.Transport/Components/Pipeline/PipelineFailure.cs index be86684..585114b 100644 --- a/src/Elastic.Transport/Components/Pipeline/PipelineFailure.cs +++ b/src/Elastic.Transport/Components/Pipeline/PipelineFailure.cs @@ -33,12 +33,12 @@ public enum PipelineFailure CouldNotStartSniffOnStartup, /// - /// The overall timeout specified by was reached + /// The overall timeout specified by was reached /// MaxTimeoutReached, /// - /// The overall max retries as specified by was reached + /// The overall max retries as specified by was reached /// MaxRetriesReached, diff --git a/src/Elastic.Transport/Components/Pipeline/RequestData.cs b/src/Elastic.Transport/Components/Pipeline/RequestData.cs index cb9b0b0..778ab7b 100644 --- a/src/Elastic.Transport/Components/Pipeline/RequestData.cs +++ b/src/Elastic.Transport/Components/Pipeline/RequestData.cs @@ -12,6 +12,66 @@ namespace Elastic.Transport; +/// +/// Represents the path of an endpoint in a transport request, including the HTTP method +/// and the path and query information. +/// +/// +/// This struct is used to store information about the HTTP method and the path and query of an endpoint, +/// which are essential components when constructing a request URI. +/// +public readonly record struct EndpointPath(HttpMethod Method, string PathAndQuery); + + +/// +/// Represents an endpoint in a transport request, encapsulating the HTTP method, path and query, +/// and the node to which the request is being sent. +/// +/// +/// This class is used to construct the URI for the request based on the node's URI and the path and query. +/// An empty endpoint can be created using the method as a default or placeholder instance. +/// +public record Endpoint(in EndpointPath Path, Node Node) +{ + /// Represents an empty endpoint used as a default or placeholder instance of . + public static Endpoint Empty(in EndpointPath path) => new(path, EmptyNode); + + private static readonly Node EmptyNode = new(new Uri("http://empty.example")); + + /// Indicates whether the endpoint is an empty placeholder instance. + public bool IsEmpty => Node == EmptyNode; + + /// The for the request. + public Uri Uri { get; private init; } = new(Node.Uri, Path.PathAndQuery); + + /// The HTTP method used for the request (e.g., GET, POST, PUT, DELETE, HEAD). + public HttpMethod Method => Path.Method; + + /// Gets the path and query of the endpoint. + public string PathAndQuery => Path.PathAndQuery; + + private readonly Node _node = Node; + + /// + /// Represents a node within the transport layer of the Elastic search client. + /// This object encapsulates the characteristics of a node, allowing for comparisons and operations + /// within the broader search infrastructure. + /// + public Node Node + { + get => _node; + init + { + _node = value; + Uri = new(Node.Uri, Path.PathAndQuery); + } + } + + /// + public override string ToString() => $"{Path.Method.GetStringValue()} {Uri}"; + +} + /// /// Where and how should connect to. /// @@ -24,40 +84,56 @@ public sealed class RequestData //TODO add xmldocs and clean up this class #pragma warning disable 1591 public const string DefaultMimeType = "application/json"; - public const string MimeTypeTextPlain = "text/plain"; public const string OpaqueIdHeader = "X-Opaque-Id"; public const string RunAsSecurityHeader = "es-security-runas-user"; - private Uri? _requestUri; - private Node? _node; - public RequestData( - HttpMethod method, - string pathAndQuery, - PostData? data, ITransportConfiguration global, IRequestConfiguration? local, CustomResponseBuilder? customResponseBuilder, - MemoryStreamFactory memoryStreamFactory, - OpenTelemetryData openTelemetryData + MemoryStreamFactory memoryStreamFactory ) { - OpenTelemetryData = openTelemetryData; CustomResponseBuilder = customResponseBuilder; ConnectionSettings = global; MemoryStreamFactory = memoryStreamFactory; - Method = method; - PostData = data; - PathAndQuery = pathAndQuery; + SkipDeserializationForStatusCodes = global.SkipDeserializationForStatusCodes; + DnsRefreshTimeout = global.DnsRefreshTimeout; + MetaHeaderProvider = global.MetaHeaderProvider; + ProxyAddress = global.ProxyAddress; + ProxyUsername = global.ProxyUsername; + ProxyPassword = global.ProxyPassword; + DisableAutomaticProxyDetection = global.DisableAutomaticProxyDetection; + UserAgent = global.UserAgent; + KeepAliveInterval = (int)(global.KeepAliveInterval?.TotalMilliseconds ?? 2000); + KeepAliveTime = (int)(global.KeepAliveTime?.TotalMilliseconds ?? 2000); + + RunAs = local?.RunAs ?? global.RunAs; - if (data != null) - data.DisableDirectStreaming = local?.DisableDirectStreaming ?? global.DisableDirectStreaming; + DisableDirectStreaming = local?.DisableDirectStreaming ?? global.DisableDirectStreaming ?? false; - Pipelined = local?.EnableHttpPipelining ?? global.HttpPipeliningEnabled; - HttpCompression = global.EnableHttpCompression; - ContentType = local?.ContentType ?? global.ProductRegistration.DefaultMimeType ?? DefaultMimeType; - Accept = local?.Accept ?? global.ProductRegistration.DefaultMimeType ?? DefaultMimeType; + Pipelined = local?.HttpPipeliningEnabled ?? global.HttpPipeliningEnabled ?? true; + HttpCompression = global.EnableHttpCompression ?? local?.EnableHttpCompression ?? true; + ContentType = local?.ContentType ?? global.Accept ?? DefaultMimeType; + Accept = local?.Accept ?? global.Accept ?? DefaultMimeType; + ThrowExceptions = local?.ThrowExceptions ?? global.ThrowExceptions ?? false; + RequestTimeout = local?.RequestTimeout ?? global.RequestTimeout ?? RequestConfiguration.DefaultRequestTimeout; + RequestMetaData = local?.RequestMetaData?.Items ?? EmptyReadOnly.Dictionary; + AuthenticationHeader = local?.Authentication ?? global.Authentication; + AllowedStatusCodes = local?.AllowedStatusCodes ?? EmptyReadOnly.Collection; + ClientCertificates = local?.ClientCertificates ?? global.ClientCertificates; + TransferEncodingChunked = local?.TransferEncodingChunked ?? global.TransferEncodingChunked ?? false; + TcpStats = local?.EnableTcpStats ?? global.EnableTcpStats ?? true; + ThreadPoolStats = local?.EnableThreadPoolStats ?? global.EnableThreadPoolStats ?? true; + ParseAllHeaders = local?.ParseAllHeaders ?? global.ParseAllHeaders ?? false; + ResponseHeadersToParse = local is not null + ? new HeadersList(local.ResponseHeadersToParse, global.ResponseHeadersToParse) + : global.ResponseHeadersToParse; + PingTimeout = + local?.PingTimeout + ?? global.PingTimeout + ?? (global.NodePool.UsingSsl ? RequestConfiguration.DefaultPingTimeoutOnSsl : RequestConfiguration.DefaultPingTimeout); if (global.Headers != null) Headers = new NameValueCollection(global.Headers); @@ -75,79 +151,26 @@ OpenTelemetryData openTelemetryData Headers.Add(OpaqueIdHeader, local.OpaqueId); } - RunAs = local?.RunAs; - SkipDeserializationForStatusCodes = global.SkipDeserializationForStatusCodes; - ThrowExceptions = local?.ThrowExceptions ?? global.ThrowExceptions; - - RequestTimeout = local?.RequestTimeout ?? global.RequestTimeout; - PingTimeout = - local?.PingTimeout - ?? global.PingTimeout - ?? (global.NodePool.UsingSsl ? TransportConfiguration.DefaultPingTimeoutOnSsl : TransportConfiguration.DefaultPingTimeout); - - KeepAliveInterval = (int)(global.KeepAliveInterval?.TotalMilliseconds ?? 2000); - KeepAliveTime = (int)(global.KeepAliveTime?.TotalMilliseconds ?? 2000); - DnsRefreshTimeout = global.DnsRefreshTimeout; - - MetaHeaderProvider = global.MetaHeaderProvider; - RequestMetaData = local?.RequestMetaData?.Items ?? EmptyReadOnly.Dictionary; - - ProxyAddress = global.ProxyAddress; - ProxyUsername = global.ProxyUsername; - ProxyPassword = global.ProxyPassword; - DisableAutomaticProxyDetection = global.DisableAutomaticProxyDetection; - AuthenticationHeader = local?.AuthenticationHeader ?? global.Authentication; - AllowedStatusCodes = local?.AllowedStatusCodes ?? EmptyReadOnly.Collection; - ClientCertificates = local?.ClientCertificates ?? global.ClientCertificates; - UserAgent = global.UserAgent; - TransferEncodingChunked = local?.TransferEncodingChunked ?? global.TransferEncodingChunked; - TcpStats = local?.EnableTcpStats ?? global.EnableTcpStats; - ThreadPoolStats = local?.EnableThreadPoolStats ?? global.EnableThreadPoolStats; - ParseAllHeaders = local?.ParseAllHeaders ?? global.ParseAllHeaders ?? false; - - if (local is not null) - { - ResponseHeadersToParse = local.ResponseHeadersToParse; - ResponseHeadersToParse = new HeadersList(local.ResponseHeadersToParse, global.ResponseHeadersToParse); - } - else - ResponseHeadersToParse = global.ResponseHeadersToParse; } public string Accept { get; } public IReadOnlyCollection AllowedStatusCodes { get; } - public AuthorizationHeader AuthenticationHeader { get; } - public X509CertificateCollection ClientCertificates { get; } + public AuthorizationHeader? AuthenticationHeader { get; } + public X509CertificateCollection? ClientCertificates { get; } public ITransportConfiguration ConnectionSettings { get; } public CustomResponseBuilder? CustomResponseBuilder { get; } - public bool DisableAutomaticProxyDetection { get; } - public HeadersList ResponseHeadersToParse { get; } + public HeadersList? ResponseHeadersToParse { get; } + public NameValueCollection? Headers { get; } + public bool DisableDirectStreaming { get; } public bool ParseAllHeaders { get; } - public NameValueCollection Headers { get; } + public bool DisableAutomaticProxyDetection { get; } public bool HttpCompression { get; } public int KeepAliveInterval { get; } public int KeepAliveTime { get; } - public bool MadeItToResponse { get; set; } public MemoryStreamFactory MemoryStreamFactory { get; } - public HttpMethod Method { get; } - - public Node Node - { - get => _node; - set - { - // We want the Uri to regenerate when the node changes - _requestUri = null; - _node = value; - } - } - public AuditEvent OnFailureAuditEvent => MadeItToResponse ? AuditEvent.BadResponse : AuditEvent.BadRequest; - public PipelineFailure OnFailurePipelineFailure => MadeItToResponse ? PipelineFailure.BadResponse : PipelineFailure.BadRequest; - public string PathAndQuery { get; } public TimeSpan PingTimeout { get; } public bool Pipelined { get; } - public PostData? PostData { get; } public string ProxyAddress { get; } public string ProxyPassword { get; } public string ProxyUsername { get; } @@ -161,31 +184,13 @@ public Node Node public bool TcpStats { get; } public bool ThreadPoolStats { get; } - /// - /// The for the request. - /// - public Uri Uri - { - get - { - if (_requestUri is not null) return _requestUri; - - _requestUri = Node is not null ? new Uri(Node.Uri, PathAndQuery) : null; - return _requestUri; - } - } - public TimeSpan DnsRefreshTimeout { get; } - public MetaHeaderProvider MetaHeaderProvider { get; } + public MetaHeaderProvider? MetaHeaderProvider { get; } public IReadOnlyDictionary RequestMetaData { get; } - public bool IsAsync { get; internal set; } - - internal OpenTelemetryData OpenTelemetryData { get; } - - public override string ToString() => $"{Method.GetStringValue()} {PathAndQuery}"; + //internal OpenTelemetryData OpenTelemetryData { get; } internal bool ValidateResponseContentType(string responseMimeType) { diff --git a/src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs b/src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs index 4a36a03..79dc658 100644 --- a/src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs +++ b/src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs @@ -45,10 +45,10 @@ internal RequestPipeline() { } public abstract DateTimeOffset StartedOn { get; } - public abstract TResponse CallProductEndpoint(RequestData requestData) + public abstract TResponse CallProductEndpoint(Endpoint endpoint, RequestData requestData, PostData? postData) where TResponse : TransportResponse, new(); - public abstract Task CallProductEndpointAsync(RequestData requestData, CancellationToken cancellationToken) + public abstract Task CallProductEndpointAsync(Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken) where TResponse : TransportResponse, new(); public abstract void MarkAlive(Node node); @@ -87,15 +87,15 @@ public abstract Task CallProductEndpointAsync(RequestData public abstract Task SniffOnConnectionFailureAsync(CancellationToken cancellationToken); - public abstract void BadResponse(ref TResponse response, ApiCallDetails callDetails, RequestData data, TransportException exception) + public abstract void BadResponse(ref TResponse response, ApiCallDetails callDetails, Endpoint endpoint, RequestData data, PostData? postData, TransportException exception) where TResponse : TransportResponse, new(); - public abstract void ThrowNoNodesAttempted(RequestData requestData, List? seenExceptions); + public abstract void ThrowNoNodesAttempted(Endpoint endpoint, List? seenExceptions); public abstract void AuditCancellationRequested(); public abstract TransportException? CreateClientException(TResponse? response, ApiCallDetails? callDetails, - RequestData data, List? seenExceptions) + Endpoint endpoint, RequestData data, List? seenExceptions) where TResponse : TransportResponse, new(); #pragma warning restore 1591 diff --git a/src/Elastic.Transport/Components/Pipeline/ResponseBuilder.cs b/src/Elastic.Transport/Components/Pipeline/ResponseBuilder.cs index f4952a9..95f18c3 100644 --- a/src/Elastic.Transport/Components/Pipeline/ResponseBuilder.cs +++ b/src/Elastic.Transport/Components/Pipeline/ResponseBuilder.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Net.NetworkInformation; using System.Threading; using System.Threading.Tasks; @@ -24,15 +26,17 @@ public abstract class ResponseBuilder /// Create an instance of from /// public abstract TResponse ToResponse( + Endpoint endpoint, RequestData requestData, - Exception ex, + PostData? postData, + Exception? ex, int? statusCode, - Dictionary> headers, + Dictionary>? headers, Stream responseStream, - string mimeType, + string? mimeType, long contentLength, - IReadOnlyDictionary threadPoolStats, - IReadOnlyDictionary tcpStats + IReadOnlyDictionary? threadPoolStats, + IReadOnlyDictionary? tcpStats ) where TResponse : TransportResponse, new(); @@ -40,15 +44,74 @@ IReadOnlyDictionary tcpStats /// Create an instance of from /// public abstract Task ToResponseAsync( + Endpoint endpoint, RequestData requestData, - Exception ex, + PostData? postData, + Exception? ex, int? statusCode, - Dictionary> headers, + Dictionary>? headers, Stream responseStream, - string mimeType, + string? mimeType, long contentLength, - IReadOnlyDictionary threadPoolStats, - IReadOnlyDictionary tcpStats, + IReadOnlyDictionary? threadPoolStats, + IReadOnlyDictionary? tcpStats, CancellationToken cancellationToken = default ) where TResponse : TransportResponse, new(); + + internal static ApiCallDetails Initialize( + Endpoint endpoint, + RequestData requestData, + PostData? postData, + Exception exception, + int? statusCode, + Dictionary> headers, string mimeType, + IReadOnlyDictionary threadPoolStats, IReadOnlyDictionary tcpStats, + long contentLength + ) + { + var hasSuccessfulStatusCode = false; + var allowedStatusCodes = requestData.AllowedStatusCodes; + if (statusCode.HasValue) + { + if (allowedStatusCodes.Contains(-1) || allowedStatusCodes.Contains(statusCode.Value)) + hasSuccessfulStatusCode = true; + else + hasSuccessfulStatusCode = requestData.ConnectionSettings + .StatusCodeToResponseSuccess(endpoint.Method, statusCode.Value); + } + + // We don't validate the content-type (MIME type) for HEAD requests or responses that have no content (204 status code). + // Elastic Cloud responses to HEAD requests strip the content-type header so we want to avoid validation in that case. + var hasExpectedContentType = !MayHaveBody(statusCode, endpoint.Method, contentLength) || requestData.ValidateResponseContentType(mimeType); + + var details = new ApiCallDetails + { + HasSuccessfulStatusCode = hasSuccessfulStatusCode, + HasExpectedContentType = hasExpectedContentType, + OriginalException = exception, + HttpStatusCode = statusCode, + RequestBodyInBytes = postData?.WrittenBytes, + Uri = endpoint.Uri, + HttpMethod = endpoint.Method, + TcpStats = tcpStats, + ThreadPoolStats = threadPoolStats, + ResponseMimeType = mimeType, + TransportConfiguration = requestData.ConnectionSettings + }; + + if (headers is not null) + details.ParsedHeaders = new ReadOnlyDictionary>(headers); + + return details; + } + + /// + /// A helper which returns true if the response could potentially have a body. + /// We check for content-length != 0 rather than > 0 as we may not have a content-length header and the length may be -1. + /// In that case, we may have a body and can only use the status code and method conditions to rule out a potential body. + /// + protected static bool MayHaveBody(int? statusCode, HttpMethod httpMethod, long contentLength) => + contentLength != 0 && (!statusCode.HasValue || statusCode.Value != 204 && httpMethod != HttpMethod.HEAD); + } diff --git a/src/Elastic.Transport/Components/TransportClient/Content/RequestDataContent.cs b/src/Elastic.Transport/Components/TransportClient/Content/RequestDataContent.cs index a73a09e..2b9dd3a 100644 --- a/src/Elastic.Transport/Components/TransportClient/Content/RequestDataContent.cs +++ b/src/Elastic.Transport/Components/TransportClient/Content/RequestDataContent.cs @@ -29,17 +29,19 @@ namespace Elastic.Transport; internal sealed class RequestDataContent : HttpContent { private readonly RequestData _requestData; + private readonly PostData? _postData; - private readonly Func + private readonly Func _onStreamAvailableAsync; - private readonly Action _onStreamAvailable; + private readonly Action _onStreamAvailable; private readonly CancellationToken _token; /// Constructor used in synchronous paths. - public RequestDataContent(RequestData requestData) + public RequestDataContent(RequestData requestData, PostData postData) { _requestData = requestData; + _postData = postData; _token = default; Headers.TryAddWithoutValidation("Content-Type", requestData.ContentType); @@ -51,11 +53,16 @@ public RequestDataContent(RequestData requestData) _onStreamAvailableAsync = OnStreamAvailableAsync; } - private static void OnStreamAvailable(RequestData data, Stream stream, HttpContent content, TransportContext context) + private static void OnStreamAvailable(RequestData data, PostData? postData, Stream stream, HttpContent content, TransportContext context) { + if (postData == null) + { + stream.Dispose(); + return; + } if (data.HttpCompression) stream = new GZipStream(stream, CompressionMode.Compress, false); - using (stream) data.PostData.Write(stream, data.ConnectionSettings); + using (stream) postData.Write(stream, data.ConnectionSettings, data.DisableDirectStreaming); } /// Constructor used in asynchronous paths. @@ -73,8 +80,17 @@ public RequestDataContent(RequestData requestData, CancellationToken token) _onStreamAvailableAsync = OnStreamAvailableAsync; } - private static async Task OnStreamAvailableAsync(RequestData data, Stream stream, HttpContent content, TransportContext context, CancellationToken ctx = default) + private static async Task OnStreamAvailableAsync(RequestData data, PostData? postData, Stream stream, HttpContent content, TransportContext context, CancellationToken ctx = default) { + if (postData == null) + { +#if NET6_0_OR_GREATER + await stream.DisposeAsync().ConfigureAwait(false); +#else + stream.Dispose(); +#endif + return; + } if (data.HttpCompression) stream = new GZipStream(stream, CompressionMode.Compress, false); #if NET6_0_OR_GREATER @@ -82,7 +98,7 @@ private static async Task OnStreamAvailableAsync(RequestData data, Stream stream #else using (stream) #endif - await data.PostData.WriteAsync(stream, data.ConnectionSettings, ctx).ConfigureAwait(false); + await postData.WriteAsync(stream, data.ConnectionSettings, data.DisableDirectStreaming, ctx).ConfigureAwait(false); } /// @@ -108,7 +124,7 @@ async Task SerializeToStreamAsync(Stream stream, TransportContext context, Cance var source = CancellationTokenSource.CreateLinkedTokenSource(_token, cancellationToken); var serializeToStreamTask = new TaskCompletionSource(); var wrappedStream = new CompleteTaskOnCloseStream(stream, serializeToStreamTask); - await _onStreamAvailableAsync(_requestData, wrappedStream, this, context, source.Token).ConfigureAwait(false); + await _onStreamAvailableAsync(_requestData, _postData, wrappedStream, this, context, source.Token).ConfigureAwait(false); await serializeToStreamTask.Task.ConfigureAwait(false); } @@ -117,7 +133,7 @@ protected override void SerializeToStream(Stream stream, TransportContext contex { var serializeToStreamTask = new TaskCompletionSource(); using var wrappedStream = new CompleteTaskOnCloseStream(stream, serializeToStreamTask); - _onStreamAvailable(_requestData, wrappedStream, this, context); + _onStreamAvailable(_requestData, _postData, wrappedStream, this, context); //await serializeToStreamTask.Task.ConfigureAwait(false); } #endif diff --git a/src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker.cs b/src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker.cs index 4fd3335..00031d8 100644 --- a/src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker.cs +++ b/src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker.cs @@ -56,16 +56,16 @@ public HttpRequestInvoker(Func - public TResponse Request(RequestData requestData) + public TResponse Request(Endpoint endpoint, RequestData requestData, PostData? postData) where TResponse : TransportResponse, new() => - RequestCoreAsync(false, requestData).EnsureCompleted(); + RequestCoreAsync(false, endpoint, requestData, postData).EnsureCompleted(); /// - public Task RequestAsync(RequestData requestData, CancellationToken cancellationToken) + public Task RequestAsync(Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken) where TResponse : TransportResponse, new() => - RequestCoreAsync(true, requestData, cancellationToken).AsTask(); + RequestCoreAsync(true, endpoint, requestData, postData, cancellationToken).AsTask(); - private async ValueTask RequestCoreAsync(bool isAsync, RequestData requestData, CancellationToken cancellationToken = default) + private async ValueTask RequestCoreAsync(bool isAsync, Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() { var client = GetClient(requestData); @@ -79,20 +79,19 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req ReadOnlyDictionary tcpStats = null; ReadOnlyDictionary threadPoolStats = null; Dictionary> responseHeaders = null; - requestData.IsAsync = isAsync; var beforeTicks = Stopwatch.GetTimestamp(); try { - var requestMessage = CreateHttpRequestMessage(requestData); + var requestMessage = CreateHttpRequestMessage(endpoint, requestData, isAsync); - if (requestData.PostData is not null) + if (postData is not null) { if (isAsync) - await SetContentAsync(requestMessage, requestData, cancellationToken).ConfigureAwait(false); + await SetContentAsync(requestMessage, requestData, postData, cancellationToken).ConfigureAwait(false); else - SetContent(requestMessage, requestData); + SetContent(requestMessage, requestData, postData); } using (requestMessage?.Content ?? (IDisposable)Stream.Null) @@ -122,7 +121,6 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req statusCode = (int)responseMessage.StatusCode; } - requestData.MadeItToResponse = true; mimeType = responseMessage.Content.Headers.ContentType?.ToString(); responseHeaders = ParseHeaders(requestData, responseMessage); @@ -160,11 +158,11 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req { if (isAsync) response = await requestData.ConnectionSettings.ProductRegistration.ResponseBuilder.ToResponseAsync - (requestData, ex, statusCode, responseHeaders, responseStream, mimeType, contentLength, threadPoolStats, tcpStats, cancellationToken) + (endpoint, requestData, postData, ex, statusCode, responseHeaders, responseStream, mimeType, contentLength, threadPoolStats, tcpStats, cancellationToken) .ConfigureAwait(false); else response = requestData.ConnectionSettings.ProductRegistration.ResponseBuilder.ToResponse - (requestData, ex, statusCode, responseHeaders, responseStream, mimeType, contentLength, threadPoolStats, tcpStats); + (endpoint, requestData, postData, ex, statusCode, responseHeaders, responseStream, mimeType, contentLength, threadPoolStats, tcpStats); // Unless indicated otherwise by the TransportResponse, we've now handled the response stream, so we can dispose of the HttpResponseMessage // to release the connection. In cases, where the derived response works directly on the stream, it can be left open and additional IDisposable @@ -195,7 +193,7 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req { // if there's an exception, ensure we always release the stream and response so that the connection is freed. responseStream.Dispose(); - receivedResponse.Dispose(); + receivedResponse.Dispose(); throw; } } @@ -217,7 +215,7 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req responseHeaders ??= new Dictionary>(); responseHeaders.Add(header.Key, header.Value); } - else if (requestData.ResponseHeadersToParse.Count > 0) + else if (requestData.ResponseHeadersToParse is { Count: > 0 }) foreach (var headerToParse in requestData.ResponseHeadersToParse) if (responseMessage.Headers.TryGetValues(headerToParse, out var values)) { @@ -324,35 +322,38 @@ private string ComparableFingerprint(string fingerprint) /// Creates an instance of using the . /// This method is virtual so subclasses of can modify the instance if needed. /// - /// An instance of describing where and how to call out to + /// An object describing where we want to call out to + /// An object describing how we want to call out to + /// /// /// Can throw if is set but the platform does /// not allow this to be set on /// - internal HttpRequestMessage CreateHttpRequestMessage(RequestData requestData) + internal HttpRequestMessage CreateHttpRequestMessage(Endpoint endpoint, RequestData requestData, bool isAsync) { - var request = CreateRequestMessage(requestData); - SetAuthenticationIfNeeded(request, requestData); + var request = CreateRequestMessage(endpoint, requestData, isAsync); + SetAuthenticationIfNeeded(endpoint, requestData, request); return request; } /// Isolated hook for subclasses to set authentication on /// The instance of that needs authentication details - /// An object describing where and how we want to call out to - internal void SetAuthenticationIfNeeded(HttpRequestMessage requestMessage, RequestData requestData) + /// An object describing where we want to call out to + /// An object describing how we want to call out to + internal void SetAuthenticationIfNeeded(Endpoint endpoint, RequestData requestData, HttpRequestMessage requestMessage) { //If user manually specifies an Authorization Header give it preference - if (requestData.Headers.HasKeys() && requestData.Headers.AllKeys.Contains("Authorization")) + if (requestData.Headers != null && requestData.Headers.HasKeys() && requestData.Headers.AllKeys.Contains("Authorization")) { var header = AuthenticationHeaderValue.Parse(requestData.Headers["Authorization"]); requestMessage.Headers.Authorization = header; return; } - SetConfiguredAuthenticationHeaderIfNeeded(requestMessage, requestData); + SetConfiguredAuthenticationHeaderIfNeeded(endpoint, requestData, requestMessage); } - private static void SetConfiguredAuthenticationHeaderIfNeeded(HttpRequestMessage requestMessage, RequestData requestData) + private static void SetConfiguredAuthenticationHeaderIfNeeded(Endpoint endpoint, RequestData requestData, HttpRequestMessage requestMessage) { // Basic auth credentials take the following precedence (highest -> lowest): // 1 - Specified with the URI (highest precedence) @@ -361,9 +362,9 @@ private static void SetConfiguredAuthenticationHeaderIfNeeded(HttpRequestMessage string parameters = null; string scheme = null; - if (!requestData.Uri.UserInfo.IsNullOrEmpty()) + if (!endpoint.Uri.UserInfo.IsNullOrEmpty()) { - parameters = BasicAuthentication.GetBase64String(Uri.UnescapeDataString(requestData.Uri.UserInfo)); + parameters = BasicAuthentication.GetBase64String(Uri.UnescapeDataString(endpoint.Uri.UserInfo)); scheme = BasicAuthentication.BasicAuthenticationScheme; } else if (requestData.AuthenticationHeader != null && requestData.AuthenticationHeader.TryGetAuthorizationParameters(out var v)) @@ -377,10 +378,10 @@ private static void SetConfiguredAuthenticationHeaderIfNeeded(HttpRequestMessage requestMessage.Headers.Authorization = new AuthenticationHeaderValue(scheme, parameters); } - private static HttpRequestMessage CreateRequestMessage(RequestData requestData) + private static HttpRequestMessage CreateRequestMessage(Endpoint endpoint, RequestData requestData, bool isAsync) { - var method = ConvertHttpMethod(requestData.Method); - var requestMessage = new HttpRequestMessage(method, requestData.Uri); + var method = ConvertHttpMethod(endpoint.Method); + var requestMessage = new HttpRequestMessage(method, endpoint.Uri); if (requestData.Headers != null) foreach (string key in requestData.Headers) @@ -404,7 +405,7 @@ private static HttpRequestMessage CreateRequestMessage(RequestData requestData) { foreach (var producer in requestData.MetaHeaderProvider.Producers) { - var value = producer.ProduceHeaderValue(requestData); + var value = producer.ProduceHeaderValue(requestData, isAsync); if (!string.IsNullOrEmpty(value)) requestMessage.Headers.TryAddWithoutValidation(producer.HeaderName, value); @@ -414,25 +415,25 @@ private static HttpRequestMessage CreateRequestMessage(RequestData requestData) return requestMessage; } - private static void SetContent(HttpRequestMessage message, RequestData requestData) + private static void SetContent(HttpRequestMessage message, RequestData requestData, PostData postData) { if (requestData.TransferEncodingChunked) - message.Content = new RequestDataContent(requestData); + message.Content = new RequestDataContent(requestData, postData); else { var stream = requestData.MemoryStreamFactory.Create(); if (requestData.HttpCompression) { using var zipStream = new GZipStream(stream, CompressionMode.Compress, true); - requestData.PostData.Write(zipStream, requestData.ConnectionSettings); + postData.Write(zipStream, requestData.ConnectionSettings, requestData.DisableDirectStreaming); } else - requestData.PostData.Write(stream, requestData.ConnectionSettings); + postData.Write(stream, requestData.ConnectionSettings, requestData.DisableDirectStreaming); // the written bytes are uncompressed, so can only be used when http compression isn't used - if (requestData.PostData.DisableDirectStreaming.GetValueOrDefault(false) && !requestData.HttpCompression) + if (requestData.DisableDirectStreaming && !requestData.HttpCompression) { - message.Content = new ByteArrayContent(requestData.PostData.WrittenBytes); + message.Content = new ByteArrayContent(postData.WrittenBytes); stream.Dispose(); } else @@ -448,7 +449,7 @@ private static void SetContent(HttpRequestMessage message, RequestData requestDa } } - private static async Task SetContentAsync(HttpRequestMessage message, RequestData requestData, CancellationToken cancellationToken) + private static async Task SetContentAsync(HttpRequestMessage message, RequestData requestData, PostData postData, CancellationToken cancellationToken) { if (requestData.TransferEncodingChunked) message.Content = new RequestDataContent(requestData, cancellationToken); @@ -458,15 +459,15 @@ private static async Task SetContentAsync(HttpRequestMessage message, RequestDat if (requestData.HttpCompression) { using var zipStream = new GZipStream(stream, CompressionMode.Compress, true); - await requestData.PostData.WriteAsync(zipStream, requestData.ConnectionSettings, cancellationToken).ConfigureAwait(false); + await postData.WriteAsync(zipStream, requestData.ConnectionSettings, requestData.DisableDirectStreaming, cancellationToken).ConfigureAwait(false); } else - await requestData.PostData.WriteAsync(stream, requestData.ConnectionSettings, cancellationToken).ConfigureAwait(false); + await postData.WriteAsync(stream, requestData.ConnectionSettings, requestData.DisableDirectStreaming, cancellationToken).ConfigureAwait(false); // the written bytes are uncompressed, so can only be used when http compression isn't used - if (requestData.PostData.DisableDirectStreaming.GetValueOrDefault(false) && !requestData.HttpCompression) + if (requestData.DisableDirectStreaming && !requestData.HttpCompression) { - message.Content = new ByteArrayContent(requestData.PostData.WrittenBytes); + message.Content = new ByteArrayContent(postData.WrittenBytes); #if DOTNETCORE_2_1_OR_HIGHER await stream.DisposeAsync().ConfigureAwait(false); #else diff --git a/src/Elastic.Transport/Components/TransportClient/HttpWebRequestInvoker.cs b/src/Elastic.Transport/Components/TransportClient/HttpWebRequestInvoker.cs index 6ced0e1..d64c0cf 100644 --- a/src/Elastic.Transport/Components/TransportClient/HttpWebRequestInvoker.cs +++ b/src/Elastic.Transport/Components/TransportClient/HttpWebRequestInvoker.cs @@ -50,16 +50,16 @@ public HttpWebRequestInvoker() { } void IDisposable.Dispose() {} /// > - public TResponse Request(RequestData requestData) + public TResponse Request(Endpoint endpoint, RequestData requestData, PostData? postData) where TResponse : TransportResponse, new() => - RequestCoreAsync(false, requestData).EnsureCompleted(); + RequestCoreAsync(false, endpoint, requestData, postData).EnsureCompleted(); /// > - public Task RequestAsync(RequestData requestData, CancellationToken cancellationToken = default) + public Task RequestAsync(Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() => - RequestCoreAsync(true, requestData, cancellationToken).AsTask(); + RequestCoreAsync(true, endpoint, requestData, postData, cancellationToken).AsTask(); - private async ValueTask RequestCoreAsync(bool isAsync, RequestData requestData, CancellationToken cancellationToken = default) + private async ValueTask RequestCoreAsync(bool isAsync, Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() { Action unregisterWaitHandle = null; @@ -72,14 +72,13 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req ReadOnlyDictionary tcpStats = null; ReadOnlyDictionary threadPoolStats = null; Dictionary> responseHeaders = null; - requestData.IsAsync = true; var beforeTicks = Stopwatch.GetTimestamp(); try { - var data = requestData.PostData; - var request = CreateHttpWebRequest(requestData); + var data = postData; + var request = CreateHttpWebRequest(endpoint, requestData, postData, isAsync); using (cancellationToken.Register(() => request.Abort())) { if (data is not null) @@ -95,10 +94,10 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req if (requestData.HttpCompression) { using var zipStream = new GZipStream(stream, CompressionMode.Compress); - await data.WriteAsync(zipStream, requestData.ConnectionSettings, cancellationToken).ConfigureAwait(false); + await data.WriteAsync(zipStream, requestData.ConnectionSettings, requestData.DisableDirectStreaming, cancellationToken).ConfigureAwait(false); } else - await data.WriteAsync(stream, requestData.ConnectionSettings, cancellationToken).ConfigureAwait(false); + await data.WriteAsync(stream, requestData.ConnectionSettings, requestData.DisableDirectStreaming, cancellationToken).ConfigureAwait(false); } unregisterWaitHandle?.Invoke(); } @@ -109,10 +108,10 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req if (requestData.HttpCompression) { using var zipStream = new GZipStream(stream, CompressionMode.Compress); - data.Write(zipStream, requestData.ConnectionSettings); + data.Write(zipStream, requestData.ConnectionSettings, requestData.DisableDirectStreaming); } else - data.Write(stream, requestData.ConnectionSettings); + data.Write(stream, requestData.ConnectionSettings, requestData.DisableDirectStreaming); } } @@ -121,8 +120,6 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req if (prepareRequestMs > OpenTelemetry.MinimumMillisecondsToEmitTimingSpanAttribute && OpenTelemetry.CurrentSpanIsElasticTransportOwnedHasListenersAndAllDataRequested) Activity.Current?.SetTag(OpenTelemetryAttributes.ElasticTransportPrepareRequestMs, prepareRequestMs); - requestData.MadeItToResponse = true; - //http://msdn.microsoft.com/en-us/library/system.net.httpwebresponse.getresponsestream.aspx //Either the stream or the response object needs to be closed but not both although it won't //throw any errors if both are closed atleast one of them has to be Closed. @@ -169,13 +166,13 @@ private async ValueTask RequestCoreAsync(bool isAsync, Req { TResponse response; - if (isAsync) - response = await requestData.ConnectionSettings.ProductRegistration.ResponseBuilder.ToResponseAsync - (requestData, ex, statusCode, responseHeaders, responseStream, mimeType, contentLength, threadPoolStats, tcpStats, cancellationToken) - .ConfigureAwait(false); - else - response = requestData.ConnectionSettings.ProductRegistration.ResponseBuilder.ToResponse - (requestData, ex, statusCode, responseHeaders, responseStream, mimeType, contentLength, threadPoolStats, tcpStats); + if (isAsync) + response = await requestData.ConnectionSettings.ProductRegistration.ResponseBuilder.ToResponseAsync + (endpoint, requestData, postData, ex, statusCode, responseHeaders, responseStream, mimeType, contentLength, threadPoolStats, tcpStats, cancellationToken) + .ConfigureAwait(false); + else + response = requestData.ConnectionSettings.ProductRegistration.ResponseBuilder.ToResponse + (endpoint, requestData, postData, ex, statusCode, responseHeaders, responseStream, mimeType, contentLength, threadPoolStats, tcpStats); // Unless indicated otherwise by the TransportResponse, we've now handled the response stream, so we can dispose of the HttpResponseMessage // to release the connection. In cases, where the derived response works directly on the stream, it can be left open and additional IDisposable @@ -232,7 +229,7 @@ private static Dictionary> ParseHeaders(RequestData responseHeaders.Add(key, responseMessage.Headers.GetValues(key)); } } - else if (requestData.ResponseHeadersToParse.Count > 0) + else if (requestData.ResponseHeadersToParse is { Count: > 0 }) { foreach (var headerToParse in requestData.ResponseHeadersToParse) { @@ -250,11 +247,14 @@ private static Dictionary> ParseHeaders(RequestData /// /// Allows subclasses to modify the instance that is going to be used for the API call /// - /// An instance of describing where and how to call out to - protected virtual HttpWebRequest CreateHttpWebRequest(RequestData requestData) + /// An instance of describing where to call out to + /// An instance of describing how to call out to + /// Optional data to send over the wire + /// + protected virtual HttpWebRequest CreateHttpWebRequest(Endpoint endpoint, RequestData requestData, PostData? postData, bool isAsync) { - var request = CreateWebRequest(requestData); - SetAuthenticationIfNeeded(requestData, request); + var request = CreateWebRequest(endpoint, requestData, postData, isAsync); + SetAuthenticationIfNeeded(endpoint, requestData, request); SetProxyIfNeeded(request, requestData); SetServerCertificateValidationCallBackIfNeeded(request, requestData); SetClientCertificates(request, requestData); @@ -322,9 +322,9 @@ protected virtual void SetServerCertificateValidationCallBackIfNeeded(HttpWebReq #endif } - private static HttpWebRequest CreateWebRequest(RequestData requestData) + private static HttpWebRequest CreateWebRequest(Endpoint endpoint, RequestData requestData, PostData? postData, bool isAsync) { - var request = (HttpWebRequest)WebRequest.Create(requestData.Uri); + var request = (HttpWebRequest)WebRequest.Create(endpoint.Uri); request.Accept = requestData.Accept; request.ContentType = requestData.ContentType; @@ -358,7 +358,7 @@ private static HttpWebRequest CreateWebRequest(RequestData requestData) { foreach (var producer in requestData.MetaHeaderProvider.Producers) { - var value = producer.ProduceHeaderValue(requestData); + var value = producer.ProduceHeaderValue(requestData, isAsync); if (!string.IsNullOrEmpty(value)) request.Headers.Add(producer.HeaderName, value); @@ -372,9 +372,9 @@ private static HttpWebRequest CreateWebRequest(RequestData requestData) //WebRequest won't send Content-Length: 0 for empty bodies //which goes against RFC's and might break i.e IIS when used as a proxy. //see: https://github.com/elastic/elasticsearch-net/issues/562 - var m = requestData.Method.GetStringValue(); + var m = endpoint.Method.GetStringValue(); request.Method = m; - if (m != "HEAD" && m != "GET" && requestData.PostData == null) + if (m != "HEAD" && m != "GET" && postData == null) request.ContentLength = 0; return request; @@ -411,7 +411,7 @@ protected virtual void SetProxyIfNeeded(HttpWebRequest request, RequestData requ } /// Hook for subclasses to set authentication on - protected virtual void SetAuthenticationIfNeeded(RequestData requestData, HttpWebRequest request) + protected virtual void SetAuthenticationIfNeeded(Endpoint endpoint, RequestData requestData, HttpWebRequest request) { //If user manually specifies an Authorization Header give it preference if (requestData.Headers.HasKeys() && requestData.Headers.AllKeys.Contains("Authorization")) @@ -420,10 +420,10 @@ protected virtual void SetAuthenticationIfNeeded(RequestData requestData, HttpWe request.Headers["Authorization"] = header; return; } - SetBasicAuthenticationIfNeeded(request, requestData); + SetBasicAuthenticationIfNeeded(endpoint, requestData, request); } - private static void SetBasicAuthenticationIfNeeded(HttpWebRequest request, RequestData requestData) + private static void SetBasicAuthenticationIfNeeded(Endpoint endpoint, RequestData requestData, HttpWebRequest request) { // Basic auth credentials take the following precedence (highest -> lowest): // 1 - Specified on the request (highest precedence) @@ -438,9 +438,9 @@ private static void SetBasicAuthenticationIfNeeded(HttpWebRequest request, Reque string parameters = null; string scheme = null; - if (!requestData.Uri.UserInfo.IsNullOrEmpty()) + if (!endpoint.Uri.UserInfo.IsNullOrEmpty()) { - parameters = BasicAuthentication.GetBase64String(Uri.UnescapeDataString(requestData.Uri.UserInfo)); + parameters = BasicAuthentication.GetBase64String(Uri.UnescapeDataString(endpoint.Uri.UserInfo)); scheme = BasicAuthentication.BasicAuthenticationScheme; } else if (requestData.AuthenticationHeader != null && requestData.AuthenticationHeader.TryGetAuthorizationParameters(out var v)) diff --git a/src/Elastic.Transport/Components/TransportClient/IRequestInvoker.cs b/src/Elastic.Transport/Components/TransportClient/IRequestInvoker.cs index 2dcf160..aeced28 100644 --- a/src/Elastic.Transport/Components/TransportClient/IRequestInvoker.cs +++ b/src/Elastic.Transport/Components/TransportClient/IRequestInvoker.cs @@ -19,7 +19,9 @@ public interface IRequestInvoker : IDisposable /// /// Perform a request to the endpoint described by using its associated configuration. /// - /// An object describing where and how to perform the IO call + /// An object describing where to perform the IO call + /// An object describing how to perform the IO call + /// Optional data to post /// /// /// An implementation of ensuring enough information is available @@ -31,13 +33,15 @@ public interface IRequestInvoker : IDisposable /// for and to determine what to /// do with the response /// - public Task RequestAsync(RequestData requestData, CancellationToken cancellationToken) + public Task RequestAsync(Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken) where TResponse : TransportResponse, new(); /// /// Perform a request to the endpoint described by using its associated configuration. /// - /// An object describing where and how to perform the IO call + /// An object describing where to perform the IO call + /// An object describing how to perform the IO call + /// Optional data to post /// /// An implementation of ensuring enough information is available /// for and to determine what to @@ -48,7 +52,7 @@ public Task RequestAsync(RequestData requestData, Cancella /// for and to determine what to /// do with the response /// - public TResponse Request(RequestData requestData) + public TResponse Request(Endpoint endpoint, RequestData requestData, PostData? postData) where TResponse : TransportResponse, new(); } diff --git a/src/Elastic.Transport/Components/TransportClient/InMemoryRequestInvoker.cs b/src/Elastic.Transport/Components/TransportClient/InMemoryRequestInvoker.cs index aead20a..3154537 100644 --- a/src/Elastic.Transport/Components/TransportClient/InMemoryRequestInvoker.cs +++ b/src/Elastic.Transport/Components/TransportClient/InMemoryRequestInvoker.cs @@ -43,29 +43,31 @@ public InMemoryRequestInvoker(byte[] responseBody, int statusCode = 200, Excepti void IDisposable.Dispose() { } /// > - public TResponse Request(RequestData requestData) + public TResponse Request(Endpoint endpoint, RequestData requestData, PostData? postData) where TResponse : TransportResponse, new() => - BuildResponse(requestData); + BuildResponse(endpoint, requestData, postData); /// > - public Task RequestAsync(RequestData requestData, CancellationToken cancellationToken) + public Task RequestAsync(Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken) where TResponse : TransportResponse, new() => - BuildResponseAsync(requestData, cancellationToken); + BuildResponseAsync(endpoint, requestData, postData, cancellationToken); /// /// Allow subclasses to provide their own implementations for while reusing the more complex logic /// to create a response /// - /// An instance of describing where and how to call out to + /// An instance of describing where to call out to + /// An instance of describing how to call out to + /// Optional data to post /// The bytes intended to be used as return /// The status code that the responses should return /// - public TResponse BuildResponse(RequestData requestData, byte[] responseBody = null, int? statusCode = null, - string contentType = null) + public TResponse BuildResponse(Endpoint endpoint, RequestData requestData, PostData? postData, byte[]? responseBody = null, int? statusCode = null, + string? contentType = null) where TResponse : TransportResponse, new() { var body = responseBody ?? _responseBody; - var data = requestData.PostData; + var data = postData; if (data is not null) { @@ -73,29 +75,28 @@ public TResponse BuildResponse(RequestData requestData, byte[] respon if (requestData.HttpCompression) { using var zipStream = new GZipStream(stream, CompressionMode.Compress); - data.Write(zipStream, requestData.ConnectionSettings); + data.Write(zipStream, requestData.ConnectionSettings, requestData.DisableDirectStreaming); } else { - data.Write(stream, requestData.ConnectionSettings); + data.Write(stream, requestData.ConnectionSettings, requestData.DisableDirectStreaming); } } - requestData.MadeItToResponse = true; var sc = statusCode ?? _statusCode; Stream responseStream = body != null ? requestData.MemoryStreamFactory.Create(body) : requestData.MemoryStreamFactory.Create(EmptyBody); return requestData.ConnectionSettings.ProductRegistration.ResponseBuilder - .ToResponse(requestData, _exception, sc, _headers, responseStream, contentType ?? _contentType ?? RequestData.DefaultMimeType, body?.Length ?? 0, null, null); + .ToResponse(endpoint, requestData, postData, _exception, sc, _headers, responseStream, contentType ?? _contentType ?? RequestData.DefaultMimeType, body?.Length ?? 0, null, null); } /// > - public async Task BuildResponseAsync(RequestData requestData, CancellationToken cancellationToken, - byte[] responseBody = null, int? statusCode = null, string contentType = null) + public async Task BuildResponseAsync(Endpoint endpoint, RequestData requestData, PostData? postData, CancellationToken cancellationToken, + byte[]? responseBody = null, int? statusCode = null, string? contentType = null) where TResponse : TransportResponse, new() { var body = responseBody ?? _responseBody; - var data = requestData.PostData; + var data = postData; if (data is not null) { @@ -104,21 +105,19 @@ public async Task BuildResponseAsync(RequestData requestDa if (requestData.HttpCompression) { using var zipStream = new GZipStream(stream, CompressionMode.Compress); - await data.WriteAsync(zipStream, requestData.ConnectionSettings, cancellationToken).ConfigureAwait(false); + await data.WriteAsync(zipStream, requestData.ConnectionSettings, requestData.DisableDirectStreaming, cancellationToken).ConfigureAwait(false); } else { - await data.WriteAsync(stream, requestData.ConnectionSettings, cancellationToken).ConfigureAwait(false); + await data.WriteAsync(stream, requestData.ConnectionSettings, requestData.DisableDirectStreaming, cancellationToken).ConfigureAwait(false); } } - requestData.MadeItToResponse = true; - var sc = statusCode ?? _statusCode; Stream responseStream = body != null ? requestData.MemoryStreamFactory.Create(body) : requestData.MemoryStreamFactory.Create(EmptyBody); return await requestData.ConnectionSettings.ProductRegistration.ResponseBuilder - .ToResponseAsync(requestData, _exception, sc, _headers, responseStream, contentType ?? _contentType, body?.Length ?? 0, null, null, cancellationToken) + .ToResponseAsync(endpoint, requestData, postData, _exception, sc, _headers, responseStream, contentType ?? _contentType, body?.Length ?? 0, null, null, cancellationToken) .ConfigureAwait(false); } } diff --git a/src/Elastic.Transport/Configuration/HeadersList.cs b/src/Elastic.Transport/Configuration/HeadersList.cs index 3f56bb3..a08a889 100644 --- a/src/Elastic.Transport/Configuration/HeadersList.cs +++ b/src/Elastic.Transport/Configuration/HeadersList.cs @@ -14,7 +14,7 @@ namespace Elastic.Transport; /// public readonly struct HeadersList : IEnumerable { - private readonly List _headers; + private readonly List _headers = []; /// /// Create a new from an existing enumerable of header names. @@ -23,63 +23,51 @@ namespace Elastic.Transport; /// The header names to initialise the with. public HeadersList(IEnumerable headers) { - _headers = new List(); - foreach (var header in headers) { if (!_headers.Contains(header, StringComparer.OrdinalIgnoreCase)) - { _headers.Add(header); - } } } - /// - /// - /// - /// - /// + /// Represents a unique, case-insensitive, immutable collection of header names. public HeadersList(IEnumerable headers, string additionalHeader) { - _headers = new List(); - foreach (var header in headers) { if (!_headers.Contains(header, StringComparer.OrdinalIgnoreCase)) - { _headers.Add(header); - } } - if (!_headers.Contains(additionalHeader, StringComparer.OrdinalIgnoreCase)) - { - _headers.Add(additionalHeader); - } + if (!_headers.Contains(additionalHeader, StringComparer.OrdinalIgnoreCase)) _headers.Add(additionalHeader); + } + + /// Represents a unique, case-insensitive, immutable collection of header names. + public HeadersList(IEnumerable headers, IEnumerable otherHeaders) + : this(new HeadersList(headers), new HeadersList(otherHeaders)) + { } /// - /// + /// Initializes a new instance of by combining two existing instances. + /// Duplicate names, including those which only differ by case, will be ignored. /// - /// - /// - public HeadersList(IEnumerable headers, IEnumerable otherHeaders) + /// The first set of header names to initialize the with. + /// The second set of header names to initialize the with. + public HeadersList(HeadersList? headers, HeadersList? otherHeaders) { - _headers = new List(); + AddToHeaders(headers); + AddToHeaders(otherHeaders); + } - foreach (var header in headers) - { - if (!_headers.Contains(header, StringComparer.OrdinalIgnoreCase)) - { - _headers.Add(header); - } - } + private void AddToHeaders(HeadersList? headers) + { + if (headers is null) return; - foreach (var header in otherHeaders) + foreach (var header in headers) { if (!_headers.Contains(header, StringComparer.OrdinalIgnoreCase)) - { _headers.Add(header); - } } } @@ -92,8 +80,10 @@ public HeadersList(IEnumerable headers, IEnumerable otherHeaders /// /// Gets the number of elements contained in the . /// - public int Count => _headers is null ? 0 : _headers.Count; + public int Count => _headers.Count; + // ReSharper disable once ConstantConditionalAccessQualifier + // ReSharper disable once ConstantNullCoalescingCondition /// public IEnumerator GetEnumerator() => _headers?.GetEnumerator() ?? (IEnumerator)new EmptyEnumerator(); diff --git a/src/Elastic.Transport/Configuration/ITransportConfiguration.cs b/src/Elastic.Transport/Configuration/ITransportConfiguration.cs index d610098..f8abe90 100644 --- a/src/Elastic.Transport/Configuration/ITransportConfiguration.cs +++ b/src/Elastic.Transport/Configuration/ITransportConfiguration.cs @@ -17,20 +17,11 @@ namespace Elastic.Transport; /// All the transport configuration that you as the user can use to steer the behavior of the and all the components such /// as and . /// -public interface ITransportConfiguration : IDisposable +public interface ITransportConfiguration : IRequestConfiguration, IDisposable { - /// - AuthorizationHeader Authentication { get; } - - /// Provides a semaphoreslim to transport implementations that need to limit access to a resource + /// Provides a to transport implementations that need to limit access to a resource SemaphoreSlim BootstrapLock { get; } - /// - /// Use the following certificates to authenticate all HTTP requests. You can also set them on individual - /// request using - /// - X509CertificateCollection ClientCertificates { get; } - /// The connection abstraction behind which all actual IO happens IRequestInvoker Connection { get; } @@ -68,38 +59,6 @@ public interface ITransportConfiguration : IDisposable /// bool DisableAutomaticProxyDetection { get; } - /// - /// When set to true will disable (de)serializing directly to the request and response stream and return a byte[] - /// copy of the raw request and response. Defaults to false - /// - bool DisableDirectStreaming { get; } - - /// - /// When set to true will disable capturing an audit trail for requests. - /// - bool DisableAuditTrail { get; } - - /// - /// This signals that we do not want to send initial pings to unknown/previously dead nodes - /// and just send the call straightaway - /// - bool DisablePings { get; } - - /// - /// Enable gzip compressed requests and responses - /// - bool EnableHttpCompression { get; } - - /// - /// Try to send these headers for every request - /// - NameValueCollection? Headers { get; } - - /// - /// Whether HTTP pipelining is enabled. The default is true - /// - bool HttpPipeliningEnabled { get; } - /// /// KeepAliveInterval - specifies the interval, in milliseconds, between /// when successive keep-alive packets are sent if no acknowledgement is @@ -118,20 +77,6 @@ public interface ITransportConfiguration : IDisposable /// TimeSpan? MaxDeadTimeout { 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; } - /// Provides a memory stream factory MemoryStreamFactory MemoryStreamFactory { get; } @@ -156,16 +101,6 @@ public interface ITransportConfiguration : IDisposable /// Action? OnRequestDataCreated { get; } - /// - /// When enabled, all headers from the HTTP response will be included in the . - /// - bool? ParseAllHeaders { get; } - - /// - /// The timeout in milliseconds to use for ping requests, which are issued to determine whether a node is alive - /// - TimeSpan? PingTimeout { get; } - /// /// When set will force all connections through this proxy /// @@ -189,17 +124,6 @@ public interface ITransportConfiguration : IDisposable /// The serializer to use to serialize requests and deserialize responses Serializer RequestResponseSerializer { get; } - /// - /// The timeout in milliseconds for each request to Elasticsearch - /// - TimeSpan RequestTimeout { get; } - - /// - /// A containing the names of all HTTP response headers to attempt to parse and - /// included on the . - /// - HeadersList ResponseHeadersToParse { get; } - /// /// Register a ServerCertificateValidationCallback per request /// @@ -233,13 +157,6 @@ public interface ITransportConfiguration : IDisposable /// bool SniffsOnStartup { 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; } - /// /// Access to instance that is aware of this instance /// @@ -264,26 +181,11 @@ public interface ITransportConfiguration : IDisposable /// Func StatusCodeToResponseSuccess { get; } - /// - /// Whether the request should be sent with chunked Transfer-Encoding. - /// - bool TransferEncodingChunked { get; } - /// /// DnsRefreshTimeout for the connections. Defaults to 5 minutes. /// TimeSpan DnsRefreshTimeout { 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; } - /// /// Provide hints to serializer and products to produce pretty, non minified json. /// Note: this is not a guarantee you will always get prettified json @@ -293,7 +195,7 @@ public interface ITransportConfiguration : IDisposable /// /// Produces the client meta header for a request. /// - MetaHeaderProvider MetaHeaderProvider { get; } + MetaHeaderProvider? MetaHeaderProvider { get; } /// /// Disables the meta header which is included on all requests by default. This header contains lightweight information diff --git a/src/Elastic.Transport/Configuration/RequestConfiguration.cs b/src/Elastic.Transport/Configuration/RequestConfiguration.cs index 183d950..c0b9ba6 100644 --- a/src/Elastic.Transport/Configuration/RequestConfiguration.cs +++ b/src/Elastic.Transport/Configuration/RequestConfiguration.cs @@ -18,27 +18,25 @@ public interface IRequestConfiguration /// /// Force a different Accept header on the request /// - string Accept { get; set; } + string? Accept { get; set; } /// /// Treat the following statuses (on top of the 200 range) NOT as error. /// - IReadOnlyCollection AllowedStatusCodes { get; set; } + IReadOnlyCollection? AllowedStatusCodes { get; set; } - /// - /// Provide an authentication header override for this request - /// - AuthorizationHeader AuthenticationHeader { get; set; } + /// Provide an authentication header override for this request + AuthorizationHeader? Authentication { get; set; } /// /// Use the following client certificates to authenticate this single request /// - X509CertificateCollection ClientCertificates { get; set; } + X509CertificateCollection? ClientCertificates { get; set; } /// /// Force a different Content-Type header on the request /// - string ContentType { get; set; } + string? ContentType { get; set; } /// /// Whether to buffer the request and response bytes for the call @@ -54,7 +52,7 @@ public interface IRequestConfiguration /// 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? DisablePing { get; set; } + bool? DisablePings { get; set; } /// /// Forces no sniffing to occur on the request no matter what configuration is in place @@ -65,25 +63,39 @@ public interface IRequestConfiguration /// /// Whether or not this request should be pipelined. http://en.wikipedia.org/wiki/HTTP_pipelining defaults to true /// - bool? EnableHttpPipelining { get; set; } + bool? HttpPipeliningEnabled { get; set; } + + /// + /// Enable gzip compressed requests and responses + /// + bool? EnableHttpCompression { get; set; } /// /// This will force the operation on the specified node, this will bypass any configured connection pool and will no retry. /// - Uri ForceNode { get; set; } + Uri? ForceNode { get; set; } /// - /// This will override whatever is set on the connection configuration or whatever default the connectionpool has. + /// 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; set; } + /// + /// Limits the total runtime including retries separately from + ///
+	/// When not specified defaults to  which itself defaults to 60 seconds
+	/// 
+ ///
+ TimeSpan? MaxRetryTimeout { get; set; } + /// /// 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; set; } + string? OpaqueId { get; set; } - /// + /// Determines whether to parse all HTTP headers in the request. bool? ParseAllHeaders { get; set; } /// @@ -96,14 +108,15 @@ public interface IRequestConfiguration /// TimeSpan? RequestTimeout { get; set; } - /// - HeadersList ResponseHeadersToParse { get; set; } + + /// Specifies the headers from the response that should be parsed. + HeadersList? ResponseHeadersToParse { get; set; } /// /// 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; set; } + string? RunAs { get; set; } /// /// Instead of following a c/go like error checking on response.IsValid do throw an exception (except when is false) @@ -120,12 +133,16 @@ public interface IRequestConfiguration /// /// Try to send these headers for this single request /// - NameValueCollection Headers { get; set; } + NameValueCollection? Headers { get; set; } - /// + /// + /// Enable statistics about TCP connections to be collected when making a request + /// bool? EnableTcpStats { get; set; } - /// + /// + /// Enable statistics about thread pools to be collected when making a request + /// bool? EnableThreadPoolStats { get; set; } /// @@ -137,102 +154,125 @@ public interface IRequestConfiguration /// public class RequestConfiguration : IRequestConfiguration { + + /// The default request timeout. Defaults to 1 minute + public static readonly TimeSpan DefaultRequestTimeout = TimeSpan.FromMinutes(10); + + /// 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); + + /// - public string Accept { get; set; } + public string? Accept { get; set; } + /// - public IReadOnlyCollection AllowedStatusCodes { get; set; } + public IReadOnlyCollection? AllowedStatusCodes { get; set; } + /// - public AuthorizationHeader AuthenticationHeader { get; set; } + public AuthorizationHeader? Authentication { get; set; } + /// - public X509CertificateCollection ClientCertificates { get; set; } + public X509CertificateCollection? ClientCertificates { get; set; } + /// public string ContentType { get; set; } + /// public bool? DisableDirectStreaming { get; set; } + /// public bool? DisableAuditTrail { get; set; } + + /// + public bool? DisablePings { get; set; } + /// public bool? DisablePing { get; set; } + /// public bool? DisableSniff { get; set; } + + /// + public bool? HttpPipeliningEnabled { get; set; } + /// public bool? EnableHttpPipelining { get; set; } = true; + + /// + public bool? EnableHttpCompression { get; set; } + /// public Uri ForceNode { get; set; } + /// public int? MaxRetries { get; set; } + + /// + public TimeSpan? MaxRetryTimeout { get; set; } + /// public string OpaqueId { get; set; } + /// public TimeSpan? PingTimeout { get; set; } + /// public TimeSpan? RequestTimeout { get; set; } + /// public string RunAs { get; set; } + /// public bool? ThrowExceptions { get; set; } + /// public bool? TransferEncodingChunked { get; set; } + /// public NameValueCollection Headers { get; set; } + /// public bool? EnableTcpStats { get; set; } + /// public bool? EnableThreadPoolStats { get; set; } + /// - public HeadersList ResponseHeadersToParse { get; set; } + public HeadersList? ResponseHeadersToParse { get; set; } + /// public bool? ParseAllHeaders { get; set; } /// public RequestMetaData RequestMetaData { get; set; } + } /// public class RequestConfigurationDescriptor : IRequestConfiguration { /// - public RequestConfigurationDescriptor(IRequestConfiguration? config) + public RequestConfigurationDescriptor() { - Self.RequestTimeout = config?.RequestTimeout; - Self.PingTimeout = config?.PingTimeout; - Self.ContentType = config?.ContentType; - Self.Accept = config?.Accept; - Self.MaxRetries = config?.MaxRetries; - Self.ForceNode = config?.ForceNode; - Self.DisableSniff = config?.DisableSniff; - Self.DisablePing = config?.DisablePing; - Self.DisableDirectStreaming = config?.DisableDirectStreaming; - Self.DisableAuditTrail = config?.DisableAuditTrail; - Self.AllowedStatusCodes = config?.AllowedStatusCodes; - Self.AuthenticationHeader = config?.AuthenticationHeader; - Self.EnableHttpPipelining = config?.EnableHttpPipelining ?? true; - Self.RunAs = config?.RunAs; - Self.ClientCertificates = config?.ClientCertificates; - Self.ThrowExceptions = config?.ThrowExceptions; - Self.OpaqueId = config?.OpaqueId; - Self.TransferEncodingChunked = config?.TransferEncodingChunked; - Self.Headers = config?.Headers; - Self.EnableTcpStats = config?.EnableTcpStats; - Self.EnableThreadPoolStats = config?.EnableThreadPoolStats; - Self.ParseAllHeaders = config?.ParseAllHeaders; - - if (config?.ResponseHeadersToParse is not null) - Self.ResponseHeadersToParse = config.ResponseHeadersToParse; } string IRequestConfiguration.Accept { get; set; } IReadOnlyCollection IRequestConfiguration.AllowedStatusCodes { get; set; } - AuthorizationHeader IRequestConfiguration.AuthenticationHeader { get; set; } + AuthorizationHeader IRequestConfiguration.Authentication { get; set; } X509CertificateCollection IRequestConfiguration.ClientCertificates { get; set; } string IRequestConfiguration.ContentType { get; set; } bool? IRequestConfiguration.DisableDirectStreaming { get; set; } bool? IRequestConfiguration.DisableAuditTrail { get; set; } - bool? IRequestConfiguration.DisablePing { get; set; } + bool? IRequestConfiguration.DisablePings { get; set; } bool? IRequestConfiguration.DisableSniff { get; set; } - bool? IRequestConfiguration.EnableHttpPipelining { get; set; } = true; + bool? IRequestConfiguration.HttpPipeliningEnabled { get; set; } + bool? IRequestConfiguration.EnableHttpCompression { get; set; } Uri IRequestConfiguration.ForceNode { get; set; } int? IRequestConfiguration.MaxRetries { get; set; } + TimeSpan? IRequestConfiguration.MaxRetryTimeout { get; set; } string IRequestConfiguration.OpaqueId { get; set; } TimeSpan? IRequestConfiguration.PingTimeout { get; set; } TimeSpan? IRequestConfiguration.RequestTimeout { get; set; } @@ -243,7 +283,7 @@ public RequestConfigurationDescriptor(IRequestConfiguration? config) NameValueCollection IRequestConfiguration.Headers { get; set; } bool? IRequestConfiguration.EnableTcpStats { get; set; } bool? IRequestConfiguration.EnableThreadPoolStats { get; set; } - HeadersList IRequestConfiguration.ResponseHeadersToParse { get; set; } + HeadersList? IRequestConfiguration.ResponseHeadersToParse { get; set; } bool? IRequestConfiguration.ParseAllHeaders { get; set; } RequestMetaData IRequestConfiguration.RequestMetaData { get; set; } @@ -305,16 +345,16 @@ public RequestConfigurationDescriptor AllowedStatusCodes(params int[] codes) } /// - public RequestConfigurationDescriptor DisableSniffing(bool? disable = true) + public RequestConfigurationDescriptor DisableSniffing(bool disable = true) { Self.DisableSniff = disable; return this; } - /// - public RequestConfigurationDescriptor DisablePing(bool? disable = true) + /// + public RequestConfigurationDescriptor DisablePing(bool disable = true) { - Self.DisablePing = disable; + Self.DisablePings = disable; return this; } @@ -326,14 +366,14 @@ public RequestConfigurationDescriptor ThrowExceptions(bool throwExceptions = tru } /// - public RequestConfigurationDescriptor DisableDirectStreaming(bool? disable = true) + public RequestConfigurationDescriptor DisableDirectStreaming(bool disable = true) { Self.DisableDirectStreaming = disable; return this; } /// - public RequestConfigurationDescriptor DisableAuditTrail(bool? disable = true) + public RequestConfigurationDescriptor DisableAuditTrail(bool disable = true) { Self.DisableAuditTrail = disable; return this; @@ -356,14 +396,14 @@ public RequestConfigurationDescriptor MaxRetries(int retry) /// public RequestConfigurationDescriptor Authentication(AuthorizationHeader authentication) { - Self.AuthenticationHeader = authentication; + Self.Authentication = authentication; return this; } - /// + /// public RequestConfigurationDescriptor EnableHttpPipelining(bool enable = true) { - Self.EnableHttpPipelining = enable; + Self.HttpPipeliningEnabled = enable; return this; } @@ -383,7 +423,7 @@ public RequestConfigurationDescriptor ClientCertificate(string certificatePath) ClientCertificates(new X509Certificate2Collection { new X509Certificate(certificatePath) }); /// - public RequestConfigurationDescriptor TransferEncodingChunked(bool? transferEncodingChunked = true) + public RequestConfigurationDescriptor TransferEncodingChunked(bool transferEncodingChunked = true) { Self.TransferEncodingChunked = transferEncodingChunked; return this; @@ -397,21 +437,21 @@ public RequestConfigurationDescriptor GlobalHeaders(NameValueCollection headers) } /// - public RequestConfigurationDescriptor EnableTcpStats(bool? enableTcpStats = true) + public RequestConfigurationDescriptor EnableTcpStats(bool enableTcpStats = true) { Self.EnableTcpStats = enableTcpStats; return this; } /// - public RequestConfigurationDescriptor EnableThreadPoolStats(bool? enableThreadPoolStats = true) + public RequestConfigurationDescriptor EnableThreadPoolStats(bool enableThreadPoolStats = true) { Self.EnableThreadPoolStats = enableThreadPoolStats; return this; } /// - public RequestConfigurationDescriptor ParseAllHeaders(bool? enable = true) + public RequestConfigurationDescriptor ParseAllHeaders(bool enable = true) { Self.ParseAllHeaders = enable; return this; diff --git a/src/Elastic.Transport/Configuration/TransportConfiguration.cs b/src/Elastic.Transport/Configuration/TransportConfiguration.cs index a0fb696..72abbe2 100644 --- a/src/Elastic.Transport/Configuration/TransportConfiguration.cs +++ b/src/Elastic.Transport/Configuration/TransportConfiguration.cs @@ -41,23 +41,6 @@ public class TransportConfiguration : TransportConfigurationBase public static MemoryStreamFactory DefaultMemoryStreamFactory { get; } = Transport.DefaultMemoryStreamFactory.Default; - /// - /// 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 Elasticsearch. - /// 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. @@ -125,40 +108,25 @@ public abstract class TransportConfigurationBase : ITransportConfiguration private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly UrlFormatter _urlFormatter; - private AuthorizationHeader _authenticationHeader; - private X509CertificateCollection _clientCertificates; private Action _completedRequestHandler = DefaultCompletedRequestHandler; private int _transportClientLimit; private TimeSpan? _deadTimeout; private bool _disableAutomaticProxyDetection; - private bool _disableDirectStreaming; - private bool _disableAuditTrail; - 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; private Action _onRequestDataCreated = DefaultRequestDataCreated; - private TimeSpan? _pingTimeout; private string _proxyAddress; private string _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 MemoryStreamFactory _memoryStreamFactory; - private bool _enableTcpStats; - private bool _enableThreadPoolStats; private UserAgent _userAgent; private string _certificateFingerprint; private bool _disableMetaHeader; @@ -166,6 +134,8 @@ public abstract class TransportConfigurationBase : ITransportConfiguration private readonly Func _statusCodeToResponseSuccess; + private IRequestConfiguration RequestConfig => this; + /// /// /// @@ -179,10 +149,11 @@ protected TransportConfigurationBase(NodePool nodePool, IRequestInvoker? request _requestInvoker = requestInvoker ?? new HttpRequestInvoker(); _productRegistration = productRegistration ?? DefaultProductRegistration.Default; + UseThisRequestResponseSerializer = requestResponseSerializer ?? new LowLevelRequestResponseSerializer(); + RequestConfig.Accept = productRegistration?.DefaultMimeType; _transportClientLimit = TransportConfiguration.DefaultConnectionLimit; - _requestTimeout = TransportConfiguration.DefaultTimeout; _dnsRefreshTimeout = TransportConfiguration.DefaultDnsRefreshTimeout; _memoryStreamFactory = TransportConfiguration.DefaultMemoryStreamFactory; _sniffOnConnectionFault = true; @@ -197,11 +168,11 @@ protected TransportConfigurationBase(NodePool nodePool, IRequestInvoker? request if (nodePool is CloudNodePool cloudPool) { - _authenticationHeader = cloudPool.AuthenticationHeader; - _enableHttpCompression = true; + RequestConfig.Authentication = cloudPool.AuthenticationHeader; + RequestConfig.EnableHttpCompression = true; } - _headersToParse = new HeadersList(_productRegistration.ResponseHeadersToParse); + RequestConfig.ResponseHeadersToParse = new HeadersList(_productRegistration.ResponseHeadersToParse); } /// @@ -212,38 +183,60 @@ protected TransportConfigurationBase(NodePool nodePool, IRequestInvoker? request // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global protected Serializer UseThisRequestResponseSerializer { get; set; } - AuthorizationHeader ITransportConfiguration.Authentication => _authenticationHeader; + string IRequestConfiguration.Accept { get; set; } + + IReadOnlyCollection IRequestConfiguration.AllowedStatusCodes { get; set; } + + AuthorizationHeader IRequestConfiguration.Authentication { get; set; } SemaphoreSlim ITransportConfiguration.BootstrapLock => _semaphore; - X509CertificateCollection ITransportConfiguration.ClientCertificates => _clientCertificates; + X509CertificateCollection IRequestConfiguration.ClientCertificates { get; set; } + + string IRequestConfiguration.ContentType + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + IRequestInvoker ITransportConfiguration.Connection => _requestInvoker; ProductRegistration ITransportConfiguration.ProductRegistration => _productRegistration; int ITransportConfiguration.ConnectionLimit => _transportClientLimit; NodePool ITransportConfiguration.NodePool => _nodePool; TimeSpan? ITransportConfiguration.DeadTimeout => _deadTimeout; bool ITransportConfiguration.DisableAutomaticProxyDetection => _disableAutomaticProxyDetection; - bool ITransportConfiguration.DisableDirectStreaming => _disableDirectStreaming; - bool ITransportConfiguration.DisableAuditTrail => _disableAuditTrail; - bool ITransportConfiguration.DisablePings => _disablePings; - bool ITransportConfiguration.EnableHttpCompression => _enableHttpCompression; - NameValueCollection ITransportConfiguration.Headers => _headers; - bool ITransportConfiguration.HttpPipeliningEnabled => _enableHttpPipelining; + bool? IRequestConfiguration.DisableDirectStreaming { get; set; } + bool? IRequestConfiguration.DisableAuditTrail { get; set; } + bool? IRequestConfiguration.DisablePings { get; set; } + + // TODO Assign ? + bool? IRequestConfiguration.DisableSniff { get; set; } + + bool? IRequestConfiguration.EnableHttpCompression { get; set; } + NameValueCollection IRequestConfiguration.Headers { get; set; } + bool? IRequestConfiguration.HttpPipeliningEnabled { get; set; } + TimeSpan? ITransportConfiguration.KeepAliveInterval => _keepAliveInterval; TimeSpan? ITransportConfiguration.KeepAliveTime => _keepAliveTime; TimeSpan? ITransportConfiguration.MaxDeadTimeout => _maxDeadTimeout; - int? ITransportConfiguration.MaxRetries => _maxRetries; - TimeSpan? ITransportConfiguration.MaxRetryTimeout => _maxRetryTimeout; + int? IRequestConfiguration.MaxRetries { get; set; } + TimeSpan? IRequestConfiguration.MaxRetryTimeout { get; set; } + + // never assigned globally + Uri? IRequestConfiguration.ForceNode { get; set; } + // never assigned globally + string IRequestConfiguration.OpaqueId { get; set; } + MemoryStreamFactory ITransportConfiguration.MemoryStreamFactory => _memoryStreamFactory; Func ITransportConfiguration.NodePredicate => _nodePredicate; Action ITransportConfiguration.OnRequestCompleted => _completedRequestHandler; Action ITransportConfiguration.OnRequestDataCreated => _onRequestDataCreated; - TimeSpan? ITransportConfiguration.PingTimeout => _pingTimeout; + TimeSpan? IRequestConfiguration.PingTimeout { get; set; } string ITransportConfiguration.ProxyAddress => _proxyAddress; string ITransportConfiguration.ProxyPassword => _proxyPassword; string ITransportConfiguration.ProxyUsername => _proxyUsername; NameValueCollection ITransportConfiguration.QueryStringParameters => _queryString; Serializer ITransportConfiguration.RequestResponseSerializer => UseThisRequestResponseSerializer; - TimeSpan ITransportConfiguration.RequestTimeout => _requestTimeout; + TimeSpan? IRequestConfiguration.RequestTimeout { get; set; } TimeSpan ITransportConfiguration.DnsRefreshTimeout => _dnsRefreshTimeout; string ITransportConfiguration.CertificateFingerprint => _certificateFingerprint; @@ -254,13 +247,23 @@ protected TransportConfigurationBase(NodePool nodePool, IRequestInvoker? request TimeSpan? ITransportConfiguration.SniffInformationLifeSpan => _sniffLifeSpan; bool ITransportConfiguration.SniffsOnConnectionFault => _sniffOnConnectionFault; bool ITransportConfiguration.SniffsOnStartup => _sniffOnStartup; - bool ITransportConfiguration.ThrowExceptions => _throwExceptions; + + // TODO Assign + string IRequestConfiguration.RunAs { get; set; } + + bool? IRequestConfiguration.ThrowExceptions { get; set; } UrlFormatter ITransportConfiguration.UrlFormatter => _urlFormatter; UserAgent ITransportConfiguration.UserAgent => _userAgent; Func ITransportConfiguration.StatusCodeToResponseSuccess => _statusCodeToResponseSuccess; - bool ITransportConfiguration.TransferEncodingChunked => _transferEncodingChunked; - bool ITransportConfiguration.EnableTcpStats => _enableTcpStats; - bool ITransportConfiguration.EnableThreadPoolStats => _enableThreadPoolStats; + bool? IRequestConfiguration.TransferEncodingChunked { get; set; } + bool? IRequestConfiguration.EnableTcpStats { get; set; } + bool? IRequestConfiguration.EnableThreadPoolStats { get; set; } + + RequestMetaData? IRequestConfiguration.RequestMetaData + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } void IDisposable.Dispose() => DisposeManagedResources(); @@ -282,8 +285,8 @@ public T EnableTcpKeepAlive(TimeSpan keepAliveTime, TimeSpan keepAliveInterval) Assign(keepAliveTime, (a, v) => a._keepAliveTime = v) .Assign(keepAliveInterval, (a, v) => a._keepAliveInterval = v); - /// - public T MaximumRetries(int maxRetries) => Assign(maxRetries, (a, v) => a._maxRetries = v); + /// + public T MaximumRetries(int maxRetries) => Assign(maxRetries, (a, v) => RequestConfig.MaxRetries = v); /// /// @@ -304,30 +307,30 @@ public T SniffOnConnectionFault(bool sniffsOnConnectionFault = true) => /// 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); - /// - public T EnableHttpCompression(bool enabled = true) => Assign(enabled, (a, v) => a._enableHttpCompression = v); + /// + public T EnableHttpCompression(bool enabled = true) => Assign(enabled, (a, v) => RequestConfig.EnableHttpCompression = v); /// public T DisableAutomaticProxyDetection(bool disable = true) => Assign(disable, (a, v) => a._disableAutomaticProxyDetection = v); - /// - public T ThrowExceptions(bool alwaysThrow = true) => Assign(alwaysThrow, (a, v) => a._throwExceptions = v); + /// + public T ThrowExceptions(bool alwaysThrow = true) => Assign(alwaysThrow, (a, v) => RequestConfig.ThrowExceptions = v); - /// - public T DisablePing(bool disable = true) => Assign(disable, (a, v) => a._disablePings = v); + /// + public T DisablePing(bool disable = true) => Assign(disable, (a, v) => RequestConfig.DisablePings = v); /// // ReSharper disable once MemberCanBePrivate.Global public T GlobalQueryStringParameters(NameValueCollection queryStringParameters) => Assign(queryStringParameters, (a, v) => a._queryString.Add(v)); - /// + /// public T GlobalHeaders(NameValueCollection headers) => Assign(headers, (a, v) => a._headers.Add(v)); - /// - public T RequestTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._requestTimeout = v); + /// + public T RequestTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => RequestConfig.RequestTimeout = v); - /// - public T PingTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._pingTimeout = v); + /// + public T PingTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => RequestConfig.PingTimeout = v); /// public T DeadTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._deadTimeout = v); @@ -335,8 +338,8 @@ public T SniffOnConnectionFault(bool sniffsOnConnectionFault = true) => /// public T MaxDeadTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._maxDeadTimeout = v); - /// - public T MaxRetryTimeout(TimeSpan maxRetryTimeout) => Assign(maxRetryTimeout, (a, v) => a._maxRetryTimeout = v); + /// + public T MaxRetryTimeout(TimeSpan maxRetryTimeout) => Assign(maxRetryTimeout, (a, v) => RequestConfig.MaxRetryTimeout = v); /// public T DnsRefreshTimeout(TimeSpan timeout) => Assign(timeout, (a, v) => a._dnsRefreshTimeout = v); @@ -358,12 +361,12 @@ public T Proxy(Uri proxyAddress, string username, string password) => public T Proxy(Uri proxyAddress) => Assign(proxyAddress.ToString(), (a, v) => a._proxyAddress = v); - /// + /// // ReSharper disable once MemberCanBePrivate.Global - public T DisableDirectStreaming(bool b = true) => Assign(b, (a, v) => a._disableDirectStreaming = v); + public T DisableDirectStreaming(bool b = true) => Assign(b, (a, v) => RequestConfig.DisableDirectStreaming = v); - /// - public T DisableAuditTrail(bool b = true) => Assign(b, (a, v) => a._disableAuditTrail = v); + /// + public T DisableAuditTrail(bool b = true) => Assign(b, (a, v) => RequestConfig.DisableAuditTrail = v); /// public T OnRequestCompleted(Action handler) => @@ -374,10 +377,10 @@ public T OnRequestDataCreated(Action handler) => Assign(handler, (a, v) => a._onRequestDataCreated += v ?? DefaultRequestDataCreated); /// - public T Authentication(AuthorizationHeader header) => Assign(header, (a, v) => a._authenticationHeader = v); + public T Authentication(AuthorizationHeader header) => Assign(header, (a, v) => RequestConfig.Authentication = v); - /// - public T EnableHttpPipelining(bool enabled = true) => Assign(enabled, (a, v) => a._enableHttpPipelining = v); + /// + public T EnableHttpPipelining(bool enabled = true) => Assign(enabled, (a, v) => RequestConfig.HttpPipeliningEnabled = v); /// /// @@ -412,23 +415,21 @@ public virtual T EnableDebugMode(Action onRequestCompleted = nul // ReSharper disable once MemberCanBeProtected.Global public virtual T PrettyJson(bool b = true) => Assign(b, (a, v) => a._prettyJson = v); - private bool? _parseAllHeaders; - bool? ITransportConfiguration.ParseAllHeaders => _parseAllHeaders; + bool? IRequestConfiguration.ParseAllHeaders { get; set; } - /// - public virtual T ParseAllHeaders(bool b = true) => Assign(b, (a, v) => a._parseAllHeaders = v); + /// + public virtual T ParseAllHeaders(bool b = true) => Assign(b, (a, v) => ((IRequestConfiguration)this).ParseAllHeaders = v); - private HeadersList _headersToParse; - HeadersList ITransportConfiguration.ResponseHeadersToParse => _headersToParse; + HeadersList? IRequestConfiguration.ResponseHeadersToParse { get; set; } MetaHeaderProvider ITransportConfiguration.MetaHeaderProvider => _metaHeaderProvider; bool ITransportConfiguration.DisableMetaHeader => _disableMetaHeader; - /// + /// public virtual T ResponseHeadersToParse(HeadersList headersToParse) { - _headersToParse = new HeadersList(_headersToParse, headersToParse); + ((IRequestConfiguration)this).ResponseHeadersToParse = new HeadersList(((IRequestConfiguration)this).ResponseHeadersToParse, headersToParse); return (T)this; } @@ -436,17 +437,17 @@ public virtual T ResponseHeadersToParse(HeadersList headersToParse) public T ServerCertificateValidationCallback(Func callback) => Assign(callback, (a, v) => a._serverCertificateValidationCallback = v); - /// + /// public T ClientCertificates(X509CertificateCollection certificates) => - Assign(certificates, (a, v) => a._clientCertificates = v); + Assign(certificates, (a, v) => RequestConfig.ClientCertificates = v); - /// + /// public T ClientCertificate(X509Certificate certificate) => - Assign(new X509Certificate2Collection { certificate }, (a, v) => a._clientCertificates = v); + Assign(new X509Certificate2Collection { certificate }, (a, v) => RequestConfig.ClientCertificates = v); - /// + /// public T ClientCertificate(string certificatePath) => - Assign(new X509Certificate2Collection { new X509Certificate(certificatePath) }, (a, v) => a._clientCertificates = v); + Assign(new X509Certificate2Collection { new X509Certificate(certificatePath) }, (a, v) => RequestConfig.ClientCertificates = v); /// public T SkipDeserializationForStatusCodes(params int[] statusCodes) => @@ -455,17 +456,17 @@ public T SkipDeserializationForStatusCodes(params int[] statusCodes) => /// public T UserAgent(UserAgent userAgent) => Assign(userAgent, (a, v) => a._userAgent = v); - /// - public T TransferEncodingChunked(bool transferEncodingChunked = true) => Assign(transferEncodingChunked, (a, v) => a._transferEncodingChunked = v); + /// + public T TransferEncodingChunked(bool transferEncodingChunked = true) => Assign(transferEncodingChunked, (a, v) => RequestConfig.TransferEncodingChunked = v); /// public T MemoryStreamFactory(MemoryStreamFactory memoryStreamFactory) => Assign(memoryStreamFactory, (a, v) => a._memoryStreamFactory = v); - /// > - public T EnableTcpStats(bool enableTcpStats = true) => Assign(enableTcpStats, (a, v) => a._enableTcpStats = v); + /// > + public T EnableTcpStats(bool enableTcpStats = true) => Assign(enableTcpStats, (a, v) => RequestConfig.EnableTcpStats = v); - /// > - public T EnableThreadPoolStats(bool enableThreadPoolStats = true) => Assign(enableThreadPoolStats, (a, v) => a._enableThreadPoolStats = v); + /// > + public T EnableThreadPoolStats(bool enableThreadPoolStats = true) => Assign(enableThreadPoolStats, (a, v) => RequestConfig.EnableThreadPoolStats = v); /// > public T DisableMetaHeader(bool disable = true) => Assign(disable, (a, v) => a._disableMetaHeader = v); diff --git a/src/Elastic.Transport/Diagnostics/Auditing/AuditEvent.cs b/src/Elastic.Transport/Diagnostics/Auditing/AuditEvent.cs index bc2f6c8..7e64b89 100644 --- a/src/Elastic.Transport/Diagnostics/Auditing/AuditEvent.cs +++ b/src/Elastic.Transport/Diagnostics/Auditing/AuditEvent.cs @@ -53,14 +53,14 @@ public enum AuditEvent /// /// The request took too long. /// This could mean the call was retried but retrying was to slow and cumulatively this exceeded - /// + /// /// MaxTimeoutReached, /// /// The request was not able to complete /// successfully and exceeded the available retries as configured on - /// . + /// . /// MaxRetriesReached, @@ -82,7 +82,7 @@ public enum AuditEvent CancellationRequested, /// - /// The request failed within the allotted but failed + /// The request failed within the allotted but failed /// on all the available /// FailedOverAllNodes, diff --git a/src/Elastic.Transport/DistributedTransport.cs b/src/Elastic.Transport/DistributedTransport.cs index 4739288..c5d1b16 100644 --- a/src/Elastic.Transport/DistributedTransport.cs +++ b/src/Elastic.Transport/DistributedTransport.cs @@ -92,39 +92,33 @@ public DistributedTransport( /// public TResponse Request( - HttpMethod method, - string path, + in EndpointPath path, PostData? data, - RequestParameters? requestParameters, in OpenTelemetryData openTelemetryData, IRequestConfiguration? localConfiguration, CustomResponseBuilder? responseBuilder ) where TResponse : TransportResponse, new() => - RequestCoreAsync(isAsync: false, - method, path, data, requestParameters, openTelemetryData, localConfiguration, responseBuilder).EnsureCompleted(); + RequestCoreAsync(isAsync: false, path, data, openTelemetryData, localConfiguration, responseBuilder) + .EnsureCompleted(); /// public Task RequestAsync( - HttpMethod method, - string path, + in EndpointPath path, PostData? data, - RequestParameters? requestParameters, in OpenTelemetryData openTelemetryData, IRequestConfiguration? localConfiguration, CustomResponseBuilder? responseBuilder, CancellationToken cancellationToken = default ) where TResponse : TransportResponse, new() => - RequestCoreAsync(isAsync: true, - method, path, data, requestParameters, openTelemetryData, localConfiguration, responseBuilder, cancellationToken).AsTask(); + RequestCoreAsync(isAsync: true, path, data, openTelemetryData, localConfiguration, responseBuilder, cancellationToken) + .AsTask(); private async ValueTask RequestCoreAsync( bool isAsync, - HttpMethod method, - string path, + EndpointPath path, PostData? data, - RequestParameters? requestParameters, OpenTelemetryData openTelemetryData, IRequestConfiguration? localRequestConfiguration, CustomResponseBuilder? customResponseBuilder, @@ -135,7 +129,7 @@ private async ValueTask RequestCoreAsync( Activity activity = null; if (OpenTelemetry.ElasticTransportActivitySource.HasListeners()) - activity = OpenTelemetry.ElasticTransportActivitySource.StartActivity(openTelemetryData.SpanName ?? method.GetStringValue(), + activity = OpenTelemetry.ElasticTransportActivitySource.StartActivity(openTelemetryData.SpanName ?? path.Method.GetStringValue(), ActivityKind.Client); try @@ -147,11 +141,13 @@ private async ValueTask RequestCoreAsync( else pipeline.FirstPoolUsage(Configuration.BootstrapLock); - var pathAndQuery = requestParameters?.CreatePathWithQueryStrings(path, Configuration) ?? path; - var requestData = new RequestData(method, pathAndQuery, data, Configuration, localRequestConfiguration, customResponseBuilder, MemoryStreamFactory, openTelemetryData); + //var pathAndQuery = requestParameters?.CreatePathWithQueryStrings(path, Configuration) ?? path; + var requestData = new RequestData(Configuration, localRequestConfiguration, customResponseBuilder, MemoryStreamFactory); Configuration.OnRequestDataCreated?.Invoke(requestData); TResponse response = null; + var endpoint = Endpoint.Empty(path); + if (activity is { IsAllDataRequested: true }) { if (activity.IsAllDataRequested) @@ -165,11 +161,11 @@ private async ValueTask RequestCoreAsync( activity.SetTag(OpenTelemetryAttributes.ElasticTransportVersion, ReflectionVersionInfo.TransportVersion); activity.SetTag(SemanticConventions.UserAgentOriginal, Configuration.UserAgent.ToString()); - if (requestData.OpenTelemetryData.SpanAttributes is not null) - foreach (var attribute in requestData.OpenTelemetryData.SpanAttributes) + if (openTelemetryData.SpanAttributes is not null) + foreach (var attribute in openTelemetryData.SpanAttributes) activity.SetTag(attribute.Key, attribute.Value); - activity.SetTag(SemanticConventions.HttpRequestMethod, requestData.Method.GetStringValue()); + activity.SetTag(SemanticConventions.HttpRequestMethod, endpoint.Method.GetStringValue()); } List? seenExceptions = null; @@ -177,20 +173,20 @@ private async ValueTask RequestCoreAsync( if (pipeline.TryGetSingleNode(out var singleNode)) { + endpoint = endpoint with { Node = singleNode }; // No value in marking a single node as dead. We have no other options! attemptedNodes = 1; - requestData.Node = singleNode; - activity?.SetTag(SemanticConventions.UrlFull, requestData.Uri.AbsoluteUri); - activity?.SetTag(SemanticConventions.ServerAddress, requestData.Uri.Host); - activity?.SetTag(SemanticConventions.ServerPort, requestData.Uri.Port); + activity?.SetTag(SemanticConventions.UrlFull, endpoint.Uri.AbsoluteUri); + activity?.SetTag(SemanticConventions.ServerAddress, endpoint.Uri.Host); + activity?.SetTag(SemanticConventions.ServerPort, endpoint.Uri.Port); try { if (isAsync) - response = await pipeline.CallProductEndpointAsync(requestData, cancellationToken) + response = await pipeline.CallProductEndpointAsync(endpoint, requestData, data, cancellationToken) .ConfigureAwait(false); else - response = pipeline.CallProductEndpoint(requestData); + response = pipeline.CallProductEndpoint(endpoint, requestData, data); } catch (PipelineException pipelineException) when (!pipelineException.Recoverable) { @@ -202,7 +198,7 @@ private async ValueTask RequestCoreAsync( } catch (Exception killerException) { - ThrowUnexpectedTransportException(killerException, seenExceptions, requestData, response, pipeline); + ThrowUnexpectedTransportException(killerException, seenExceptions, endpoint, response, pipeline); } } else @@ -210,13 +206,13 @@ private async ValueTask RequestCoreAsync( foreach (var node in pipeline.NextNode()) { attemptedNodes++; - requestData.Node = node; + endpoint = endpoint with { Node = node }; // If multiple nodes are attempted, the final node attempted will be used to set the operation span attributes. // Each physical node attempt in CallProductEndpoint will also record these attributes. - activity?.SetTag(SemanticConventions.UrlFull, requestData.Uri.AbsoluteUri); - activity?.SetTag(SemanticConventions.ServerAddress, requestData.Uri.Host); - activity?.SetTag(SemanticConventions.ServerPort, requestData.Uri.Port); + activity?.SetTag(SemanticConventions.UrlFull, endpoint.Uri.AbsoluteUri); + activity?.SetTag(SemanticConventions.ServerAddress, endpoint.Uri.Host); + activity?.SetTag(SemanticConventions.ServerPort, endpoint.Uri.Port); try { @@ -236,10 +232,10 @@ private async ValueTask RequestCoreAsync( } if (isAsync) - response = await pipeline.CallProductEndpointAsync(requestData, cancellationToken) + response = await pipeline.CallProductEndpointAsync(endpoint, requestData, data, cancellationToken) .ConfigureAwait(false); else - response = pipeline.CallProductEndpoint(requestData); + response = pipeline.CallProductEndpoint(endpoint, requestData, data); if (!response.ApiCallDetails.SuccessOrKnownError) { @@ -270,7 +266,7 @@ private async ValueTask RequestCoreAsync( throw new UnexpectedTransportException(killerException, seenExceptions) { - Request = requestData, + Endpoint = endpoint, ApiCallDetails = response?.ApiCallDetails, AuditTrail = pipeline.AuditTrail }; @@ -296,7 +292,7 @@ private async ValueTask RequestCoreAsync( activity?.SetTag(SemanticConventions.HttpResponseStatusCode, response.ApiCallDetails.HttpStatusCode); activity?.SetTag(OpenTelemetryAttributes.ElasticTransportAttemptedNodes, attemptedNodes); - return FinalizeResponse(requestData, pipeline, seenExceptions, response); + return FinalizeResponse(endpoint, requestData, data, pipeline, seenExceptions, response); } finally { @@ -306,12 +302,14 @@ private async ValueTask RequestCoreAsync( private static void ThrowUnexpectedTransportException(Exception killerException, List seenExceptions, - RequestData requestData, + Endpoint endpoint, TResponse response, RequestPipeline pipeline ) where TResponse : TransportResponse, new() => throw new UnexpectedTransportException(killerException, seenExceptions) { - Request = requestData, ApiCallDetails = response?.ApiCallDetails, AuditTrail = pipeline.AuditTrail + Endpoint = endpoint, + ApiCallDetails = response?.ApiCallDetails, + AuditTrail = pipeline.AuditTrail }; private static void HandlePipelineException( @@ -326,19 +324,19 @@ ref List seenExceptions seenExceptions.Add(ex); } - private TResponse FinalizeResponse(RequestData requestData, RequestPipeline pipeline, + private TResponse FinalizeResponse(Endpoint endpoint, RequestData requestData, PostData? postData, RequestPipeline pipeline, List? seenExceptions, TResponse? response ) where TResponse : TransportResponse, new() { - if (requestData.Node == null) //foreach never ran - pipeline.ThrowNoNodesAttempted(requestData, seenExceptions); + if (endpoint.IsEmpty) //foreach never ran + pipeline.ThrowNoNodesAttempted(endpoint, seenExceptions); var callDetails = GetMostRecentCallDetails(response, seenExceptions); - var clientException = pipeline.CreateClientException(response, callDetails, requestData, seenExceptions); + var clientException = pipeline.CreateClientException(response, callDetails, endpoint, requestData, seenExceptions); if (response?.ApiCallDetails == null) - pipeline.BadResponse(ref response, callDetails, requestData, clientException); + pipeline.BadResponse(ref response, callDetails, endpoint, requestData, postData, clientException); HandleTransportException(requestData, clientException, response); return response; diff --git a/src/Elastic.Transport/Exceptions/TransportException.cs b/src/Elastic.Transport/Exceptions/TransportException.cs index 958b1e2..fe94bb0 100644 --- a/src/Elastic.Transport/Exceptions/TransportException.cs +++ b/src/Elastic.Transport/Exceptions/TransportException.cs @@ -15,7 +15,7 @@ namespace Elastic.Transport; /// /// Exceptions that occur are wrapped inside this exception. This is done to not lose valuable diagnostic information. /// -/// When is set these exceptions are rethrown and need +/// When is set these exceptions are rethrown and need /// to be caught /// /// @@ -51,7 +51,7 @@ public TransportException(PipelineFailure failure, string message, TransportResp public PipelineFailure? FailureReason { get; } /// Information about the request that triggered this exception - public RequestData Request { get; internal set; } + public Endpoint? Endpoint { get; internal set; } /// The response if available that triggered the exception public ApiCallDetails ApiCallDetails { get; internal set; } @@ -74,16 +74,10 @@ public string DebugInformation .Append(failureReason) .Append(" while attempting "); - if (Request != null) + if (Endpoint is not null) { - sb.Append(Request.Method.GetStringValue()).Append(" on "); - if (Request.Uri != null) - sb.AppendLine(Request.Uri.ToString()); - else - { - sb.Append(Request.PathAndQuery) - .AppendLine(" on an empty node, likely a node predicate on ConnectionSettings not matching ANY nodes"); - } + sb.Append(Endpoint.Method.GetStringValue()).Append(" on "); + sb.AppendLine(Endpoint.Uri.ToString()); } else if (ApiCallDetails != null) { diff --git a/src/Elastic.Transport/ITransport.cs b/src/Elastic.Transport/ITransport.cs index ab5c7eb..a813aef 100644 --- a/src/Elastic.Transport/ITransport.cs +++ b/src/Elastic.Transport/ITransport.cs @@ -18,10 +18,8 @@ public interface ITransport /// /// NOTE: It is highly recommended to prefer the asynchronous version of this method instead of this synchronous API. /// The type to deserialize the response body into. - /// The for the HTTP request. /// The path of the request. /// The data to be included as the body of the HTTP request. - /// The parameters for the request. /// Data to be used to control the OpenTelemetry instrumentation. /// Per request configuration /// @@ -30,10 +28,8 @@ public interface ITransport /// /// The deserialized . public TResponse Request( - HttpMethod method, - string path, + in EndpointPath path, PostData? postData, - RequestParameters? requestParameters, in OpenTelemetryData openTelemetryData, IRequestConfiguration? localConfiguration, CustomResponseBuilder? responseBuilder @@ -45,10 +41,8 @@ public TResponse Request( /// Orchestrate a request asynchronously into a using the workflow defined in the . /// /// The type to deserialize the response body into. - /// The for the HTTP request. /// The path of the request. /// The data to be included as the body of the HTTP request. - /// The parameters for the request. /// The cancellation token to use. /// Data to be used to control the OpenTelemetry instrumentation. /// Per request configuration @@ -58,10 +52,8 @@ public TResponse Request( /// /// The deserialized . public Task RequestAsync( - HttpMethod method, - string path, + in EndpointPath path, PostData? postData, - RequestParameters? requestParameters, in OpenTelemetryData openTelemetryData, IRequestConfiguration? localConfiguration, CustomResponseBuilder? responseBuilder, @@ -90,31 +82,29 @@ public static class TransportExtensions { /// > - public static TResponse Request( - this ITransport transport, - HttpMethod method, - string path) + public static TResponse Request(this ITransport transport, in EndpointPath path) where TResponse : TransportResponse, new() - => transport.Request(method, path, null, null, default, null, null); + => transport.Request(path, null, default, null, null); /// > - public static TResponse Request( - this ITransport transport, - HttpMethod method, - string path, - PostData? postData) + public static TResponse Request(this ITransport transport, in EndpointPath path, PostData? postData) where TResponse : TransportResponse, new() - => transport.Request(method, path, postData, null, default, null, null); + => transport.Request(path, postData, default, null, null); /// > - public static TResponse Request( - this ITransport transport, - HttpMethod method, - string path, - PostData? postData, - RequestParameters? requestParameters) + public static TResponse Request(this ITransport transport, in EndpointPath path, PostData? postData, IRequestConfiguration configuration) where TResponse : TransportResponse, new() - => transport.Request(method, path, postData, requestParameters, default, null, null); + => transport.Request(path, postData, default, configuration, null); + + /// > + public static TResponse Request(this ITransport transport, HttpMethod method, string path) + where TResponse : TransportResponse, new() + => transport.Request(new EndpointPath(method, path), null, default, null, null); + + /// > + public static TResponse Request(this ITransport transport, HttpMethod method, string path, PostData? postData) + where TResponse : TransportResponse, new() + => transport.Request(new EndpointPath(method, path), postData, default, null, null); /// > public static TResponse Request( @@ -122,29 +112,28 @@ public static TResponse Request( HttpMethod method, string path, PostData? postData, - RequestParameters? requestParameters, IRequestConfiguration localConfiguration) where TResponse : TransportResponse, new() - => transport.Request(method, path, postData, requestParameters, default, localConfiguration, null); + => transport.Request(new EndpointPath(method, path), postData, default, localConfiguration, null); /// > - public static Task RequestAsync( - this ITransport transport, - HttpMethod method, - string path, - CancellationToken cancellationToken = default) + public static Task RequestAsync(this ITransport transport, in EndpointPath path, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() - => transport.RequestAsync(method, path, null, null, default, null, null, cancellationToken); + => transport.RequestAsync(path, null, default, null, null, cancellationToken); + + /// > + public static Task RequestAsync(this ITransport transport, in EndpointPath path, PostData? postData, CancellationToken cancellationToken = default) + where TResponse : TransportResponse, new() + => transport.RequestAsync(path, postData, default, null, null, cancellationToken); /// > public static Task RequestAsync( this ITransport transport, HttpMethod method, string path, - PostData? postData, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() - => transport.RequestAsync(method, path, postData, null, default, null, null, cancellationToken); + => transport.RequestAsync(new EndpointPath(method, path), null, default, null, null, cancellationToken); /// > public static Task RequestAsync( @@ -152,10 +141,9 @@ public static Task RequestAsync( HttpMethod method, string path, PostData? postData, - RequestParameters? requestParameters, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() - => transport.RequestAsync(method, path, postData, requestParameters, default, null, null, cancellationToken); + => transport.RequestAsync(new EndpointPath(method, path), postData, default, null, null, cancellationToken); /// > public static Task RequestAsync( @@ -163,9 +151,8 @@ public static Task RequestAsync( HttpMethod method, string path, PostData? postData, - RequestParameters? requestParameters, IRequestConfiguration localConfiguration, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() - => transport.RequestAsync(method, path, postData, requestParameters, default, localConfiguration, null, cancellationToken); + => transport.RequestAsync(new EndpointPath(method, path), postData, default, localConfiguration, null, cancellationToken); } diff --git a/src/Elastic.Transport/ITransportHttpMethodExtensions.cs b/src/Elastic.Transport/ITransportHttpMethodExtensions.cs index c156ff1..e987d48 100644 --- a/src/Elastic.Transport/ITransportHttpMethodExtensions.cs +++ b/src/Elastic.Transport/ITransportHttpMethodExtensions.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; +using static Elastic.Transport.HttpMethod; namespace Elastic.Transport; @@ -12,72 +13,105 @@ namespace Elastic.Transport; /// public static class TransportHttpMethodExtensions { + private static EndpointPath ToEndpointPath(HttpMethod method, string path, RequestParameters parameters, ITransportConfiguration configuration) => + new(method, parameters.CreatePathWithQueryStrings(path, configuration)); + /// Perform a GET request - public static TResponse Get(this ITransport requestHandler, string path, - RequestParameters? parameters = null) + public static TResponse Get(this ITransport> transport, string path, RequestParameters parameters) where TResponse : TransportResponse, new() => - requestHandler.Request(HttpMethod.GET, path, null, parameters); + transport.Request(ToEndpointPath(GET, path, parameters, transport.Configuration), postData: null, openTelemetryData: default, null, null); /// Perform a GET request - public static Task GetAsync(this ITransport requestHandler, string path, - RequestParameters? parameters = null, CancellationToken cancellationToken = default) + public static Task GetAsync(this ITransport> transport, string path, + RequestParameters parameters, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() => - requestHandler.RequestAsync(HttpMethod.GET, path, null, parameters, cancellationToken); + transport.RequestAsync(ToEndpointPath(GET, path, parameters, transport.Configuration), postData: null, openTelemetryData: default, null, null, cancellationToken); - /// Perform a HEAD request - public static TResponse Head(this ITransport requestHandler, string path, - RequestParameters? parameters = null) + /// Perform a GET request + public static TResponse Get(this ITransport transport, string pathAndQuery) where TResponse : TransportResponse, new() => - requestHandler.Request(HttpMethod.HEAD, path, null, parameters); + transport.Request(new EndpointPath(GET, pathAndQuery), postData: null, openTelemetryData: default, null, null); - /// Perform a HEAD request - public static Task HeadAsync(this ITransport requestHandler, string path, - RequestParameters? parameters = null, CancellationToken cancellationToken = default) + /// Perform a GET request + public static Task GetAsync(this ITransport transport, string pathAndQuery, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() => - requestHandler.RequestAsync(HttpMethod.HEAD, path, null, parameters, cancellationToken); + transport.RequestAsync(new EndpointPath(GET, pathAndQuery), postData: null, openTelemetryData: default, null, null, cancellationToken); + + /// Perform a HEAD request + public static VoidResponse Head(this ITransport> transport, string path, RequestParameters parameters) + => transport.Request(ToEndpointPath(HEAD, path, parameters, transport.Configuration), postData: null, openTelemetryData: default, null, null); /// Perform a HEAD request - public static VoidResponse Head(this ITransport requestHandler, string path, RequestParameters? parameters = null) => - requestHandler.Head(path, parameters); + public static Task HeadAsync(this ITransport> transport, string path, RequestParameters parameters, CancellationToken cancellationToken = default) + => transport.RequestAsync(ToEndpointPath(HEAD, path, parameters, transport.Configuration), postData: null, openTelemetryData: default, null, null, cancellationToken); /// Perform a HEAD request - public static Task HeadAsync(this ITransport requestHandler, string path, - RequestParameters? parameters = null, CancellationToken cancellationToken = default) => - requestHandler.HeadAsync(path, parameters, cancellationToken); + public static VoidResponse Head(this ITransport transport, string pathAndQuery) + => transport.Request(new EndpointPath(HEAD, pathAndQuery), postData: null, openTelemetryData: default, null, null); + + /// Perform a HEAD request + public static Task HeadAsync(this ITransport transport, string pathAndQuery, CancellationToken cancellationToken = default) + => transport.RequestAsync(new EndpointPath(HEAD, pathAndQuery), postData: null, openTelemetryData: default, null, null, cancellationToken); + + /// Perform a POST request + public static TResponse Post(this ITransport> transport, string path, PostData data, RequestParameters parameters) + where TResponse : TransportResponse, new() => + transport.Request(ToEndpointPath(POST, path, parameters, transport.Configuration), data, openTelemetryData: default, null, null); + + /// Perform a POST request + public static Task PostAsync(this ITransport> transport, string path, PostData data, + RequestParameters parameters, CancellationToken cancellationToken = default) + where TResponse : TransportResponse, new() => + transport.RequestAsync(ToEndpointPath(POST, path, parameters, transport.Configuration), data, openTelemetryData: default, null, null, cancellationToken); /// Perform a POST request - public static TResponse Post(this ITransport requestHandler, string path, PostData data, - RequestParameters? parameters = null) + public static TResponse Post(this ITransport transport, string pathAndQuery, PostData data) where TResponse : TransportResponse, new() => - requestHandler.Request(HttpMethod.POST, path, data, parameters); + transport.Request(new EndpointPath(POST, pathAndQuery), data, openTelemetryData: default, null, null); /// Perform a POST request - public static Task PostAsync(this ITransport requestHandler, string path, PostData data, - RequestParameters? parameters = null, CancellationToken cancellationToken = default) + public static Task PostAsync(this ITransport transport, string pathAndQuery, PostData data, CancellationToken cancellationToken = default) + where TResponse : TransportResponse, new() => + transport.RequestAsync(new EndpointPath(POST, pathAndQuery), data, openTelemetryData: default, null, null, cancellationToken); + + /// Perform a PUT request + public static TResponse Put(this ITransport> transport, string path, PostData data, RequestParameters parameters) + where TResponse : TransportResponse, new() => + transport.Request(ToEndpointPath(PUT, path, parameters, transport.Configuration), data, openTelemetryData: default, null, null); + + /// Perform a PUT request + public static Task PutAsync(this ITransport> transport, string path, PostData data, RequestParameters parameters, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() => - requestHandler.RequestAsync(HttpMethod.POST, path, data, parameters, cancellationToken); + transport.RequestAsync(ToEndpointPath(PUT, path, parameters, transport.Configuration), data, openTelemetryData: default, null, null, cancellationToken); /// Perform a PUT request - public static TResponse Put(this ITransport requestHandler, string path, PostData data, - RequestParameters? parameters = null) + public static TResponse Put(this ITransport transport, string pathAndQuery, PostData data) where TResponse : TransportResponse, new() => - requestHandler.Request(HttpMethod.PUT, path, data, parameters); + transport.Request(new EndpointPath(PUT, pathAndQuery), data, openTelemetryData: default, null, null); /// Perform a PUT request - public static Task PutAsync(this ITransport requestHandler, string path, PostData data, - RequestParameters? parameters = null, CancellationToken cancellationToken = default) + public static Task PutAsync(this ITransport transport, string pathAndQuery, PostData data, CancellationToken cancellationToken = default) + where TResponse : TransportResponse, new() => + transport.RequestAsync(new EndpointPath(PUT, pathAndQuery), data, openTelemetryData: default, null, null, cancellationToken); + + + /// Perform a DELETE request + public static TResponse Delete(this ITransport> transport, string path, RequestParameters parameters, PostData? data = null) + where TResponse : TransportResponse, new() => + transport.Request(ToEndpointPath(DELETE, path, parameters, transport.Configuration), data, openTelemetryData: default, null, null); + + /// Perform a DELETE request + public static Task DeleteAsync(this ITransport> transport, string path, RequestParameters parameters, PostData? data = null, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() => - requestHandler.RequestAsync(HttpMethod.PUT, path, data, parameters, cancellationToken); + transport.RequestAsync(ToEndpointPath(DELETE, path, parameters, transport.Configuration), data, openTelemetryData: default, null, null, cancellationToken); /// Perform a DELETE request - public static TResponse Delete(this ITransport requestHandler, string path, PostData? data = null, - RequestParameters? parameters = null) + public static TResponse Delete(this ITransport transport, string pathAndQuery, PostData? data = null) where TResponse : TransportResponse, new() => - requestHandler.Request(HttpMethod.DELETE, path, data, parameters); + transport.Request(new EndpointPath(DELETE, pathAndQuery), data, openTelemetryData: default, null, null); /// Perform a DELETE request - public static Task DeleteAsync(this ITransport requestHandler, string path, - PostData? data = null, RequestParameters? parameters = null, CancellationToken cancellationToken = default) + public static Task DeleteAsync(this ITransport transport, string pathAndQuery, PostData? data = null, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() => - requestHandler.RequestAsync(HttpMethod.DELETE, path, data, parameters, cancellationToken); + transport.RequestAsync(new EndpointPath(DELETE, pathAndQuery), data, openTelemetryData: default, null, null, cancellationToken); } diff --git a/src/Elastic.Transport/Products/DefaultProductRegistration.cs b/src/Elastic.Transport/Products/DefaultProductRegistration.cs index 03d2b17..8db6de6 100644 --- a/src/Elastic.Transport/Products/DefaultProductRegistration.cs +++ b/src/Elastic.Transport/Products/DefaultProductRegistration.cs @@ -60,7 +60,7 @@ public DefaultProductRegistration() public override MetaHeaderProvider MetaHeaderProvider => _metaHeaderProvider; /// - public override string DefaultMimeType => null; + public override string? DefaultMimeType => null; /// public override string ProductAssemblyVersion { get; } @@ -79,28 +79,28 @@ public override bool TryGetServerErrorReason(TResponse response, out return false; } - /// - public override RequestData CreateSniffRequestData(Node node, IRequestConfiguration requestConfiguration, ITransportConfiguration settings, MemoryStreamFactory memoryStreamFactory) => + /// + public override Endpoint CreateSniffEndpoint(Node node, IRequestConfiguration requestConfiguration, ITransportConfiguration settings) => throw new NotImplementedException(); /// - public override Task>> SniffAsync(IRequestInvoker requestInvoker, bool forceSsl, RequestData requestData, CancellationToken cancellationToken) => + public override Task>> SniffAsync(IRequestInvoker requestInvoker, bool forceSsl, Endpoint endpoint, RequestData requestData, CancellationToken cancellationToken) => throw new NotImplementedException(); /// - public override Tuple> Sniff(IRequestInvoker requestInvoker, bool forceSsl, RequestData requestData) => + public override Tuple> Sniff(IRequestInvoker requestInvoker, bool forceSsl, Endpoint endpoint, RequestData requestData) => throw new NotImplementedException(); - /// - public override RequestData CreatePingRequestData(Node node, RequestConfiguration requestConfiguration, ITransportConfiguration global, MemoryStreamFactory memoryStreamFactory) => + /// + public override Endpoint CreatePingEndpoint(Node node, IRequestConfiguration requestConfiguration) => throw new NotImplementedException(); /// - public override Task PingAsync(IRequestInvoker requestInvoker, RequestData pingData, CancellationToken cancellationToken) => + public override Task PingAsync(IRequestInvoker requestInvoker, Endpoint endpoint, RequestData requestData, CancellationToken cancellationToken) => throw new NotImplementedException(); /// - public override TransportResponse Ping(IRequestInvoker requestInvoker, RequestData pingData) => + public override TransportResponse Ping(IRequestInvoker requestInvoker, Endpoint endpoint, RequestData pingData) => throw new NotImplementedException(); /// diff --git a/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchErrorExtensions.cs b/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchErrorExtensions.cs index 237c7e8..fa94e63 100644 --- a/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchErrorExtensions.cs +++ b/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchErrorExtensions.cs @@ -37,7 +37,7 @@ public static bool TryGetElasticsearchServerError(this BytesResponse response, o /// /// Try to parse an Elasticsearch , this only works if - /// gives us access to + /// gives us access to /// public static bool TryGetElasticsearchServerError(this TransportResponse response, out ElasticsearchServerError serverError) { diff --git a/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs b/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs index 12dfe7b..fa9af27 100644 --- a/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs +++ b/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs @@ -84,7 +84,7 @@ public ElasticsearchProductRegistration(Type markerType) : this() public override ResponseBuilder ResponseBuilder => new ElasticsearchResponseBuilder(); /// - public override string DefaultMimeType => _clientMajorVersion.HasValue ? $"application/vnd.elasticsearch+json;compatible-with={_clientMajorVersion.Value}" : null; + public override string? DefaultMimeType => _clientMajorVersion.HasValue ? $"application/vnd.elasticsearch+json;compatible-with={_clientMajorVersion.Value}" : null; /// Exposes the path used for sniffing in Elasticsearch public const string SniffPath = "_nodes/http,settings"; @@ -119,28 +119,23 @@ public override bool TryGetServerErrorReason(TResponse response, out return e != null; } - /// - public override RequestData CreateSniffRequestData(Node node, IRequestConfiguration requestConfiguration, - ITransportConfiguration settings, - MemoryStreamFactory memoryStreamFactory - ) + //TODO remove settings dependency + /// + public override Endpoint CreateSniffEndpoint(Node node, IRequestConfiguration requestConfiguration, ITransportConfiguration settings) { var requestParameters = new DefaultRequestParameters { QueryString = { { "timeout", requestConfiguration.PingTimeout }, { "flat_settings", true } } }; var sniffPath = requestParameters.CreatePathWithQueryStrings(SniffPath, settings); - return new RequestData(HttpMethod.GET, sniffPath, null, settings, requestConfiguration, null, memoryStreamFactory, default) - { - Node = node - }; + return new Endpoint(new EndpointPath(HttpMethod.GET, sniffPath), node); } /// public override async Task>> SniffAsync(IRequestInvoker requestInvoker, - bool forceSsl, RequestData requestData, CancellationToken cancellationToken) + bool forceSsl, Endpoint endpoint, RequestData requestData, CancellationToken cancellationToken) { - var response = await requestInvoker.RequestAsync(requestData, cancellationToken) + var response = await requestInvoker.RequestAsync(endpoint, requestData, null, cancellationToken) .ConfigureAwait(false); var nodes = response.ToNodes(forceSsl); return Tuple.Create>(response, @@ -149,40 +144,29 @@ public override async Task>> /// public override Tuple> Sniff(IRequestInvoker requestInvoker, bool forceSsl, - RequestData requestData) + Endpoint endpoint, RequestData requestData) { - var response = requestInvoker.Request(requestData); + var response = requestInvoker.Request(endpoint, requestData, null); var nodes = response.ToNodes(forceSsl); return Tuple.Create>(response, new ReadOnlyCollection(nodes.ToArray())); } - /// - public override RequestData CreatePingRequestData(Node node, RequestConfiguration requestConfiguration, - ITransportConfiguration global, - MemoryStreamFactory memoryStreamFactory - ) - { - var data = new RequestData(HttpMethod.HEAD, string.Empty, null, global, requestConfiguration, null, memoryStreamFactory, default) - { - Node = node - }; - - return data; - } + /// + public override Endpoint CreatePingEndpoint(Node node, IRequestConfiguration requestConfiguration) => + new(new EndpointPath(HttpMethod.HEAD, string.Empty), node); /// - public override async Task PingAsync(IRequestInvoker requestInvoker, RequestData pingData, - CancellationToken cancellationToken) + public override async Task PingAsync(IRequestInvoker requestInvoker, Endpoint endpoint, RequestData requestData, CancellationToken cancellationToken) { - var response = await requestInvoker.RequestAsync(pingData, cancellationToken).ConfigureAwait(false); + var response = await requestInvoker.RequestAsync(endpoint, requestData, null, cancellationToken).ConfigureAwait(false); return response; } /// - public override TransportResponse Ping(IRequestInvoker requestInvoker, RequestData pingData) + public override TransportResponse Ping(IRequestInvoker requestInvoker, Endpoint endpoint, RequestData pingData) { - var response = requestInvoker.Request(pingData); + var response = requestInvoker.Request(endpoint, pingData, null); return response; } diff --git a/src/Elastic.Transport/Products/ProductRegistration.cs b/src/Elastic.Transport/Products/ProductRegistration.cs index da2c949..d2eed60 100644 --- a/src/Elastic.Transport/Products/ProductRegistration.cs +++ b/src/Elastic.Transport/Products/ProductRegistration.cs @@ -59,38 +59,37 @@ public abstract class ProductRegistration /// Create an instance of that describes where and how to ping see /// All the parameters of this method correspond with 's constructor /// - public abstract RequestData CreatePingRequestData(Node node, RequestConfiguration requestConfiguration, ITransportConfiguration global, MemoryStreamFactory memoryStreamFactory); + public abstract Endpoint CreatePingEndpoint(Node node, IRequestConfiguration requestConfiguration); /// /// Provide an implementation that performs the ping directly using and the - /// return by + /// return by /// - public abstract Task PingAsync(IRequestInvoker requestInvoker, RequestData pingData, CancellationToken cancellationToken); + public abstract Task PingAsync(IRequestInvoker requestInvoker, Endpoint endpoint, RequestData requestData, CancellationToken cancellationToken); /// /// Provide an implementation that performs the ping directly using and the - /// return by + /// return by /// - public abstract TransportResponse Ping(IRequestInvoker requestInvoker, RequestData pingData); + public abstract TransportResponse Ping(IRequestInvoker requestInvoker, Endpoint endpoint, RequestData pingData); /// /// Create an instance of that describes where and how to sniff the cluster using /// All the parameters of this method correspond with 's constructor /// - public abstract RequestData CreateSniffRequestData(Node node, IRequestConfiguration requestConfiguration, ITransportConfiguration settings, - MemoryStreamFactory memoryStreamFactory); + public abstract Endpoint CreateSniffEndpoint(Node node, IRequestConfiguration requestConfiguration, ITransportConfiguration settings); /// /// Provide an implementation that performs the sniff directly using and the - /// return by + /// return by /// - public abstract Task>> SniffAsync(IRequestInvoker requestInvoker, bool forceSsl, RequestData requestData, CancellationToken cancellationToken); + public abstract Task>> SniffAsync(IRequestInvoker requestInvoker, bool forceSsl, Endpoint endpoint, RequestData requestData, CancellationToken cancellationToken); /// /// Provide an implementation that performs the sniff directly using and the - /// return by + /// return by /// - public abstract Tuple> Sniff(IRequestInvoker requestInvoker, bool forceSsl, RequestData requestData); + public abstract Tuple> Sniff(IRequestInvoker requestInvoker, bool forceSsl, Endpoint endpoint, RequestData requestData); /// Allows certain nodes to be queried first to obtain sniffing information public abstract int SniffOrder(Node node); diff --git a/src/Elastic.Transport/Requests/Body/PostData.ByteArray.cs b/src/Elastic.Transport/Requests/Body/PostData.ByteArray.cs index 9843d80..99089ef 100644 --- a/src/Elastic.Transport/Requests/Body/PostData.ByteArray.cs +++ b/src/Elastic.Transport/Requests/Body/PostData.ByteArray.cs @@ -24,34 +24,33 @@ protected internal PostDataByteArray(byte[] item) Type = PostType.ByteArray; } - public override void Write(Stream writableStream, ITransportConfiguration settings) + public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) { if (WrittenBytes == null) return; - var stream = InitWrite(writableStream, settings, out var buffer, out var disableDirectStreaming); + MemoryStream? buffer = null; if (!disableDirectStreaming) - stream.Write(WrittenBytes, 0, WrittenBytes.Length); + writableStream.Write(WrittenBytes, 0, WrittenBytes.Length); else buffer = settings.MemoryStreamFactory.Create(WrittenBytes); - FinishStream(writableStream, buffer, settings); + FinishStream(writableStream, buffer, disableDirectStreaming); } - public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, - CancellationToken cancellationToken) + public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) { if (WrittenBytes == null) return; - var stream = InitWrite(writableStream, settings, out var buffer, out var disableDirectStreaming); + MemoryStream? buffer = null; if (!disableDirectStreaming) - await stream.WriteAsync(WrittenBytes, 0, WrittenBytes.Length, cancellationToken) + await writableStream.WriteAsync(WrittenBytes, 0, WrittenBytes.Length, cancellationToken) .ConfigureAwait(false); else buffer = settings.MemoryStreamFactory.Create(WrittenBytes); - await FinishStreamAsync(writableStream, buffer, settings, cancellationToken).ConfigureAwait(false); + await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Elastic.Transport/Requests/Body/PostData.MultiJson.cs b/src/Elastic.Transport/Requests/Body/PostData.MultiJson.cs index 6dd3341..b6cb7fd 100644 --- a/src/Elastic.Transport/Requests/Body/PostData.MultiJson.cs +++ b/src/Elastic.Transport/Requests/Body/PostData.MultiJson.cs @@ -27,8 +27,8 @@ public static PostData MultiJson(IEnumerable listOfSerializables) => private class PostDataMultiJson : PostData { - private readonly IEnumerable _enumerableOfObject; - private readonly IEnumerable _enumerableOfStrings; + private readonly IEnumerable? _enumerableOfObject; + private readonly IEnumerable? _enumerableOfStrings; protected internal PostDataMultiJson(IEnumerable item) { @@ -42,13 +42,13 @@ protected internal PostDataMultiJson(IEnumerable item) Type = PostType.EnumerableOfObject; } - public override void Write(Stream writableStream, ITransportConfiguration settings) + public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) { if (Type != PostType.EnumerableOfObject && Type != PostType.EnumerableOfString) throw new Exception( $"{nameof(PostDataMultiJson)} only does not support {nameof(PostType)}.{Type.GetStringValue()}"); - var stream = InitWrite(writableStream, settings, out var buffer, out _); + MemoryStream? buffer = null; switch (Type) { @@ -60,12 +60,12 @@ public override void Write(Stream writableStream, ITransportConfiguration settin if (!enumerator.MoveNext()) return; - BufferIfNeeded(settings, ref buffer, ref stream); + BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref writableStream); do { var bytes = enumerator.Current.Utf8Bytes(); - stream.Write(bytes, 0, bytes.Length); - stream.Write(NewLineByteArray, 0, 1); + writableStream.Write(bytes, 0, bytes.Length); + writableStream.Write(NewLineByteArray, 0, 1); } while (enumerator.MoveNext()); break; @@ -78,12 +78,12 @@ public override void Write(Stream writableStream, ITransportConfiguration settin if (!enumerator.MoveNext()) return; - BufferIfNeeded(settings, ref buffer, ref stream); + BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref writableStream); do { var o = enumerator.Current; - settings.RequestResponseSerializer.Serialize(o, stream, SerializationFormatting.None); - stream.Write(NewLineByteArray, 0, 1); + settings.RequestResponseSerializer.Serialize(o, writableStream, SerializationFormatting.None); + writableStream.Write(NewLineByteArray, 0, 1); } while (enumerator.MoveNext()); break; @@ -92,18 +92,16 @@ public override void Write(Stream writableStream, ITransportConfiguration settin throw new ArgumentOutOfRangeException(); } - FinishStream(writableStream, buffer, settings); + FinishStream(writableStream, buffer, disableDirectStreaming); } - public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, - CancellationToken cancellationToken) + public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) { if (Type != PostType.EnumerableOfObject && Type != PostType.EnumerableOfString) throw new Exception( $"{nameof(PostDataMultiJson)} only does not support {nameof(PostType)}.{Type.GetStringValue()}"); - var stream = InitWrite(writableStream, settings, out var buffer, out _); - + MemoryStream? buffer = null; switch (Type) { case PostType.EnumerableOfString: @@ -115,12 +113,12 @@ public override async Task WriteAsync(Stream writableStream, ITransportConfigura if (!enumerator.MoveNext()) return; - BufferIfNeeded(settings, ref buffer, ref stream); + BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref writableStream); do { var bytes = enumerator.Current.Utf8Bytes(); - await stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); - await stream.WriteAsync(NewLineByteArray, 0, 1, cancellationToken).ConfigureAwait(false); + await writableStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + await writableStream.WriteAsync(NewLineByteArray, 0, 1, cancellationToken).ConfigureAwait(false); } while (enumerator.MoveNext()); break; @@ -134,14 +132,14 @@ public override async Task WriteAsync(Stream writableStream, ITransportConfigura if (!enumerator.MoveNext()) return; - BufferIfNeeded(settings, ref buffer, ref stream); + BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref writableStream); do { var o = enumerator.Current; - await settings.RequestResponseSerializer.SerializeAsync(o, stream, + await settings.RequestResponseSerializer.SerializeAsync(o, writableStream, SerializationFormatting.None, cancellationToken) .ConfigureAwait(false); - await stream.WriteAsync(NewLineByteArray, 0, 1, cancellationToken).ConfigureAwait(false); + await writableStream.WriteAsync(NewLineByteArray, 0, 1, cancellationToken).ConfigureAwait(false); } while (enumerator.MoveNext()); break; @@ -150,7 +148,7 @@ await settings.RequestResponseSerializer.SerializeAsync(o, stream, throw new ArgumentOutOfRangeException(); } - await FinishStreamAsync(writableStream, buffer, settings, cancellationToken).ConfigureAwait(false); + await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Elastic.Transport/Requests/Body/PostData.ReadOnlyMemory.cs b/src/Elastic.Transport/Requests/Body/PostData.ReadOnlyMemory.cs index 797b980..99e2c9f 100644 --- a/src/Elastic.Transport/Requests/Body/PostData.ReadOnlyMemory.cs +++ b/src/Elastic.Transport/Requests/Body/PostData.ReadOnlyMemory.cs @@ -27,38 +27,37 @@ protected internal PostDataReadOnlyMemory(ReadOnlyMemory item) Type = PostType.ReadOnlyMemory; } - public override void Write(Stream writableStream, ITransportConfiguration settings) + public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) { if (_memoryOfBytes.IsEmpty) return; - var stream = InitWrite(writableStream, settings, out var buffer, out var disableDirectStreaming); + MemoryStream? buffer = null; if (!disableDirectStreaming) - stream.Write(_memoryOfBytes.Span); + writableStream.Write(_memoryOfBytes.Span); else { WrittenBytes ??= _memoryOfBytes.Span.ToArray(); buffer = settings.MemoryStreamFactory.Create(WrittenBytes); } - FinishStream(writableStream, buffer, settings); + FinishStream(writableStream, buffer, disableDirectStreaming); } - public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, - CancellationToken cancellationToken) + public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) { if (_memoryOfBytes.IsEmpty) return; - var stream = InitWrite(writableStream, settings, out var buffer, out var disableDirectStreaming); + MemoryStream? buffer = null; if (!disableDirectStreaming) - stream.Write(_memoryOfBytes.Span); + writableStream.Write(_memoryOfBytes.Span); else { WrittenBytes ??= _memoryOfBytes.Span.ToArray(); buffer = settings.MemoryStreamFactory.Create(WrittenBytes); } - await FinishStreamAsync(writableStream, buffer, settings, cancellationToken).ConfigureAwait(false); + await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Elastic.Transport/Requests/Body/PostData.Serializable.cs b/src/Elastic.Transport/Requests/Body/PostData.Serializable.cs index 54dc048..17a1d40 100644 --- a/src/Elastic.Transport/Requests/Body/PostData.Serializable.cs +++ b/src/Elastic.Transport/Requests/Body/PostData.Serializable.cs @@ -27,33 +27,32 @@ public SerializableData(T item) _serializable = item; } - public static implicit operator SerializableData(T serializableData) => - new SerializableData(serializableData); + public static implicit operator SerializableData(T serializableData) => new(serializableData); - public override void Write(Stream writableStream, ITransportConfiguration settings) + public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) { MemoryStream buffer = null; var stream = writableStream; - BufferIfNeeded(settings, ref buffer, ref stream); + BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref stream); var indent = settings.PrettyJson ? Indented : None; settings.RequestResponseSerializer.Serialize(_serializable, stream, indent); - FinishStream(writableStream, buffer, settings); + FinishStream(writableStream, buffer, disableDirectStreaming); } - public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, CancellationToken cancellationToken) + public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) { MemoryStream buffer = null; var stream = writableStream; - BufferIfNeeded(settings, ref buffer, ref stream); + BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref stream); var indent = settings.PrettyJson ? Indented : None; await settings.RequestResponseSerializer .SerializeAsync(_serializable, stream, indent, cancellationToken) .ConfigureAwait(false); - await FinishStreamAsync(writableStream, buffer, settings, cancellationToken).ConfigureAwait(false); + await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Elastic.Transport/Requests/Body/PostData.Streamable.cs b/src/Elastic.Transport/Requests/Body/PostData.Streamable.cs index ab31659..31032a0 100644 --- a/src/Elastic.Transport/Requests/Body/PostData.Streamable.cs +++ b/src/Elastic.Transport/Requests/Body/PostData.Streamable.cs @@ -46,23 +46,22 @@ public StreamableData(T state, Action syncWriter, Type = PostType.StreamHandler; } - public override void Write(Stream writableStream, ITransportConfiguration settings) + public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) { MemoryStream buffer = null; var stream = writableStream; - BufferIfNeeded(settings, ref buffer, ref stream); + BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref stream); _syncWriter(_state, stream); - FinishStream(writableStream, buffer, settings); + FinishStream(writableStream, buffer, disableDirectStreaming); } - public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, - CancellationToken cancellationToken) + public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) { MemoryStream buffer = null; var stream = writableStream; - BufferIfNeeded(settings, ref buffer, ref stream); + BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref stream); await _asyncWriter(_state, stream, cancellationToken).ConfigureAwait(false); - await FinishStreamAsync(writableStream, buffer, settings, cancellationToken).ConfigureAwait(false); + await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Elastic.Transport/Requests/Body/PostData.String.cs b/src/Elastic.Transport/Requests/Body/PostData.String.cs index a5c096a..fb1fad1 100644 --- a/src/Elastic.Transport/Requests/Body/PostData.String.cs +++ b/src/Elastic.Transport/Requests/Body/PostData.String.cs @@ -33,38 +33,37 @@ protected internal PostDataString(string item) Type = PostType.LiteralString; } - public override void Write(Stream writableStream, ITransportConfiguration settings) + public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) { if (string.IsNullOrEmpty(_literalString)) return; - var stream = InitWrite(writableStream, settings, out var buffer, out var disableDirectStreaming); + MemoryStream? buffer = null; var stringBytes = WrittenBytes ?? _literalString.Utf8Bytes(); WrittenBytes ??= stringBytes; if (!disableDirectStreaming) - stream.Write(stringBytes, 0, stringBytes.Length); + writableStream.Write(stringBytes, 0, stringBytes.Length); else buffer = settings.MemoryStreamFactory.Create(stringBytes); - FinishStream(writableStream, buffer, settings); + FinishStream(writableStream, buffer, disableDirectStreaming); } - public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, - CancellationToken cancellationToken) + public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(_literalString)) return; - var stream = InitWrite(writableStream, settings, out var buffer, out var disableDirectStreaming); + MemoryStream? buffer = null; var stringBytes = WrittenBytes ?? _literalString.Utf8Bytes(); WrittenBytes ??= stringBytes; if (!disableDirectStreaming) - await stream.WriteAsync(stringBytes, 0, stringBytes.Length, cancellationToken) + await writableStream.WriteAsync(stringBytes, 0, stringBytes.Length, cancellationToken) .ConfigureAwait(false); else buffer = settings.MemoryStreamFactory.Create(stringBytes); - await FinishStreamAsync(writableStream, buffer, settings, cancellationToken).ConfigureAwait(false); + await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Elastic.Transport/Requests/Body/PostData.cs b/src/Elastic.Transport/Requests/Body/PostData.cs index 9e59775..b354136 100644 --- a/src/Elastic.Transport/Requests/Body/PostData.cs +++ b/src/Elastic.Transport/Requests/Body/PostData.cs @@ -33,21 +33,15 @@ public abstract partial class PostData // ReSharper disable once MemberCanBePrivate.Global protected static readonly byte[] NewLineByteArray = {(byte) '\n'}; - /// - /// By setting this to true, and will buffer the data and - /// expose it on - /// - public bool? DisableDirectStreaming { get; set; } - /// Reports the data this instance is wrapping // ReSharper disable once MemberCanBeProtected.Global public PostType Type { get; private set; } /// - /// If is set to true, this will hold the buffered data after + /// If is set to true, this will hold the buffered data after /// or is called /// - public byte[] WrittenBytes { get; private set; } + public byte[]? WrittenBytes { get; private set; } /// A static instance that represents a body with no data // ReSharper disable once UnusedMember.Global @@ -57,14 +51,13 @@ public abstract partial class PostData /// Implementations of are expected to implement writing the data they hold to /// /// - public abstract void Write(Stream writableStream, ITransportConfiguration settings); + public abstract void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming); /// /// Implementations of are expected to implement writing the data they hold to /// /// - public abstract Task WriteAsync(Stream writableStream, ITransportConfiguration settings, - CancellationToken cancellationToken); + public abstract Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken); /// /// byte[] implicitly converts to so you do not have to use the static @@ -72,40 +65,27 @@ public abstract Task WriteAsync(Stream writableStream, ITransportConfiguration s /// public static implicit operator PostData(byte[] byteArray) => Bytes(byteArray); - /// Sets up the stream and buffer and determines if direct streaming should be disabled - // ReSharper disable once MemberCanBePrivate.Global - protected Stream InitWrite(Stream writableStream, ITransportConfiguration settings, out MemoryStream buffer, - out bool disableDirectStreaming) - { - buffer = null; - var stream = writableStream; - disableDirectStreaming = DisableDirectStreaming ?? settings.DisableDirectStreaming; - return stream; - } - - /// - /// Based on or this will swap - /// with after allocating . - /// NOTE: is expected to be null when called and may be null when this method returns + /// Buffers the stream if direct streaming is disabled. /// - protected void BufferIfNeeded(ITransportConfiguration settings, ref MemoryStream buffer, - ref Stream stream) + /// Factory to create memory streams. + /// Flag indicating whether direct streaming is disabled. + /// Reference to the buffer memory stream. + /// Reference to the stream to write to. + protected void BufferIfNeeded(MemoryStreamFactory memoryStreamFactory, bool disableDirectStreaming, ref MemoryStream buffer, ref Stream stream) { - var disableDirectStreaming = DisableDirectStreaming ?? settings.DisableDirectStreaming; if (!disableDirectStreaming) return; - buffer = settings.MemoryStreamFactory.Create(); + buffer = memoryStreamFactory.Create(); stream = buffer; } /// /// Implementation of may call this to make sure makes it to - /// if or request to buffer the data. + /// if requests to buffer the data. /// - protected void FinishStream(Stream writableStream, MemoryStream buffer, ITransportConfiguration settings) + protected void FinishStream(Stream writableStream, MemoryStream? buffer, bool disableDirectStreaming) { - var disableDirectStreaming = DisableDirectStreaming ?? settings.DisableDirectStreaming; if (buffer == null || !disableDirectStreaming) return; buffer.Position = 0; @@ -116,7 +96,7 @@ protected void FinishStream(Stream writableStream, MemoryStream buffer, ITranspo /// /// Implementation of may call this to make sure makes it to - /// if or request to buffer the data. + /// if requests to buffer the data. /// protected async #if !NETSTANDARD2_0 && !NETFRAMEWORK @@ -124,10 +104,8 @@ protected async #else Task #endif - FinishStreamAsync(Stream writableStream, MemoryStream buffer, ITransportConfiguration settings, - CancellationToken ctx) + FinishStreamAsync(Stream writableStream, MemoryStream? buffer, bool disableDirectStreaming, CancellationToken ctx) { - var disableDirectStreaming = DisableDirectStreaming ?? settings.DisableDirectStreaming; if (buffer == null || !disableDirectStreaming) return; buffer.Position = 0; diff --git a/src/Elastic.Transport/Requests/MetaData/DefaultMetaHeaderProvider.cs b/src/Elastic.Transport/Requests/MetaData/DefaultMetaHeaderProvider.cs index c484b1f..5b2ff5d 100644 --- a/src/Elastic.Transport/Requests/MetaData/DefaultMetaHeaderProvider.cs +++ b/src/Elastic.Transport/Requests/MetaData/DefaultMetaHeaderProvider.cs @@ -58,24 +58,18 @@ public DefaultMetaHeaderProducer(VersionInfo versionInfo, string serviceIdentifi _syncMetaDataHeader = new MetaDataHeader(versionInfo, serviceIdentifier, false); } - /// - /// - /// + /// public override string HeaderName => MetaHeaderName; - /// - /// - /// - /// - /// - public override string ProduceHeaderValue(RequestData requestData) + /// + public override string? ProduceHeaderValue(RequestData requestData, bool isAsync) { try { if (requestData.ConnectionSettings.DisableMetaHeader) return null; - var headerValue = requestData.IsAsync + var headerValue = isAsync ? _asyncMetaDataHeader.ToString() : _syncMetaDataHeader.ToString(); diff --git a/src/Elastic.Transport/Requests/MetaData/MetaHeaderProvider.cs b/src/Elastic.Transport/Requests/MetaData/MetaHeaderProvider.cs index 6892d39..4d740b0 100644 --- a/src/Elastic.Transport/Requests/MetaData/MetaHeaderProvider.cs +++ b/src/Elastic.Transport/Requests/MetaData/MetaHeaderProvider.cs @@ -28,5 +28,5 @@ public abstract class MetaHeaderProducer /// /// Produces the header value based on current outgoing . /// - public abstract string ProduceHeaderValue(RequestData requestData); + public abstract string? ProduceHeaderValue(RequestData requestData, bool isAsync); } diff --git a/src/Elastic.Transport/Requests/RequestParameters.cs b/src/Elastic.Transport/Requests/RequestParameters.cs index 21e4dc6..0bdeafb 100644 --- a/src/Elastic.Transport/Requests/RequestParameters.cs +++ b/src/Elastic.Transport/Requests/RequestParameters.cs @@ -28,13 +28,6 @@ public interface IStringable /// public sealed class DefaultRequestParameters : RequestParameters; -/// -/// -/// -public static class RequestParameterExtensions -{ -} - /// /// Used by the raw client to compose querystring parameters in a matter that still exposes some xmldocs /// You can always pass a simple NameValueCollection if you want. @@ -92,7 +85,7 @@ private void RemoveQueryString(string name) } /// - public virtual string CreatePathWithQueryStrings(string? path, ITransportConfiguration global) + public virtual string CreatePathWithQueryStrings(string? path, ITransportConfiguration? global) { path ??= string.Empty; if (path.Contains("?")) @@ -107,7 +100,7 @@ public virtual string CreatePathWithQueryStrings(string? path, ITransportConfigu var nv = g == null ? new NameValueCollection() : new NameValueCollection(g); //set all querystring pairs from local `l` on the querystring collection - var formatter = global.UrlFormatter; + var formatter = global?.UrlFormatter; nv.UpdateFromDictionary(l, formatter); //if nv has no keys simply return path as provided diff --git a/src/Elastic.Transport/Responses/ResponseStatics.cs b/src/Elastic.Transport/Responses/ResponseStatics.cs index 0ab4049..f576161 100644 --- a/src/Elastic.Transport/Responses/ResponseStatics.cs +++ b/src/Elastic.Transport/Responses/ResponseStatics.cs @@ -31,7 +31,7 @@ public static string DebugInformationBuilder(ApiCallDetails r, StringBuilder sb) var auditTrail = (r.AuditTrail ?? Enumerable.Empty()).ToList(); - if (!r.TransportConfiguration.DisableAuditTrail) + if (!r.TransportConfiguration.DisableAuditTrail ?? true) { DebugAuditTrail(auditTrail, sb); } @@ -42,7 +42,7 @@ public static string DebugInformationBuilder(ApiCallDetails r, StringBuilder sb) if (r.OriginalException != null) sb.AppendLine($"# OriginalException: {r.OriginalException}"); - if (!r.TransportConfiguration.DisableAuditTrail) + if (!r.TransportConfiguration.DisableAuditTrail ?? true) DebugAuditTrailExceptions(auditTrail, sb); var response = r.ResponseBodyInBytes?.Utf8String() ?? ResponseAlreadyCaptured; diff --git a/tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs b/tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs index 5a57c7d..6b50ea5 100644 --- a/tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs +++ b/tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs @@ -14,10 +14,12 @@ public class SecurityClusterTests : IntegrationTestBase { public SecurityClusterTests(SecurityCluster cluster, ITestOutputHelper output) : base(cluster, output) { } + private static readonly EndpointPath Root = new(GET, "/"); + [Fact] public async Task AsyncRequestDoesNotThrow() { - var response = await RequestHandler.RequestAsync(GET, "/"); + var response = await RequestHandler.RequestAsync(Root); response.ApiCallDetails.Should().NotBeNull(); response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeTrue(); } @@ -25,7 +27,7 @@ public async Task AsyncRequestDoesNotThrow() [Fact] public void SyncRequestDoesNotThrow() { - var response = RequestHandler.Request(GET, "/"); + var response = RequestHandler.Request(Root); response.ApiCallDetails.Should().NotBeNull(); response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeTrue(); } @@ -33,11 +35,10 @@ public void SyncRequestDoesNotThrow() [Fact] public void SyncRequestDoesNotThrowOnBadAuth() { - var response = RequestHandler.Request(GET, "/", null, - new DefaultRequestParameters(), + var response = RequestHandler.Request(Root, null, new RequestConfiguration { - AuthenticationHeader = new BasicAuthentication("unknown-user", "bad-password") + Authentication = new BasicAuthentication("unknown-user", "bad-password") } ); response.ApiCallDetails.Should().NotBeNull(); diff --git a/tests/Elastic.Transport.IntegrationTests/Http/StreamResponseTests.cs b/tests/Elastic.Transport.IntegrationTests/Http/StreamResponseTests.cs index 7ec6720..2b4084c 100644 --- a/tests/Elastic.Transport.IntegrationTests/Http/StreamResponseTests.cs +++ b/tests/Elastic.Transport.IntegrationTests/Http/StreamResponseTests.cs @@ -106,11 +106,9 @@ public async Task WhenNoContent_MemoryStreamShouldBeDisposed() // We expect one for sending the request payload, but as the response is 204, we shouldn't // see other memory streams being created for the response. - memoryStreamFactory.Created.Count.Should().Be(1); + memoryStreamFactory.Created.Count.Should().Be(2); foreach (var memoryStream in memoryStreamFactory.Created) - { memoryStream.IsDisposed.Should().BeTrue(); - } } [Fact] diff --git a/tests/Elastic.Transport.IntegrationTests/Http/TransferEncodingChunckedTests.cs b/tests/Elastic.Transport.IntegrationTests/Http/TransferEncodingChunckedTests.cs index 1b5487f..e167fcf 100644 --- a/tests/Elastic.Transport.IntegrationTests/Http/TransferEncodingChunckedTests.cs +++ b/tests/Elastic.Transport.IntegrationTests/Http/TransferEncodingChunckedTests.cs @@ -63,7 +63,7 @@ [Fact] public async Task HttpClientUseProxyShouldBeFalseWhenDisabledAutoProxyDet requestInvoker.LastHttpClientHandler.UseProxy.Should().BeFalse(); r.Body.Should().Be(BodyString); - r = await transport.PostAsync(Path, Body, null, CancellationToken.None); + r = await transport.PostAsync(Path, Body, CancellationToken.None); requestInvoker.LastHttpClientHandler.UseProxy.Should().BeFalse(); r.Body.Should().Be(BodyString); } @@ -75,7 +75,7 @@ [Fact] public async Task HttpClientUseProxyShouldBeTrueWhenEnabledAutoProxyDetec transport.Post(Path, Body); requestInvoker.LastHttpClientHandler.UseProxy.Should().BeTrue(); - await transport.PostAsync(Path, Body, null, CancellationToken.None); + await transport.PostAsync(Path, Body, CancellationToken.None); requestInvoker.LastHttpClientHandler.UseProxy.Should().BeTrue(); } @@ -88,7 +88,7 @@ [Fact] public async Task HttpClientUseTransferEncodingChunkedWhenTransferEncodin var transport = Setup(requestInvoker, transferEncodingChunked: true); transport.Post(Path, Body); - await transport.PostAsync(Path, Body, null, CancellationToken.None); + await transport.PostAsync(Path, Body, CancellationToken.None); } [Fact] public async Task HttpClientSetsContentLengthWhenTransferEncodingChunkedFalse() @@ -100,7 +100,7 @@ [Fact] public async Task HttpClientSetsContentLengthWhenTransferEncodingChunkedF var transport = Setup(trackingRequestInvoker, transferEncodingChunked: false); transport.Post(Path, Body); - await transport.PostAsync(Path, Body, null, CancellationToken.None); + await transport.PostAsync(Path, Body, CancellationToken.None); } [Fact] public async Task HttpClientSetsContentLengthWhenTransferEncodingChunkedHttpCompression() @@ -112,7 +112,7 @@ [Fact] public async Task HttpClientSetsContentLengthWhenTransferEncodingChunkedH var transport = Setup(trackingRequestInvoker, transferEncodingChunked: false, httpCompression: true); transport.Post(Path, Body); - await transport.PostAsync(Path, Body, null, CancellationToken.None); + await transport.PostAsync(Path, Body, CancellationToken.None); } } } diff --git a/tests/Elastic.Transport.IntegrationTests/Plumbing/Stubs/TrackingRequestInvoker.cs b/tests/Elastic.Transport.IntegrationTests/Plumbing/Stubs/TrackingRequestInvoker.cs index fce599b..7a05aea 100644 --- a/tests/Elastic.Transport.IntegrationTests/Plumbing/Stubs/TrackingRequestInvoker.cs +++ b/tests/Elastic.Transport.IntegrationTests/Plumbing/Stubs/TrackingRequestInvoker.cs @@ -26,18 +26,18 @@ public TrackingRequestInvoker() => return _handler; }); - public TResponse Request(RequestData requestData) + public TResponse Request(Endpoint endpoint, RequestData requestData, PostData postData) where TResponse : TransportResponse, new() { CallCount++; - return _requestInvoker.Request(requestData); + return _requestInvoker.Request(endpoint, requestData, postData); } - public Task RequestAsync(RequestData requestData, CancellationToken cancellationToken) + public Task RequestAsync(Endpoint endpoint, RequestData requestData, PostData postData, CancellationToken cancellationToken) where TResponse : TransportResponse, new() { CallCount++; - return _requestInvoker.RequestAsync(requestData, cancellationToken); + return _requestInvoker.RequestAsync(endpoint, requestData, postData, cancellationToken); } public void Dispose() diff --git a/tests/Elastic.Transport.Tests/Components/Serialization/LowLevelRequestResponseSerializerTests.cs b/tests/Elastic.Transport.Tests/Components/Serialization/LowLevelRequestResponseSerializerTests.cs index 64ee39a..47635e8 100644 --- a/tests/Elastic.Transport.Tests/Components/Serialization/LowLevelRequestResponseSerializerTests.cs +++ b/tests/Elastic.Transport.Tests/Components/Serialization/LowLevelRequestResponseSerializerTests.cs @@ -56,16 +56,17 @@ public void SerializesException() source.ValueKind.Should().Be(JsonValueKind.String); source.GetString().Should().Be("Elastic.Transport.Tests"); - var windowsPath = "Components\\Serialization\\LowLevelRequestResponseSerializerTests.cs:line"; + var windowsPath = "Components\\Serialization\\LowLevelRequestResponseSerializerTests.cs"; var path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windowsPath : windowsPath.Replace('\\', '/'); exception.TryGetProperty("StackTraceString", out var stackTrace).Should().BeTrue(); stackTrace.ValueKind.Should().Be(JsonValueKind.String); - stackTrace.GetString().Should() + var stackTraceString = stackTrace.GetString(); + stackTraceString.Should() .Contain("at Elastic.Transport.Tests.Components.Serialization.LowLevelRequestResponseSerializerTests") - .And.Contain(path); + .And.Contain(path, stackTraceString); exception.TryGetProperty("HResult", out var hResult).Should().BeTrue(); hResult.ValueKind.Should().Be(JsonValueKind.Number); @@ -77,5 +78,4 @@ public void SerializesException() } } -internal class CustomException(string message) : Exception(message) -{ } +internal class CustomException(string message) : Exception(message); diff --git a/tests/Elastic.Transport.Tests/OpenTelemetryTests.cs b/tests/Elastic.Transport.Tests/OpenTelemetryTests.cs index d60e709..fd9eb5d 100644 --- a/tests/Elastic.Transport.Tests/OpenTelemetryTests.cs +++ b/tests/Elastic.Transport.Tests/OpenTelemetryTests.cs @@ -149,7 +149,7 @@ private async Task TestCoreAsync(Action assertions, OpenTelemetryData transport ??= new DistributedTransport(InMemoryConnectionFactory.Create()); - _ = await transport.RequestAsync(HttpMethod.GET, "/", null, null, openTelemetryData, null, null, default); + _ = await transport.RequestAsync(new EndpointPath(HttpMethod.GET, "/"), null, openTelemetryData, null, null, default); mre.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue(); } diff --git a/tests/Elastic.Transport.Tests/ResponseBuilderDisposeTests.cs b/tests/Elastic.Transport.Tests/ResponseBuilderDisposeTests.cs index 8b79c6a..aacd376 100644 --- a/tests/Elastic.Transport.Tests/ResponseBuilderDisposeTests.cs +++ b/tests/Elastic.Transport.Tests/ResponseBuilderDisposeTests.cs @@ -66,27 +66,19 @@ private async Task AssertResponse(bool disableDirectStreaming, int statusCode ITransportConfiguration config; if (skipStatusCode > -1 ) - { config = InMemoryConnectionFactory.Create(productRegistration) .DisableDirectStreaming(disableDirectStreaming) .SkipDeserializationForStatusCodes(skipStatusCode); - } else if (productRegistration is not null) - { config = InMemoryConnectionFactory.Create(productRegistration) .DisableDirectStreaming(disableDirectStreaming); - } else - { config = disableDirectStreaming ? _settingsDisableDirectStream : _settings; - } var memoryStreamFactory = new TrackMemoryStreamFactory(); - - var requestData = new RequestData(httpMethod, "/", null, config, null, customResponseBuilder, memoryStreamFactory, default) - { - Node = new Node(new Uri("http://localhost:9200")) - }; + + var endpoint = new Endpoint(new EndpointPath(httpMethod, "/"), new Node(new Uri("http://localhost:9200"))); + var requestData = new RequestData(config, null, customResponseBuilder, memoryStreamFactory); var stream = new TrackDisposeStream(); @@ -96,7 +88,7 @@ private async Task AssertResponse(bool disableDirectStreaming, int statusCode stream.Position = 0; } - var response = config.ProductRegistration.ResponseBuilder.ToResponse(requestData, null, statusCode, null, stream, mimeType, contentLength, null, null); + var response = config.ProductRegistration.ResponseBuilder.ToResponse(endpoint, requestData, null, null, statusCode, null, stream, mimeType, contentLength, null, null); response.Should().NotBeNull(); @@ -113,7 +105,7 @@ private async Task AssertResponse(bool disableDirectStreaming, int statusCode stream = new TrackDisposeStream(); var ct = new CancellationToken(); - response = await config.ProductRegistration.ResponseBuilder.ToResponseAsync(requestData, null, statusCode, null, stream, null, contentLength, null, null, + response = await config.ProductRegistration.ResponseBuilder.ToResponseAsync(endpoint, requestData, null, null, statusCode, null, stream, null, contentLength, null, null, cancellationToken: ct); response.Should().NotBeNull(); @@ -140,19 +132,20 @@ private class TestProductRegistration : ProductRegistration public override MetaHeaderProvider MetaHeaderProvider => null; public override string ProductAssemblyVersion => "0.0.0"; public override IReadOnlyDictionary DefaultOpenTelemetryAttributes => new Dictionary(); - public override RequestData CreatePingRequestData(Node node, RequestConfiguration requestConfiguration, ITransportConfiguration global, MemoryStreamFactory memoryStreamFactory) => throw new NotImplementedException(); - public override RequestData CreateSniffRequestData(Node node, IRequestConfiguration requestConfiguration, ITransportConfiguration settings, MemoryStreamFactory memoryStreamFactory) => throw new NotImplementedException(); public override IReadOnlyCollection DefaultHeadersToParse() => []; public override bool HttpStatusCodeClassifier(HttpMethod method, int statusCode) => true; + public override ResponseBuilder ResponseBuilder => new TestErrorResponseBuilder(); + + public override Endpoint CreatePingEndpoint(Node node, IRequestConfiguration requestConfiguration) => throw new NotImplementedException(); + public override Task PingAsync(IRequestInvoker requestInvoker, Endpoint endpoint, RequestData pingData, CancellationToken cancellationToken) => throw new NotImplementedException(); + public override TransportResponse Ping(IRequestInvoker requestInvoker, Endpoint endpoint, RequestData pingData) => throw new NotImplementedException(); + public override Endpoint CreateSniffEndpoint(Node node, IRequestConfiguration requestConfiguration, ITransportConfiguration settings) => throw new NotImplementedException(); + public override Task>> SniffAsync(IRequestInvoker requestInvoker, bool forceSsl, Endpoint endpoint, RequestData requestData, CancellationToken cancellationToken) => throw new NotImplementedException(); + public override Tuple> Sniff(IRequestInvoker requestInvoker, bool forceSsl, Endpoint endpoint, RequestData requestData) => throw new NotImplementedException(); public override bool NodePredicate(Node node) => throw new NotImplementedException(); public override Dictionary ParseOpenTelemetryAttributesFromApiCallDetails(ApiCallDetails callDetails) => throw new NotImplementedException(); - public override TransportResponse Ping(IRequestInvoker requestInvoker, RequestData pingData) => throw new NotImplementedException(); - public override Task PingAsync(IRequestInvoker requestInvoker, RequestData pingData, CancellationToken cancellationToken) => throw new NotImplementedException(); - public override Tuple> Sniff(IRequestInvoker requestInvoker, bool forceSsl, RequestData requestData) => throw new NotImplementedException(); - public override Task>> SniffAsync(IRequestInvoker requestInvoker, bool forceSsl, RequestData requestData, CancellationToken cancellationToken) => throw new NotImplementedException(); public override int SniffOrder(Node node) => throw new NotImplementedException(); public override bool TryGetServerErrorReason(TResponse response, out string reason) => throw new NotImplementedException(); - public override ResponseBuilder ResponseBuilder => new TestErrorResponseBuilder(); } private class TestError : ErrorResponse