├── .ci └── make.sh ├── .editorconfig ├── .gitattributes ├── .github ├── add-license-headers.sh ├── check-license-headers.sh ├── license-header.txt └── workflows │ ├── ci.yml │ ├── license.yml │ └── test-windows.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Directory.Build.props ├── Elastic.Transport.sln ├── Elastic.Transport.sln.DotSettings ├── NOTICE.txt ├── Playground ├── Playground.csproj └── Program.cs ├── README.md ├── benchmarks ├── Elastic.Transport.Benchmarks │ ├── Elastic.Transport.Benchmarks.csproj │ ├── Program.cs │ └── TransportBenchmarks.cs └── Elastic.Transport.Profiling │ ├── Elastic.Transport.Profiling.csproj │ └── Program.cs ├── build.bat ├── build.sh ├── build ├── keys │ ├── keypair.snk │ └── public.snk └── scripts │ ├── CommandLine.fs │ ├── Paths.fs │ ├── Program.fs │ ├── Targets.fs │ └── scripts.fsproj ├── contributing.md ├── dotnet-tools.json ├── examples └── transport-aot-example │ ├── Program.cs │ └── transport-aot-example.csproj ├── global.json ├── license.txt ├── nuget-icon.png ├── nuget.config ├── request-pipeline.png ├── src ├── Directory.Build.props ├── Elastic.Transport.VirtualizedCluster │ ├── Audit │ │ ├── Auditor.cs │ │ ├── CallTraceState.cs │ │ └── ClientCall.cs │ ├── Components │ │ ├── ExposingPipelineFactory.cs │ │ ├── SealedVirtualCluster.cs │ │ ├── VirtualCluster.cs │ │ ├── VirtualClusterConnection.cs │ │ └── VirtualizedCluster.cs │ ├── Elastic.Transport.VirtualizedCluster.csproj │ ├── Extensions │ │ └── NumericExtensions.cs │ ├── Products │ │ ├── Elasticsearch │ │ │ ├── ElasticsearchMockProductRegistration.cs │ │ │ ├── ElasticsearchSniffResponseFactory.cs │ │ │ └── ElasticsearchVirtualCluster.cs │ │ └── MockProductRegistration.cs │ ├── Providers │ │ └── TestableDateTimeProvider.cs │ ├── Rules │ │ ├── ClientCallRule.cs │ │ ├── PingRule.cs │ │ ├── RuleBase.cs │ │ ├── RuleOption.cs │ │ ├── SniffRule.cs │ │ └── TimesHelper.cs │ └── Setup.cs └── Elastic.Transport │ ├── .editorconfig │ ├── Components │ ├── Endpoint.cs │ ├── NodePool │ │ ├── CloudNodePool.cs │ │ ├── Node.cs │ │ ├── NodePool.cs │ │ ├── SingleNodePool.cs │ │ ├── SniffingNodePool.cs │ │ ├── StaticNodePool.cs │ │ ├── StickyNodePool.cs │ │ └── StickySniffingNodePool.cs │ ├── Pipeline │ │ ├── BoundConfiguration.cs │ │ ├── DefaultResponseBuilder.cs │ │ ├── PipelineException.cs │ │ ├── PipelineFailure.cs │ │ ├── RequestPipeline.cs │ │ └── RequestPipelineStatics.cs │ ├── Providers │ │ ├── DateTimeProvider.cs │ │ ├── DefaultDateTimeProvider.cs │ │ ├── DefaultMemoryStreamFactory.cs │ │ ├── DefaultRequestPipelineFactory.cs │ │ ├── MemoryStreamFactory.cs │ │ ├── RecyclableMemoryStream.cs │ │ ├── RecyclableMemoryStreamFactory.cs │ │ ├── RecyclableMemoryStreamManager-Events.cs │ │ ├── RecyclableMemoryStreamManager.cs │ │ └── RequestPipelineFactory.cs │ ├── Serialization │ │ ├── Converters │ │ │ ├── DynamicDictionaryConverter.cs │ │ │ ├── ErrorCauseConverter.cs │ │ │ └── ExceptionConverter.cs │ │ ├── IJsonSerializerOptionsProvider.cs │ │ ├── JsonElementExtensions.cs │ │ ├── LowLevelRequestResponseSerializer.cs │ │ ├── SerializationFormatting.cs │ │ ├── Serializer.cs │ │ ├── SerializerRegistrationInformation.cs │ │ ├── SystemTextJsonSerializer.cs │ │ └── TransportSerializerExtensions.cs │ └── TransportClient │ │ ├── CertificateHelpers.cs │ │ ├── CertificateValidations.cs │ │ ├── Content │ │ └── RequestDataContent.cs │ │ ├── HandlerTracking │ │ ├── ActiveHandlerTrackingEntry.cs │ │ ├── ExpiredHandlerTrackingEntry.cs │ │ ├── LifetimeTrackingHttpMessageHandler.cs │ │ ├── RequestDataHttpClientFactory.cs │ │ └── ValueStopWatch.cs │ │ ├── HttpMethod.cs │ │ ├── HttpRequestInvoker-FullFramework.cs │ │ ├── HttpRequestInvoker.cs │ │ ├── HttpWebRequestInvoker.cs │ │ ├── IRequestInvoker.cs │ │ ├── InMemoryRequestInvoker.cs │ │ ├── RequestInvokerHelpers.cs │ │ └── WebProxy.cs │ ├── Configuration │ ├── ConnectionInfo.cs │ ├── HeadersList.cs │ ├── IRequestConfiguration.cs │ ├── ITransportConfiguration.cs │ ├── RequestConfiguration.cs │ ├── RequestConfigurationDescriptor.cs │ ├── Security │ │ ├── ApiKey.cs │ │ ├── AuthorizationHeader.cs │ │ ├── Base64ApiKey.cs │ │ └── BasicAuthenticationCredentials.cs │ ├── TransportConfiguration.cs │ ├── TransportConfigurationDescriptor.cs │ └── UserAgent.cs │ ├── Diagnostics │ ├── AuditDiagnosticObserver.cs │ ├── Auditing │ │ ├── Audit.cs │ │ ├── AuditEvent.cs │ │ ├── Auditable.cs │ │ └── Auditor.cs │ ├── Diagnostic.cs │ ├── DiagnosticSources.cs │ ├── HttpConnectionDiagnosticObserver.cs │ ├── OpenTelemetry │ │ ├── OpenTelemetry.cs │ │ ├── OpenTelemetryAttributes.cs │ │ └── SemanticConventions.cs │ ├── RequestPipelineDiagnosticObserver.cs │ ├── SerializerDiagnosticObserver.cs │ ├── TcpStats.cs │ ├── ThreadpoolStats.cs │ └── TypedDiagnosticObserver.cs │ ├── DistributedTransport.cs │ ├── Elastic.Transport.csproj │ ├── Exceptions │ ├── TransportException.cs │ └── UnexpectedTransportException.cs │ ├── Extensions │ ├── .editorconfig │ ├── EmptyEnumerator.cs │ ├── EmptyReadonly.cs │ ├── Extensions.cs │ ├── Fluent.cs │ ├── NameValueCollectionExtensions.cs │ ├── NativeMethods.cs │ ├── RuntimeInformation.cs │ ├── SemVersion.cs │ ├── StringExtensions.cs │ └── TaskExtensions.cs │ ├── ITransport.cs │ ├── ITransportHttpMethodExtensions.cs │ ├── IsExternalInit.cs │ ├── Products │ ├── .editorconfig │ ├── DefaultProductRegistration.cs │ ├── Elasticsearch │ │ ├── ElasticsearchConfiguration.cs │ │ ├── ElasticsearchErrorExtensions.cs │ │ ├── ElasticsearchNodeFeatures.cs │ │ ├── ElasticsearchProductRegistration.cs │ │ ├── ElasticsearchResponse.cs │ │ ├── ElasticsearchResponseBuilder.cs │ │ ├── ErrorSerializationContext.cs │ │ ├── Failures │ │ │ ├── ElasticsearchServerError.cs │ │ │ ├── Error.cs │ │ │ ├── ErrorCause.cs │ │ │ └── ShardFailure.cs │ │ └── Sniff │ │ │ ├── NodeInfo.cs │ │ │ ├── NodeInfoHttp.cs │ │ │ ├── SniffParser.cs │ │ │ └── SniffResponse.cs │ └── ProductRegistration.cs │ ├── Properties │ └── ClsCompliancy.cs │ ├── Requests │ ├── Body │ │ ├── PostData.ByteArray.cs │ │ ├── PostData.MultiJson.cs │ │ ├── PostData.ReadOnlyMemory.cs │ │ ├── PostData.Serializable.cs │ │ ├── PostData.Streamable.cs │ │ ├── PostData.String.cs │ │ ├── PostData.cs │ │ └── PostType.cs │ ├── IUrlParameter.cs │ ├── MetaData │ │ ├── DefaultMetaHeaderProvider.cs │ │ ├── MetaDataHeader.cs │ │ ├── MetaHeaderProvider.cs │ │ ├── ReflectionVersionInfo.cs │ │ ├── RequestMetaData.cs │ │ ├── RequestMetaDataExtensions.cs │ │ ├── RuntimeVersionInfo.cs │ │ └── VersionInfo.cs │ ├── RequestParameters.cs │ └── UrlFormatter.cs │ └── Responses │ ├── BufferedResponseHelpers.cs │ ├── DefaultResponseFactory.cs │ ├── Dynamic │ ├── DynamicDictionary.cs │ ├── DynamicResponse.cs │ ├── DynamicResponseBuilder.cs │ └── DynamicValue.cs │ ├── ErrorResponse.cs │ ├── HttpDetails │ └── ApiCallDetails.cs │ ├── IResponseBuilder.cs │ ├── ResponseFactory.cs │ ├── ResponseStatics.cs │ ├── Special │ ├── BytesResponse.cs │ ├── BytesResponseBuilder.cs │ ├── StreamResponse.cs │ ├── StreamResponseBase.cs │ ├── StreamResponseBuilder.cs │ ├── StringResponse.cs │ ├── StringResponseBuilder.cs │ ├── VoidResponse.cs │ └── VoidResponseBuilder.cs │ ├── TestableResponseFactory.cs │ ├── TransportResponse.cs │ └── TypedResponseBuilder.cs └── tests ├── Elastic.Elasticsearch.IntegrationTests ├── DefaultCluster.cs ├── DefaultClusterTests.cs ├── Elastic.Elasticsearch.IntegrationTests.csproj ├── IntegrationTestBase.cs └── SecurityClusterTests.cs ├── Elastic.Transport.IntegrationTests ├── Elastic.Transport.IntegrationTests.csproj ├── Http │ ├── ApiCompatibilityHeaderTests.cs │ └── TransferEncodingChunckedTests.cs ├── OpenTelemetry │ └── OpenTelemetryTests.cs ├── Plumbing │ ├── AssemblyServerTestsBase.cs │ ├── ClassServerTestsBase.cs │ ├── DefaultStartup.cs │ ├── Examples │ │ ├── ControllerIntegrationTests.cs │ │ └── EndpointIntegrationTests.cs │ ├── Stubs │ │ ├── TestableClientHandler.cs │ │ └── TrackingRequestInvoker.cs │ ├── TransportTestServer.cs │ └── WebHostExtensions.cs └── Responses │ └── SpecialisedResponseTests.cs ├── Elastic.Transport.Tests.Shared ├── Elastic.Transport.Tests.Shared.csproj ├── TrackDisposeStream.cs └── TrackingMemoryStreamFactory.cs └── Elastic.Transport.Tests ├── AddressParsing.cs ├── CodeStandards └── NamingConventions.doc.cs ├── Components ├── NodePool │ └── StaticNodePoolTests.cs ├── Serialization │ ├── LowLevelRequestResponseSerializerTests.cs │ └── SerializationTestBase.cs └── TransportClient │ └── RequestInvokerTests.cs ├── Configuration ├── HeadersListTests.cs ├── RequestConfigurationTests.cs └── TransportConfigurationTests.cs ├── Elastic.Transport.Tests.csproj ├── InstantiationsTests.cs ├── OpenTelemetryTests.cs ├── Plumbing ├── InMemoryConnectionFactory.cs └── TestResponse.cs ├── ResponseFactoryDisposeTests.cs ├── Responses ├── Dynamic │ └── DynamicResponseBuilderTests.cs ├── Special │ ├── BytesResponseBuilderTests.cs │ ├── StreamResponseBuilderTests.cs │ ├── StringResponseBuilderTests.cs │ └── VoidResponseBuilderTests.cs └── TestableResponseFactory.cs ├── UsageTests.cs ├── VirtualClusterTests.cs └── VolatileUpdates.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto eol=lf 3 | 4 | # Set default behavior for command prompt diff. 5 | # This gives output on command line taking C# language constructs into consideration (e.g showing class name) 6 | *.cs text diff=csharp 7 | 8 | # Set windows specific files explicitly to crlf line ending 9 | *.cmd eol=crlf 10 | *.bat eol=crlf 11 | *.ps1 eol=crlf 12 | 13 | # Mark files specifically as binary to avoid line ending conversion 14 | *.snk binary 15 | *.png binary -------------------------------------------------------------------------------- /.github/add-license-headers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | script_path=$(dirname $(realpath -s $0))/../ 3 | 4 | function add_license () { 5 | (find "$script_path" -name $1 | grep -v "/bin/" | grep -v "/obj/" )|while read fname; do 6 | line=$(sed -n '2p;3q' "$fname") 7 | if ! [[ "$line" == " * Licensed to Elasticsearch B.V. under one or more contributor" ]] ; then 8 | cat "${script_path}.github/license-header.txt" "$fname" > "${fname}.new" 9 | mv "${fname}.new" "$fname" 10 | fi 11 | done 12 | } 13 | 14 | add_license "*.cs" -------------------------------------------------------------------------------- /.github/check-license-headers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check that source code files in this repo have the appropriate license 4 | # header. 5 | 6 | if [ "$TRACE" != "" ]; then 7 | export PS4='${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 8 | set -o xtrace 9 | fi 10 | set -o errexit 11 | set -o pipefail 12 | 13 | TOP=$(cd "$(dirname "$0")/.." >/dev/null && pwd) 14 | NLINES=$(wc -l .github/license-header.txt | awk '{print $1}') 15 | 16 | function check_license_header { 17 | local f 18 | f=$1 19 | if ! diff .github/license-header.txt <(head -$NLINES "$f") >/dev/null; then 20 | echo "check-license-headers: error: '$f' does not have required license header, see 'diff -u .github/license-header.txt <(head -$NLINES $f)'" 21 | return 1 22 | else 23 | return 0 24 | fi 25 | } 26 | 27 | cd "$TOP" 28 | nErrors=0 29 | for f in $(git ls-files | grep '\.cs$'); do 30 | if ! check_license_header $f; then 31 | nErrors=$((nErrors+1)) 32 | fi 33 | done 34 | 35 | for f in $(git ls-files | grep '\.fs$'); do 36 | if ! check_license_header $f; then 37 | nErrors=$((nErrors+1)) 38 | fi 39 | done 40 | 41 | if [[ $nErrors -eq 0 ]]; then 42 | exit 0 43 | else 44 | exit 1 45 | fi 46 | -------------------------------------------------------------------------------- /.github/license-header.txt: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | name: License headers 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Check license headers 14 | run: | 15 | ./.github/check-license-headers.sh -------------------------------------------------------------------------------- /.github/workflows/test-windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows tests 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '*.md' 7 | - '*.asciidoc' 8 | - '.editorconfig' 9 | - 'docs/**' 10 | branches: 11 | - main 12 | tags: 13 | - "*.*.*" 14 | pull_request: 15 | paths-ignore: 16 | - '*.md' 17 | - '*.asciidoc' 18 | - '.editorconfig' 19 | - 'docs/**' 20 | 21 | permissions: 22 | contents: read 23 | checks: write 24 | 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.ref }} 27 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 28 | 29 | defaults: 30 | run: 31 | shell: cmd 32 | 33 | jobs: 34 | tests: 35 | runs-on: windows-2022 36 | 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | with: 41 | fetch-depth: 1 42 | 43 | - run: | 44 | git fetch --prune --unshallow --tags 45 | echo exit code $? 46 | git tag --list 47 | name: Fetch Tags 48 | 49 | - uses: actions/cache@v4 50 | name: NuGet package cache 51 | with: 52 | path: ~/.nuget/packages 53 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.[cf]sproj*') }} 54 | restore-keys: | 55 | ${{ runner.os }}-nuget 56 | 57 | - name: Setup dotnet 58 | uses: actions/setup-dotnet@v4 59 | with: 60 | global-json-file: ./global.json 61 | 62 | - run: ./build.bat build -s true 63 | name: Build 64 | 65 | - run: ./build.bat test -s true 66 | name: Test 67 | 68 | - name: Test Results 69 | if: success() || failure() 70 | uses: mikepenz/action-junit-report@v4 71 | with: 72 | report_paths: 'build/output/junit-*.xml' 73 | github_token: ${{ secrets.GITHUB_TOKEN }} 74 | fail_on_failure: true 75 | require_tests: true 76 | check_name: Test Results -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.userprefs 2 | *.local.xml 3 | *.sln.docstates 4 | *.obj 5 | *.swp 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.tss 12 | *.vspscc 13 | *_i.c 14 | *_p.c 15 | *.ncb 16 | *.suo 17 | *.tlb 18 | *.tlh 19 | *.bak 20 | *.cache 21 | *.ilk 22 | *.log 23 | *.nupkg 24 | *.ncrunchsolution 25 | [Bb]in 26 | [Dd]ebug/ 27 | test-results 28 | test-results/* 29 | *.lib 30 | *.sbr 31 | *.DotSettings.user 32 | obj/ 33 | [Rr]elease*/ 34 | _ReSharper*/ 35 | _NCrunch*/ 36 | [Tt]est[Rr]esult* 37 | 38 | .fake/* 39 | .fake 40 | packages/* 41 | paket.exe 42 | paket-files/*.cached 43 | 44 | build/* 45 | !build/tools 46 | !build/keys 47 | build/tools/* 48 | !build/tools/sn 49 | !build/tools/sn/* 50 | !build/tools/ilmerge 51 | !build/*.fsx 52 | !build/*.fsx 53 | !build/*.ps1 54 | !build/*.nuspec 55 | !build/*.png 56 | !build/*.targets 57 | !build/scripts 58 | 59 | !docs/build 60 | docs/node_modules 61 | doc/Help 62 | 63 | *.ncrunchproject 64 | Cache 65 | YamlCache 66 | tests.yaml 67 | 68 | *.DS_Store 69 | *.sln.ide 70 | 71 | launchSettings.json 72 | project.lock.json 73 | .vs 74 | .vs/* 75 | 76 | .idea/ 77 | *.sln.iml 78 | /src/.vs/restore.dg 79 | src/packages/ 80 | BenchmarkDotNet.Artifacts 81 | *.received.* 82 | 83 | /.ionide/symbolCache.db -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Location: https://www.elastic.co/community/codeofconduct -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.bat)) 6 | 7 | canary 8 | 0.1 9 | 10 | latest 11 | true 12 | False 13 | false 14 | true 15 | $(SolutionRoot)\build\keys\keypair.snk 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers 24 | 25 | 26 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Elasticsearch .NET Transport 2 | Copyright 2012-2021 Elasticsearch B.V. 3 | 4 | ========== 5 | Notice for: Microsoft.IO.RecyclableMemoryStream 6 | ---------- 7 | Based on the Microsoft.IO.RecyclableMemoryStream project by Microsoft, 8 | licensed under the MIT license. https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/ 9 | 10 | MIT License 11 | 12 | Copyright (c) 2015-2016 Microsoft 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | 32 | -------------------------------------------------------------------------------- /Playground/Playground.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Playground/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Transport; 6 | using Elastic.Transport.Products.Elasticsearch; 7 | using HttpMethod = Elastic.Transport.HttpMethod; 8 | 9 | var registration = new ElasticsearchProductRegistration(typeof(Elastic.Clients.Elasticsearch.ElasticsearchClient)); 10 | 11 | var apiKey = Environment.GetEnvironmentVariable("ELASTIC_API_KEY") ?? throw new Exception(); 12 | var url = Environment.GetEnvironmentVariable("ELASTIC_URL") ?? throw new Exception(); 13 | 14 | var configuration = new TransportConfiguration(new Uri(url), new ApiKey(apiKey), ElasticsearchProductRegistration.Default) 15 | { 16 | DebugMode = true 17 | }; 18 | var transport = new DistributedTransport(configuration); 19 | 20 | var response = transport.Request(HttpMethod.GET, "/does-not-exist"); 21 | Console.WriteLine(response.DebugInformation); 22 | 23 | var dynamicResponse = transport.Request(HttpMethod.GET, "/"); 24 | Console.WriteLine(dynamicResponse.Body.Get("version.build_flavor")); 25 | 26 | var body = PostData.String("{\"name\": \"test\"}"); 27 | var indexResponse = transport.Request(HttpMethod.POST, "/does-not-exist/_doc", body); 28 | Console.WriteLine(indexResponse.DebugInformation); 29 | 30 | Console.WriteLine(registration.DefaultContentType ?? "NOT SPECIFIED"); 31 | 32 | public class EsResponse : ElasticsearchResponse; 33 | -------------------------------------------------------------------------------- /benchmarks/Elastic.Transport.Benchmarks/Elastic.Transport.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | false 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /benchmarks/Elastic.Transport.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using BenchmarkDotNet.Columns; 6 | using BenchmarkDotNet.Configs; 7 | using BenchmarkDotNet.Diagnosers; 8 | using BenchmarkDotNet.Reports; 9 | using BenchmarkDotNet.Running; 10 | 11 | namespace Elastic.Transport.Benchmarks 12 | { 13 | internal class Program 14 | { 15 | private static void Main(string[] args) 16 | { 17 | var config = ManualConfig 18 | .Create(DefaultConfig.Instance) 19 | .AddDiagnoser(MemoryDiagnoser.Default) 20 | .WithSummaryStyle(new SummaryStyle(null, false, SizeUnit.B, null)); 21 | 22 | BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /benchmarks/Elastic.Transport.Benchmarks/TransportBenchmarks.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Threading.Tasks; 7 | using BenchmarkDotNet.Attributes; 8 | 9 | namespace Elastic.Transport.Benchmarks 10 | { 11 | public class TransportBenchmarks 12 | { 13 | private ITransport _transport; 14 | 15 | [GlobalSetup] 16 | public void Setup() 17 | { 18 | var requestInvoker = new InMemoryRequestInvoker(); 19 | var pool = new SingleNodePool(new Uri("http://localhost:9200")); 20 | var settings = new TransportConfiguration(pool, requestInvoker); 21 | 22 | _transport = new DistributedTransport(settings); 23 | } 24 | 25 | [Benchmark] 26 | public void TransportSuccessfulRequestBenchmark() => _transport.Get("/"); 27 | 28 | [Benchmark] 29 | public async Task TransportSuccessfulAsyncRequestBenchmark() => await _transport.GetAsync("/"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /benchmarks/Elastic.Transport.Profiling/Elastic.Transport.Profiling.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /benchmarks/Elastic.Transport.Profiling/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Threading.Tasks; 7 | using Elastic.Transport.Products.Elasticsearch; 8 | using JetBrains.Profiler.Api; 9 | 10 | namespace Elastic.Transport.Profiling 11 | { 12 | internal class Program 13 | { 14 | private static async Task Main() 15 | { 16 | MemoryProfiler.CollectAllocations(true); 17 | MemoryProfiler.GetSnapshot("start"); 18 | 19 | var config = new TransportConfiguration(new Uri("http://localhost:9200"), 20 | new ElasticsearchProductRegistration(typeof(ElasticsearchProductRegistration))); 21 | 22 | var transport = new DistributedTransport(config); 23 | 24 | // WARMUP 25 | for (var i = 0; i < 50; i++) _ = await transport.GetAsync("/"); 26 | 27 | MemoryProfiler.GetSnapshot("before-100-requests"); 28 | for (var i = 0; i < 100; i++) _ = await transport.GetAsync("/"); 29 | MemoryProfiler.GetSnapshot("after-100-requests"); 30 | 31 | await Task.Delay(1000); 32 | MemoryProfiler.ForceGc(); 33 | 34 | MemoryProfiler.GetSnapshot("before-final-request"); 35 | _ = await transport.GetAsync("/"); 36 | MemoryProfiler.GetSnapshot("after-final-request"); 37 | 38 | MemoryProfiler.GetSnapshot("end"); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | dotnet run --project build/scripts -- %* 3 | 4 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | dotnet run --project build/scripts -- "$@" 4 | -------------------------------------------------------------------------------- /build/keys/keypair.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elastic-transport-net/0701e2c43ae9c50c5b74af899cff141875d7285a/build/keys/keypair.snk -------------------------------------------------------------------------------- /build/keys/public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elastic-transport-net/0701e2c43ae9c50c5b74af899cff141875d7285a/build/keys/public.snk -------------------------------------------------------------------------------- /build/scripts/CommandLine.fs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | module CommandLine 6 | 7 | open Argu 8 | open Microsoft.FSharp.Reflection 9 | 10 | type Arguments = 11 | | [] Clean 12 | | [] Build 13 | | [] Test 14 | 15 | | [] PristineCheck 16 | | [] GeneratePackages 17 | | [] ValidatePackages 18 | | [] GenerateReleaseNotes 19 | | [] GenerateApiChanges 20 | | [] Release 21 | 22 | | [] CreateReleaseOnGithub 23 | | [] Publish 24 | 25 | | [] SingleTarget of bool 26 | | [] Token of string 27 | | [] CleanCheckout of bool 28 | with 29 | interface IArgParserTemplate with 30 | member this.Usage = 31 | match this with 32 | | Clean -> "clean known output locations" 33 | | Build -> "Run build" 34 | | Test -> "Runs build then tests" 35 | | Release -> "runs build, tests, and create and validates the packages shy of publishing them" 36 | | Publish -> "Runs the full release" 37 | 38 | | SingleTarget _ -> "Runs the provided sub command without running their dependencies" 39 | | Token _ -> "Token to be used to authenticate with github" 40 | | CleanCheckout _ -> "Skip the clean checkout check that guards the release/publish targets" 41 | 42 | | PristineCheck 43 | | GeneratePackages 44 | | ValidatePackages 45 | | GenerateReleaseNotes 46 | | GenerateApiChanges 47 | | CreateReleaseOnGithub 48 | -> "Undocumented, dependent target" 49 | member this.Name = 50 | match FSharpValue.GetUnionFields(this, typeof) with 51 | | case, _ -> case.Name.ToLowerInvariant() 52 | -------------------------------------------------------------------------------- /build/scripts/Paths.fs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | module Paths 6 | 7 | open System 8 | open System.IO 9 | 10 | let ToolName = "elastic-transport-net" 11 | let Repository = sprintf "elastic/%s" ToolName 12 | let MainTFM = "netstandard2.0" 13 | let SignKey = "069ca2728db333c1" 14 | 15 | let ValidateAssemblyName = false 16 | let IncludeGitHashInInformational = true 17 | let GenerateApiChanges = false 18 | 19 | let Root = 20 | let mutable dir = DirectoryInfo(".") 21 | while dir.GetFiles("*.sln").Length = 0 do dir <- dir.Parent 22 | Environment.CurrentDirectory <- dir.FullName 23 | dir 24 | 25 | let RootRelative path = Path.GetRelativePath(Root.FullName, path) 26 | 27 | let Output = DirectoryInfo(Path.Combine(Root.FullName, "build", "output")) 28 | 29 | let ToolProject = DirectoryInfo(Path.Combine(Root.FullName, "src", ToolName)) 30 | -------------------------------------------------------------------------------- /build/scripts/Program.fs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | module Program 6 | 7 | open Argu 8 | open Bullseye 9 | open ProcNet 10 | open CommandLine 11 | 12 | [] 13 | let main argv = 14 | let parser = ArgumentParser.Create(programName = "./build.sh") 15 | let parsed = 16 | try 17 | let parsed = parser.ParseCommandLine(inputs = argv, raiseOnUsage = true) 18 | let arguments = parsed.GetSubCommand() 19 | Some (parsed, arguments) 20 | with e -> 21 | printfn "%s" e.Message 22 | None 23 | 24 | match parsed with 25 | | None -> 2 26 | | Some (parsed, arguments) -> 27 | 28 | let target = arguments.Name 29 | 30 | Targets.Setup parsed arguments 31 | let swallowTypes = [typeof; typeof] 32 | 33 | Targets.RunTargetsAndExit 34 | ([target], (fun e -> swallowTypes |> List.contains (e.GetType()) ), ":") 35 | 0 36 | 37 | -------------------------------------------------------------------------------- /build/scripts/scripts.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "minver-cli": { 6 | "version": "2.5.0", 7 | "commands": [ 8 | "minver" 9 | ] 10 | }, 11 | "assembly-differ": { 12 | "version": "0.15.0", 13 | "commands": [ 14 | "assembly-differ" 15 | ] 16 | }, 17 | "release-notes": { 18 | "version": "0.6.0", 19 | "commands": [ 20 | "release-notes" 21 | ] 22 | }, 23 | "nupkg-validator": { 24 | "version": "0.6.0", 25 | "commands": [ 26 | "nupkg-validator" 27 | ] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /examples/transport-aot-example/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Text.Json.Serialization; 6 | using Elastic.Transport; 7 | using Elastic.Transport.Products.Elasticsearch; 8 | 9 | var apiKey = Environment.GetEnvironmentVariable("ELASTIC_API_KEY"); 10 | var url = Environment.GetEnvironmentVariable("ELASTIC_URL"); 11 | 12 | var configuration = apiKey is not null && url is not null 13 | ? new ElasticsearchConfiguration(new Uri(url), new ApiKey(apiKey)) 14 | : new ElasticsearchConfiguration { DebugMode = true }; 15 | 16 | var transport = new DistributedTransport(configuration); 17 | 18 | var rootResponse = transport.Get("/"); 19 | if (rootResponse.ApiCallDetails.HasSuccessfulStatusCode) 20 | Console.WriteLine(rootResponse.Get("tagline")); 21 | else 22 | Console.WriteLine(rootResponse); 23 | 24 | public class MyDocument 25 | { 26 | [JsonPropertyName("message")] 27 | public string Message { init; get; } = null!; 28 | } 29 | 30 | [JsonSerializable(typeof(MyDocument))] 31 | internal partial class ExampleJsonSerializerContext : JsonSerializerContext; 32 | -------------------------------------------------------------------------------- /examples/transport-aot-example/transport-aot-example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | transport_aot_example 7 | enable 8 | enable 9 | true 10 | false 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /nuget-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elastic-transport-net/0701e2c43ae9c50c5b74af899cff141875d7285a/nuget-icon.png -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /request-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/elastic-transport-net/0701e2c43ae9c50c5b74af899cff141875d7285a/request-pipeline.png -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Elastic and contributors 6 | Elasticsearch BV 7 | Apache-2.0 8 | https://github.com/elastic/elastic-transport-net 9 | https://github.com/elastic/elastic-transport-net 10 | https://github.com/elastic/elastic-transport-net/releases 11 | 12 | true 13 | $(SolutionRoot)\build\keys\keypair.snk 14 | 15 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 16 | true 17 | nuget-icon.png 18 | 19 | true 20 | true 21 | 002400000480000094000000060200000024000052534131000400000100010015b0fa59d868c7f3ea2ae67567b19e102465745f01b430a38a42b92fd41a0f5869bec1f2b33b589d78662af432fe6b789ef72d4738f7b1a86264d7aeb5185ed8995b2bb104e7c5c58845f1a618be829e410fa34a6bd7d714ece191ed68a66333a83ae7456ee32e9aeb54bc1d7410ae8c344367257e9001abb5e96ce1f1d97696 22 | 23 | 24 | 25 | 26 | nuget-icon.png 27 | True 28 | nuget-icon.png 29 | 30 | 31 | True 32 | license.txt 33 | 34 | 35 | 36 | 37 | 38 | $(NoWarn);IDT001;IDT002 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Audit/CallTraceState.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using Elastic.Transport.Diagnostics.Auditing; 7 | 8 | namespace Elastic.Transport.VirtualizedCluster.Audit; 9 | 10 | public sealed class CallTraceState 11 | { 12 | public CallTraceState(AuditEvent e) => Event = e; 13 | 14 | public Action AssertWithBecause { get; set; } 15 | 16 | public AuditEvent Event { get; private set; } 17 | 18 | public int? Port { get; set; } 19 | 20 | public Action SimpleAssert { get; set; } 21 | } 22 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Audit/ClientCall.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Elastic.Transport.Diagnostics.Auditing; 8 | 9 | namespace Elastic.Transport.VirtualizedCluster.Audit; 10 | 11 | public sealed class ClientCall : List 12 | { 13 | public ClientCall() { } 14 | 15 | public ClientCall(Func requestOverrides) => RequestOverrides = requestOverrides; 16 | 17 | public Action AssertPoolAfterCall { get; private set; } 18 | public Action AssertResponse { get; private set; } 19 | public Func RequestOverrides { get; } 20 | 21 | public void Add(AuditEvent key, Action value) => Add(new CallTraceState(key) { SimpleAssert = value }); 22 | 23 | public void Add(AuditEvent key, int port) => Add(new CallTraceState(key) { Port = port }); 24 | 25 | public void Add(AuditEvent key) => Add(new CallTraceState(key)); 26 | 27 | public void Add(Action pool) => AssertPoolAfterCall = pool; 28 | 29 | public void Add(AuditEvent key, int port, Action assertResponse) 30 | { 31 | Add(new CallTraceState(key) { Port = port }); 32 | AssertResponse = assertResponse; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Components/ExposingPipelineFactory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | #nullable enable 6 | namespace Elastic.Transport.VirtualizedCluster.Components; 7 | 8 | /// 9 | /// An implementation that exposes all the components so that can reference them directly. 10 | /// 11 | public sealed class ExposingPipelineFactory : RequestPipelineFactory 12 | where TConfiguration : class, ITransportConfiguration 13 | { 14 | public ExposingPipelineFactory(TConfiguration configuration) 15 | { 16 | Configuration = configuration; 17 | Transport = new DistributedTransport(Configuration); 18 | } 19 | 20 | private TConfiguration Configuration { get; } 21 | public ITransport Transport { get; } 22 | 23 | public override RequestPipeline Create(BoundConfiguration boundConfiguration) => new(boundConfiguration); 24 | } 25 | #nullable restore 26 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Elastic.Transport.VirtualizedCluster.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Elastic.Transport.VirtualizedCluster 4 | Elastic.Transport.VirtualizedCluster - An in memory TransportClient that can simulate large cluster 5 | elasticsearch;elastic;search;lucene;nest 6 | Provides a way to assert transport behaviour through a rule engine backed VirtualClusterConnection 7 | CS1591;$(NoWarn);IDT001 8 | 9 | 10 | 11 | true 12 | true 13 | netstandard2.0 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Extensions/NumericExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport.VirtualizedCluster.Extensions; 6 | 7 | internal static class NumericExtensions 8 | { 9 | public static string ToOrdinal(this int num) 10 | { 11 | if (num <= 0) return num.ToString(); 12 | 13 | switch (num % 100) 14 | { 15 | case 11: 16 | case 12: 17 | case 13: 18 | return num + "th"; 19 | } 20 | 21 | switch (num % 10) 22 | { 23 | case 1: 24 | return num + "st"; 25 | case 2: 26 | return num + "nd"; 27 | case 3: 28 | return num + "rd"; 29 | default: 30 | return num + "th"; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Products/Elasticsearch/ElasticsearchMockProductRegistration.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Elastic.Transport.Products; 8 | using Elastic.Transport.Products.Elasticsearch; 9 | 10 | namespace Elastic.Transport.VirtualizedCluster.Products.Elasticsearch; 11 | 12 | /// > 13 | public sealed class ElasticsearchMockProductRegistration : MockProductRegistration 14 | { 15 | /// A static instance of to reuse 16 | public static MockProductRegistration Default { get; } = new ElasticsearchMockProductRegistration(); 17 | 18 | /// > 19 | public override ProductRegistration ProductRegistration { get; } = ElasticsearchProductRegistration.Default; 20 | 21 | /// > 22 | public override byte[] CreateSniffResponseBytes(IReadOnlyList nodes, string stackVersion, string publishAddressOverride, bool returnFullyQualifiedDomainNames) => 23 | ElasticsearchSniffResponseFactory.Create(nodes, stackVersion, publishAddressOverride, returnFullyQualifiedDomainNames); 24 | 25 | public override bool IsSniffRequest(Endpoint endpoint) => 26 | endpoint.PathAndQuery.StartsWith(ElasticsearchProductRegistration.SniffPath, StringComparison.Ordinal); 27 | 28 | public override bool IsPingRequest(Endpoint endpoint) => 29 | endpoint.Method == HttpMethod.HEAD && (endpoint.PathAndQuery == string.Empty || endpoint.PathAndQuery.StartsWith("?")); 30 | } 31 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Products/MockProductRegistration.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using Elastic.Transport.Products; 7 | using Elastic.Transport.VirtualizedCluster.Components; 8 | 9 | namespace Elastic.Transport.VirtualizedCluster.Products; 10 | 11 | /// 12 | /// Makes sure is mockable by providing a different sniff response based on the current 13 | /// 14 | public abstract class MockProductRegistration 15 | { 16 | /// 17 | /// Information about the current product we are injecting into 18 | /// 19 | public abstract ProductRegistration ProductRegistration { get; } 20 | 21 | /// 22 | /// Return the sniff response for the product as raw bytes for to return. 23 | /// 24 | /// The nodes we expect to be returned in the response 25 | /// The current version under test 26 | /// Return this hostname instead of some IP 27 | /// If the sniff can return internal + external information return both 28 | public abstract byte[] CreateSniffResponseBytes(IReadOnlyList nodes, string stackVersion, string publishAddressOverride, bool returnFullyQualifiedDomainNames); 29 | 30 | /// 31 | /// see uses this to determine if the current request is a sniff request and should follow 32 | /// the sniffing rules 33 | /// 34 | public abstract bool IsSniffRequest(Endpoint endpoint); 35 | 36 | public abstract bool IsPingRequest(Endpoint endpoint); 37 | } 38 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Providers/TestableDateTimeProvider.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport.VirtualizedCluster.Providers; 8 | 9 | /// 10 | public sealed class TestableDateTimeProvider : DateTimeProvider 11 | { 12 | private DateTimeOffset MutableNow { get; set; } = DateTimeOffset.UtcNow; 13 | 14 | /// 15 | public override DateTimeOffset Now() => MutableNow; 16 | 17 | /// 18 | /// Advance the time returns 19 | /// 20 | /// A fun that gets passed the current and needs to return the new value 21 | public void ChangeTime(Func change) => MutableNow = change(MutableNow); 22 | 23 | public override DateTimeOffset DeadTime(int attempts, TimeSpan? minDeadTimeout, TimeSpan? maxDeadTimeout) => throw new NotImplementedException(); 24 | } 25 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Rules/ClientCallRule.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | #if !NETFRAMEWORK 6 | using System; 7 | using TheException = System.Net.Http.HttpRequestException; 8 | #else 9 | using TheException = System.Net.WebException; 10 | #endif 11 | 12 | namespace Elastic.Transport.VirtualizedCluster.Rules; 13 | 14 | public interface IClientCallRule : IRule { } 15 | 16 | public sealed class ClientCallRule : RuleBase, IClientCallRule 17 | { 18 | private IClientCallRule Self => this; 19 | 20 | public ClientCallRule Fails(RuleOption times, RuleOption errorState = null) 21 | { 22 | Self.Times = times; 23 | Self.Succeeds = false; 24 | Self.Return = errorState ?? new TheException(); 25 | return this; 26 | } 27 | 28 | public ClientCallRule Succeeds(RuleOption times, int? validResponseCode = 200) 29 | { 30 | Self.Times = times; 31 | Self.Succeeds = true; 32 | Self.Return = validResponseCode; 33 | return this; 34 | } 35 | 36 | public ClientCallRule AfterSucceeds(RuleOption errorState = null) 37 | { 38 | Self.AfterSucceeds = errorState; 39 | return this; 40 | } 41 | 42 | public ClientCallRule ThrowsAfterSucceeds() 43 | { 44 | Self.AfterSucceeds = new TheException(); 45 | return this; 46 | } 47 | 48 | public ClientCallRule SucceedAlways(int? validResponseCode = 200) => Succeeds(TimesHelper.Always, validResponseCode); 49 | 50 | public ClientCallRule FailAlways(RuleOption errorState = null) => Fails(TimesHelper.Always, errorState); 51 | } 52 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Rules/PingRule.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport.VirtualizedCluster.Rules; 8 | 9 | public sealed class PingRule : RuleBase 10 | { 11 | private IRule Self => this; 12 | 13 | public PingRule Fails(RuleOption times, RuleOption errorState = null) 14 | { 15 | Self.Times = times; 16 | Self.Succeeds = false; 17 | Self.Return = errorState; 18 | return this; 19 | } 20 | 21 | public PingRule Succeeds(RuleOption times, int? validResponseCode = 200) 22 | { 23 | Self.Times = times; 24 | Self.Succeeds = true; 25 | Self.Return = validResponseCode; 26 | return this; 27 | } 28 | 29 | public PingRule SucceedAlways(int? validResponseCode = 200) => Succeeds(TimesHelper.Always, validResponseCode); 30 | 31 | public PingRule FailAlways(RuleOption errorState = null) => Fails(TimesHelper.Always, errorState); 32 | } 33 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Rules/SniffRule.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using Elastic.Transport.VirtualizedCluster.Components; 7 | 8 | namespace Elastic.Transport.VirtualizedCluster.Rules; 9 | 10 | public interface ISniffRule : IRule 11 | { 12 | /// The new cluster state after the sniff returns 13 | VirtualCluster NewClusterState { get; set; } 14 | } 15 | 16 | public sealed class SniffRule : RuleBase, ISniffRule 17 | { 18 | VirtualCluster ISniffRule.NewClusterState { get; set; } 19 | private ISniffRule Self => this; 20 | 21 | public SniffRule Fails(RuleOption times, RuleOption errorState = null) 22 | { 23 | Self.Times = times; 24 | Self.Succeeds = false; 25 | Self.Return = errorState; 26 | return this; 27 | } 28 | 29 | public SniffRule Succeeds(RuleOption times, VirtualCluster cluster = null) 30 | { 31 | Self.Times = times; 32 | Self.Succeeds = true; 33 | Self.NewClusterState = cluster; 34 | Self.Return = 200; 35 | return this; 36 | } 37 | 38 | public SniffRule SucceedAlways(VirtualCluster cluster = null) => Succeeds(TimesHelper.Always, cluster); 39 | 40 | public SniffRule FailAlways(RuleOption errorState = null) => Fails(TimesHelper.Always, errorState); 41 | } 42 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Rules/TimesHelper.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport.VirtualizedCluster.Rules; 8 | 9 | public static class TimesHelper 10 | { 11 | public static AllTimes Always = new(); 12 | public static readonly int Once = 0; 13 | public static readonly int Twice = 1; 14 | 15 | public static int Times(int n) => Math.Max(0, n - 1); 16 | 17 | public class AllTimes 18 | { 19 | internal AllTimes() { } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Elastic.Transport.VirtualizedCluster/Setup.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Transport.VirtualizedCluster.Products.Elasticsearch; 6 | 7 | namespace Elastic.Transport.VirtualizedCluster; 8 | 9 | /// 10 | /// Static factory class that can be used to bootstrap virtual product clusters. E.g a cluster of virtual Elasticsearch nodes. 11 | /// 12 | public static class Virtual 13 | { 14 | /// 15 | /// Bootstrap a virtual Elasticsearch cluster using 16 | /// 17 | public static ElasticsearchClusterFactory Elasticsearch { get; } = ElasticsearchClusterFactory.Default; 18 | } 19 | -------------------------------------------------------------------------------- /src/Elastic.Transport/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | resharper_check_namespace_highlighting=do_not_show 3 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/NodePool/SingleNodePool.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Elastic.Transport.Diagnostics.Auditing; 8 | 9 | namespace Elastic.Transport; 10 | 11 | /// A pool to a single node or endpoint. 12 | public class SingleNodePool : NodePool 13 | { 14 | /// 15 | public SingleNodePool(Uri uri) 16 | { 17 | var node = new Node(uri); 18 | UsingSsl = node.Uri.Scheme == "https"; 19 | Nodes = new List { node }; 20 | } 21 | 22 | /// 23 | public override DateTimeOffset? LastUpdate { get; protected set; } 24 | 25 | /// 26 | public override int MaxRetries => 0; 27 | 28 | /// 29 | public override IReadOnlyCollection Nodes { get; } 30 | 31 | /// 32 | public override bool SupportsPinging => false; 33 | 34 | /// 35 | public override bool SupportsReseeding => false; 36 | 37 | /// 38 | public override bool UsingSsl { get; protected set; } 39 | 40 | /// 41 | public override IEnumerable CreateView(Auditor? auditor) => Nodes; 42 | 43 | /// 44 | public override void Reseed(IEnumerable nodes) { } //ignored 45 | } 46 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/NodePool/StickyNodePool.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using Elastic.Transport.Diagnostics.Auditing; 9 | 10 | namespace Elastic.Transport; 11 | 12 | /// 13 | /// A connection pool implementation that does not support reseeding and stays on the first reporting true for . 14 | /// This is great if for instance you have multiple proxies that you can fallback on allowing you to seed the proxies in order of preference. 15 | /// 16 | public sealed class StickyNodePool : StaticNodePool 17 | { 18 | /// 19 | public StickyNodePool(IEnumerable uris) : base(uris, false) { } 20 | 21 | /// 22 | public StickyNodePool(IEnumerable nodes) : base(nodes, false) { } 23 | 24 | /// 25 | public override IEnumerable CreateView(Auditor? auditor) 26 | { 27 | var nodes = AliveNodes; 28 | 29 | if (nodes.Count == 0) 30 | { 31 | var globalCursor = Interlocked.Increment(ref GlobalCursor); 32 | 33 | //could not find a suitable node retrying on first node off globalCursor 34 | yield return RetryInternalNodes(globalCursor, auditor); 35 | 36 | yield break; 37 | } 38 | 39 | // If the cursor is greater than the default then it's been 40 | // set already but we now have a live node so we should reset it 41 | if (GlobalCursor > -1) 42 | Interlocked.Exchange(ref GlobalCursor, -1); 43 | 44 | var localCursor = 0; 45 | foreach (var aliveNode in SelectAliveNodes(localCursor, nodes, auditor)) 46 | yield return aliveNode; 47 | } 48 | 49 | /// 50 | public override void Reseed(IEnumerable nodes) { } 51 | } 52 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/NodePool/StickySniffingNodePool.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading; 9 | using Elastic.Transport.Diagnostics.Auditing; 10 | 11 | namespace Elastic.Transport; 12 | 13 | /// 14 | /// A connection pool implementation that supports reseeding but stays on the first reporting true for . 15 | /// This is great if for instance you have multiple proxies that you can fallback on allowing you to seed the proxies in order of preference. 16 | /// 17 | public sealed class StickySniffingNodePool : SniffingNodePool 18 | { 19 | /// 20 | public StickySniffingNodePool(IEnumerable uris, Func nodeScorer) 21 | : base(uris.Select(uri => new Node(uri)), nodeScorer ?? DefaultNodeScore) { } 22 | 23 | /// 24 | public StickySniffingNodePool(IEnumerable nodes, Func nodeScorer) 25 | : base(nodes, nodeScorer ?? DefaultNodeScore) { } 26 | 27 | /// 28 | public override bool SupportsPinging => true; 29 | 30 | /// 31 | public override bool SupportsReseeding => true; 32 | 33 | /// 34 | public override IEnumerable CreateView(Auditor? auditor) 35 | { 36 | var nodes = AliveNodes; 37 | 38 | if (nodes.Count == 0) 39 | { 40 | var globalCursor = Interlocked.Increment(ref GlobalCursor); 41 | 42 | //could not find a suitable node retrying on first node off globalCursor 43 | yield return RetryInternalNodes(globalCursor, auditor); 44 | 45 | yield break; 46 | } 47 | 48 | // If the cursor is greater than the default then it's been 49 | // set already but we now have a live node so we should reset it 50 | if (GlobalCursor > -1) 51 | Interlocked.Exchange(ref GlobalCursor, -1); 52 | 53 | var localCursor = 0; 54 | foreach (var aliveNode in SelectAliveNodes(localCursor, nodes, auditor)) 55 | yield return aliveNode; 56 | } 57 | 58 | /// Allows subclasses to hook into the parents dispose 59 | private static float DefaultNodeScore(Node node) => 0f; 60 | } 61 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Pipeline/RequestPipelineStatics.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Diagnostics; 6 | using Elastic.Transport.Diagnostics; 7 | 8 | //#if NETSTANDARD2_0 || NETSTANDARD2_1 9 | //using System.Threading.Tasks.Extensions; 10 | //#endif 11 | 12 | namespace Elastic.Transport; 13 | 14 | internal static class RequestPipelineStatics 15 | { 16 | public static readonly string NoNodesAttemptedMessage = 17 | "No nodes were attempted, this can happen when a node predicate does not match any nodes"; 18 | 19 | public static DiagnosticSource DiagnosticSource { get; } = new DiagnosticListener(DiagnosticSources.RequestPipeline.SourceName); 20 | } 21 | #pragma warning restore 1591 22 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Providers/DateTimeProvider.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | /// An abstraction to provide access to the current . This abstraction allows time to be tested within 11 | /// the transport. 12 | /// 13 | public abstract class DateTimeProvider 14 | { 15 | internal DateTimeProvider() { } 16 | 17 | /// The current date time 18 | public abstract DateTimeOffset Now(); 19 | 20 | /// 21 | /// Calculate the dead time for a node based on the number of attempts. 22 | /// 23 | /// The number of attempts on the node 24 | /// The initial dead time as configured by 25 | /// The configured maximum dead timeout as configured by 26 | public abstract DateTimeOffset DeadTime(int attempts, TimeSpan? minDeadTimeout, TimeSpan? maxDeadTimeout); 27 | } 28 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Providers/DefaultDateTimeProvider.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | public sealed class DefaultDateTimeProvider : DateTimeProvider 11 | { 12 | /// A static instance to reuse as is stateless 13 | public static readonly DefaultDateTimeProvider Default = new(); 14 | private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60); 15 | private static readonly TimeSpan MaximumTimeout = TimeSpan.FromMinutes(30); 16 | 17 | /// 18 | public override DateTimeOffset DeadTime(int attempts, TimeSpan? minDeadTimeout, TimeSpan? maxDeadTimeout) 19 | { 20 | var timeout = minDeadTimeout.GetValueOrDefault(DefaultTimeout); 21 | var maxTimeout = maxDeadTimeout.GetValueOrDefault(MaximumTimeout); 22 | var milliSeconds = Math.Min(timeout.TotalMilliseconds * 2 * Math.Pow(2, attempts * 0.5 - 1), maxTimeout.TotalMilliseconds); 23 | return Now().AddMilliseconds(milliSeconds); 24 | } 25 | 26 | /// 27 | public override DateTimeOffset Now() => DateTimeOffset.UtcNow; 28 | } 29 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Providers/DefaultMemoryStreamFactory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | /// A factory for creating memory streams using instances of 11 | /// 12 | public sealed class DefaultMemoryStreamFactory : MemoryStreamFactory 13 | { 14 | /// Provide a static instance of this stateless class, so it can be reused 15 | public static DefaultMemoryStreamFactory Default { get; } = new DefaultMemoryStreamFactory(); 16 | 17 | /// 18 | public override MemoryStream Create() => new(); 19 | 20 | /// 21 | public override MemoryStream Create(byte[] bytes) => new(bytes); 22 | 23 | /// 24 | public override MemoryStream Create(byte[] bytes, int index, int count) => new(bytes, index, count); 25 | } 26 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Providers/DefaultRequestPipelineFactory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// 8 | /// The default implementation for that returns 9 | /// 10 | internal sealed class DefaultRequestPipelineFactory : RequestPipelineFactory 11 | { 12 | public static readonly DefaultRequestPipelineFactory Default = new (); 13 | /// 14 | /// returns instances of 15 | /// 16 | public override RequestPipeline Create(BoundConfiguration boundConfiguration) => 17 | new RequestPipeline(boundConfiguration); 18 | } 19 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Providers/MemoryStreamFactory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | /// A factory for creating memory streams 11 | /// 12 | public abstract class MemoryStreamFactory 13 | { 14 | internal MemoryStreamFactory() { } 15 | 16 | /// 17 | /// Creates a memory stream 18 | /// 19 | public abstract MemoryStream Create(); 20 | 21 | /// 22 | /// Creates a memory stream with the bytes written to the stream 23 | /// 24 | public abstract MemoryStream Create(byte[] bytes); 25 | 26 | /// 27 | /// Creates a memory stream with the bytes written to the stream 28 | /// 29 | public abstract MemoryStream Create(byte[] bytes, int index, int count); 30 | } 31 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Providers/RecyclableMemoryStreamFactory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | 8 | namespace Elastic.Transport; 9 | 10 | /// 11 | /// A factory for creating memory streams using a recyclable pool of instances 12 | /// 13 | public sealed class RecyclableMemoryStreamFactory : MemoryStreamFactory 14 | { 15 | private const string TagSource = "Elastic.Transport"; 16 | private readonly RecyclableMemoryStreamManager _manager; 17 | 18 | /// Provide a static instance of this stateless class, so it can be reused 19 | public static RecyclableMemoryStreamFactory Default { get; } = new RecyclableMemoryStreamFactory(); 20 | 21 | /// 22 | public RecyclableMemoryStreamFactory() => _manager = CreateManager(experimental: false); 23 | 24 | private static RecyclableMemoryStreamManager CreateManager(bool experimental) 25 | { 26 | if (!experimental) return new RecyclableMemoryStreamManager() { AggressiveBufferReturn = true }; 27 | 28 | const int blockSize = 1024; 29 | const int largeBufferMultiple = 1024 * 1024; 30 | const int maxBufferSize = 16 * largeBufferMultiple; 31 | return new RecyclableMemoryStreamManager(blockSize, largeBufferMultiple, maxBufferSize) 32 | { 33 | AggressiveBufferReturn = true, MaximumFreeLargePoolBytes = maxBufferSize * 4, MaximumFreeSmallPoolBytes = 100 * blockSize 34 | }; 35 | } 36 | 37 | /// 38 | public override MemoryStream Create() => _manager.GetStream(Guid.Empty, TagSource); 39 | 40 | /// 41 | public override MemoryStream Create(byte[] bytes) => _manager.GetStream(bytes); 42 | 43 | /// 44 | public override MemoryStream Create(byte[] bytes, int index, int count) => _manager.GetStream(Guid.Empty, TagSource, bytes, index, count); 45 | } 46 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Providers/RequestPipelineFactory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// A factory that creates instances of , this factory exists so that transport can be tested. 8 | public abstract class RequestPipelineFactory 9 | { 10 | internal RequestPipelineFactory() { } 11 | 12 | /// Create an instance of 13 | public abstract RequestPipeline Create(BoundConfiguration boundConfiguration); 14 | } 15 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Serialization/Converters/DynamicDictionaryConverter.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Globalization; 9 | using System.Text.Json; 10 | using System.Text.Json.Serialization; 11 | using System.Text.Json.Serialization.Metadata; 12 | using Elastic.Transport.Products.Elasticsearch; 13 | using JsonSerializer = System.Text.Json.JsonSerializer; 14 | 15 | namespace Elastic.Transport; 16 | 17 | internal class DynamicDictionaryConverter : JsonConverter 18 | { 19 | public override DynamicDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 20 | { 21 | if (reader.TokenType == JsonTokenType.StartArray) 22 | { 23 | var array = JsonSerializer.Deserialize(ref reader, ElasticsearchTransportSerializerContext.Default.ObjectArray); // TODO: Test! This might not work without adding `object[]` to `ErrorSerializationContext` 24 | var arrayDict = new Dictionary(); 25 | for (var i = 0; i < array.Length; i++) 26 | arrayDict[i.ToString(CultureInfo.InvariantCulture)] = new DynamicValue(array[i]); 27 | return DynamicDictionary.Create(arrayDict); 28 | } 29 | if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException(); 30 | 31 | var dict = JsonSerializer.Deserialize>(ref reader, ElasticsearchTransportSerializerContext.Default.DictionaryStringObject); // TODO: Test! This might not work without adding `Dictionary` to `ErrorSerializationContext` 32 | return DynamicDictionary.Create(dict); 33 | } 34 | 35 | [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "We always provide a static JsonTypeInfoResolver")] 36 | [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", Justification = "We always provide a static JsonTypeInfoResolver")] 37 | public override void Write(Utf8JsonWriter writer, DynamicDictionary dictionary, JsonSerializerOptions options) 38 | { 39 | writer.WriteStartObject(); 40 | 41 | foreach (var kvp in dictionary) 42 | { 43 | if (kvp.Value is null) continue; 44 | 45 | writer.WritePropertyName(kvp.Key); 46 | 47 | // TODO: Test! We have to make sure all possible "Value" types are registered in the `ErrorSerializationContext` 48 | JsonSerializer.Serialize(writer, kvp.Value.Value, kvp.Value.GetType(), options); 49 | } 50 | 51 | writer.WriteEndObject(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Serialization/IJsonSerializerOptionsProvider.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text.Json; 8 | using System.Text.Json.Serialization; 9 | 10 | namespace Elastic.Transport; 11 | 12 | /// 13 | /// Provides an instance of to 14 | /// 15 | public interface IJsonSerializerOptionsProvider 16 | { 17 | /// 18 | JsonSerializerOptions CreateJsonSerializerOptions(); 19 | } 20 | 21 | /// 22 | /// Default implementation of specialized in providing more converters and 23 | /// altering the shared used by and its derived classes 24 | /// 25 | public class TransportSerializerOptionsProvider : IJsonSerializerOptionsProvider 26 | { 27 | private readonly IReadOnlyCollection? _bakedInConverters; 28 | private readonly IReadOnlyCollection? _userProvidedConverters; 29 | private readonly Action? _mutateOptions; 30 | 31 | /// 32 | public JsonSerializerOptions? CreateJsonSerializerOptions() 33 | { 34 | var options = new JsonSerializerOptions(); 35 | 36 | foreach (var converter in _bakedInConverters ?? []) 37 | options.Converters.Add(converter); 38 | 39 | foreach (var converter in _userProvidedConverters ?? []) 40 | options.Converters.Add(converter); 41 | 42 | _mutateOptions?.Invoke(options); 43 | 44 | return options; 45 | } 46 | 47 | /// 48 | public TransportSerializerOptionsProvider() { } 49 | 50 | /// 51 | public TransportSerializerOptionsProvider( 52 | IReadOnlyCollection bakedInConverters, 53 | IReadOnlyCollection? userProvidedConverters, 54 | Action? mutateOptions = null 55 | ) 56 | { 57 | _bakedInConverters = bakedInConverters; 58 | _userProvidedConverters = userProvidedConverters; 59 | _mutateOptions = mutateOptions; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Serialization/JsonElementExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Linq; 8 | using System.Text.Json; 9 | 10 | namespace Elastic.Transport.Extensions; 11 | 12 | internal static class JsonElementExtensions 13 | { 14 | /// 15 | /// Fully consumes a json element representing a json object. Meaning it will attempt to unwrap all JsonElement values 16 | /// recursively to their actual types. This should only be used in the context of which is 17 | /// allowed to be slow yet convenient 18 | /// 19 | public static IDictionary ToDictionary(this JsonElement e) => 20 | e.ValueKind switch 21 | { 22 | JsonValueKind.Object => e.EnumerateObject() 23 | .Aggregate(new Dictionary(), (dict, je) => 24 | { 25 | dict.Add(je.Name, DynamicValue.ConsumeJsonElement(typeof(object), je.Value)); 26 | return dict; 27 | }), 28 | JsonValueKind.Array => e.EnumerateArray() 29 | .Select((je, i) => (i, o: DynamicValue.ConsumeJsonElement(typeof(object), je))) 30 | .Aggregate(new Dictionary(), (dict, t) => 31 | { 32 | dict.Add(t.i.ToString(CultureInfo.InvariantCulture), t.o); 33 | return dict; 34 | }), 35 | _ => null 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Serialization/LowLevelRequestResponseSerializer.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Text.Json; 8 | using System.Text.Json.Serialization; 9 | using System.Text.Json.Serialization.Metadata; 10 | using Elastic.Transport.Products.Elasticsearch; 11 | 12 | namespace Elastic.Transport; 13 | 14 | /// 15 | /// Default low level request/response-serializer implementation for which serializes using 16 | /// the Microsoft System.Text.Json library 17 | /// 18 | internal sealed class LowLevelRequestResponseSerializer : SystemTextJsonSerializer 19 | { 20 | /// 21 | /// Provides a static reusable reference to an instance of to promote reuse. 22 | /// 23 | internal static readonly LowLevelRequestResponseSerializer Instance = new(); 24 | 25 | /// > 26 | public LowLevelRequestResponseSerializer() : this(null) { } 27 | 28 | /// 29 | /// > 30 | /// 31 | /// Add more default converters onto being used 32 | //[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] 33 | //[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] 34 | 35 | [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "We always provide a static JsonTypeInfoResolver")] 36 | [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", Justification = "We always provide a static JsonTypeInfoResolver")] 37 | public LowLevelRequestResponseSerializer(IReadOnlyCollection? converters) 38 | : base(new TransportSerializerOptionsProvider([ 39 | new ExceptionConverter(), 40 | new ErrorCauseConverter(), 41 | new ErrorConverter(), 42 | new DynamicDictionaryConverter() 43 | ], converters, options => 44 | { 45 | options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; 46 | options.TypeInfoResolver = JsonTypeInfoResolver.Combine(new DefaultJsonTypeInfoResolver(), ElasticsearchTransportSerializerContext.Default); 47 | })) { } 48 | } 49 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Serialization/SerializationFormatting.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// 8 | /// A hint to how to format the json. 9 | /// Implementation of might choose to ignore this hint though. 10 | /// 11 | public enum SerializationFormatting 12 | { 13 | /// 14 | /// Serializer should not render the json with whitespace and line endings. 15 | /// implementation HAVE to be able to adhere this value as for instance nd-json relies on this 16 | /// 17 | None, 18 | 19 | /// 20 | /// A hint that the user prefers readable data being written. implementations 21 | /// should try to adhere to this but won't break anything if they don't. 22 | /// 23 | Indented 24 | } 25 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/Serialization/SerializerRegistrationInformation.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// Provides some information to the transport auditing and diagnostics infrastructure about the serializer in use and its 10 | public sealed class SerializerRegistrationInformation 11 | { 12 | private readonly string _stringRepresentation; 13 | 14 | /// 15 | public SerializerRegistrationInformation(Type type, string purpose) 16 | { 17 | TypeInformation = type; 18 | Purpose = purpose; 19 | _stringRepresentation = $"{Purpose}: {TypeInformation.FullName}"; 20 | } 21 | 22 | /// The type of in use currently 23 | // ReSharper disable once MemberCanBePrivate.Global 24 | public Type TypeInformation { get; } 25 | 26 | /// 27 | /// A string describing the purpose of the serializer emitting this events. 28 | /// In `Elastisearch.Net` this will always be "request/response" 29 | /// Using `Nest` this could also be `source` allowing you to differentiate between the internal and configured source serializer 30 | /// 31 | // ReSharper disable once MemberCanBePrivate.Global 32 | public string Purpose { get; } 33 | 34 | /// A precalculated string representation of the serializer in use 35 | public override string ToString() => _stringRepresentation; 36 | } 37 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/TransportClient/CertificateHelpers.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Security.Cryptography; 7 | using System.Security.Cryptography.X509Certificates; 8 | 9 | namespace Elastic.Transport; 10 | 11 | internal static class CertificateHelpers 12 | { 13 | private const string _colon = ":"; 14 | private const string _hyphen = "-"; 15 | 16 | /// 17 | /// Returns the result of validating the fingerprint of a certificate against an expected fingerprint string. 18 | /// 19 | public static bool ValidateCertificateFingerprint(X509Certificate certificate, string expectedFingerprint) 20 | { 21 | string sha256Fingerprint; 22 | 23 | #if !NETFRAMEWORK&& !NETSTANDARD2_0 24 | sha256Fingerprint = certificate.GetCertHashString(HashAlgorithmName.SHA256); 25 | #else 26 | using var alg = SHA256.Create(); 27 | 28 | var sha256FingerprintBytes = alg.ComputeHash(certificate.GetRawCertData()); 29 | sha256Fingerprint = BitConverter.ToString(sha256FingerprintBytes); 30 | #endif 31 | 32 | sha256Fingerprint = ComparableFingerprint(sha256Fingerprint); 33 | return expectedFingerprint.Equals(sha256Fingerprint, StringComparison.OrdinalIgnoreCase); 34 | } 35 | 36 | /// 37 | /// Cleans the fingerprint by removing colons and dashes so that a comparison can be made 38 | /// without such characters affecting the result. 39 | /// 40 | public static string ComparableFingerprint(string fingerprint) 41 | { 42 | var finalFingerprint = fingerprint; 43 | 44 | if (fingerprint.Contains(_colon)) 45 | finalFingerprint = fingerprint.Replace(_colon, string.Empty); 46 | else if (fingerprint.Contains(_hyphen)) 47 | finalFingerprint = fingerprint.Replace(_hyphen, string.Empty); 48 | 49 | return finalFingerprint; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/TransportClient/HandlerTracking/ExpiredHandlerTrackingEntry.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | // Licensed to the .NET Foundation under one or more agreements. 6 | // The .NET Foundation licenses this file to you under the MIT license. 7 | // See the LICENSE file in the project root for more information 8 | 9 | #if !NETFRAMEWORK 10 | using System; 11 | using System.Net.Http; 12 | 13 | namespace Elastic.Transport; 14 | 15 | /// 16 | /// Thread-safety: This class is immutable 17 | /// https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Http/src/ExpiredHandlerTrackingEntry.cs 18 | /// 19 | internal sealed class ExpiredHandlerTrackingEntry 20 | { 21 | private readonly WeakReference _livenessTracker; 22 | 23 | // IMPORTANT: don't cache a reference to `other` or `other.Handler` here. 24 | // We need to allow it to be GC'ed. 25 | public ExpiredHandlerTrackingEntry(ActiveHandlerTrackingEntry other) 26 | { 27 | Key = other.Key; 28 | 29 | _livenessTracker = new WeakReference(other.Handler); 30 | InnerHandler = other.Handler.InnerHandler; 31 | } 32 | 33 | public bool CanDispose => !_livenessTracker.IsAlive; 34 | 35 | public HttpMessageHandler InnerHandler { get; } 36 | 37 | public int Key { get; } 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/TransportClient/HandlerTracking/LifetimeTrackingHttpMessageHandler.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | // Licensed to the .NET Foundation under one or more agreements. 6 | // The .NET Foundation licenses this file to you under the MIT license. 7 | // See the LICENSE file in the project root for more information 8 | 9 | #if !NETFRAMEWORK 10 | using System.Net.Http; 11 | 12 | namespace Elastic.Transport; 13 | 14 | /// 15 | /// This a marker used to check if the underlying handler should be disposed. HttpClients 16 | /// share a reference to an instance of this class, and when it goes out of scope the inner handler 17 | /// is eligible to be disposed. 18 | /// https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Http/src/LifetimeTrackingHttpMessageHandler.cs 19 | /// 20 | internal sealed class LifetimeTrackingHttpMessageHandler : DelegatingHandler 21 | { 22 | public LifetimeTrackingHttpMessageHandler(HttpMessageHandler innerHandler) 23 | : base(innerHandler) { } 24 | 25 | protected override void Dispose(bool disposing) 26 | { 27 | // The lifetime of this is tracked separately by ActiveHandlerTrackingEntry 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/TransportClient/HandlerTracking/ValueStopWatch.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | // Licensed to the .NET Foundation under one or more agreements. 6 | // The .NET Foundation licenses this file to you under the MIT license. 7 | // See the LICENSE file in the project root for more information 8 | 9 | #if !NETFRAMEWORK 10 | using System; 11 | using System.Threading; 12 | 13 | namespace Elastic.Transport; 14 | 15 | /// 16 | /// A convenience API for interacting with System.Threading.Timer in a way 17 | /// that doesn't capture the ExecutionContext. We should be using this (or equivalent) 18 | /// everywhere we use timers to avoid rooting any values stored in asynclocals. 19 | /// https://github.com/dotnet/runtime/blob/master/src/libraries/Common/src/Extensions/ValueStopwatch/ValueStopwatch.cs 20 | /// 21 | internal static class NonCapturingTimer 22 | { 23 | public static Timer Create(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) 24 | { 25 | if (callback == null) throw new ArgumentNullException(nameof(callback)); 26 | 27 | // Don't capture the current ExecutionContext and its AsyncLocals onto the timer 28 | var restoreFlow = false; 29 | try 30 | { 31 | if (ExecutionContext.IsFlowSuppressed()) return new Timer(callback, state, dueTime, period); 32 | 33 | ExecutionContext.SuppressFlow(); 34 | restoreFlow = true; 35 | 36 | return new Timer(callback, state, dueTime, period); 37 | } 38 | finally 39 | { 40 | // Restore the current ExecutionContext 41 | if (restoreFlow) ExecutionContext.RestoreFlow(); 42 | } 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/TransportClient/HttpMethod.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Runtime.Serialization; 7 | 8 | // ReSharper disable InconsistentNaming 9 | 10 | namespace Elastic.Transport; 11 | 12 | /// Http Method of the API call to be performed 13 | public enum HttpMethod 14 | { 15 | [EnumMember(Value = "GET")] 16 | // These really do not need xmldocs, leave it to the reader if they feel inspired :) 17 | #pragma warning disable 1591 18 | GET, 19 | 20 | [EnumMember(Value = "POST")] 21 | POST, 22 | 23 | [EnumMember(Value = "PUT")] 24 | PUT, 25 | 26 | [EnumMember(Value = "DELETE")] 27 | DELETE, 28 | 29 | [EnumMember(Value = "HEAD")] 30 | HEAD 31 | #pragma warning restore 1591 32 | } 33 | 34 | /// 35 | /// Defines extension methods for . 36 | /// 37 | public static class HttpMethodExtensions 38 | { 39 | /// 40 | /// Returns the string value for a given . 41 | /// 42 | public static string GetStringValue(this HttpMethod httpMethod) => 43 | httpMethod switch 44 | { 45 | HttpMethod.GET => "GET", 46 | HttpMethod.POST => "POST", 47 | HttpMethod.PUT => "PUT", 48 | HttpMethod.DELETE => "DELETE", 49 | HttpMethod.HEAD => "HEAD", 50 | _ => throw new InvalidOperationException("Unknown enum value.") 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/TransportClient/HttpRequestInvoker-FullFramework.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | #if NETFRAMEWORK 6 | using System.Net; 7 | 8 | namespace Elastic.Transport; 9 | 10 | /// The default implementation. Uses on the current .NET desktop framework. 11 | public class HttpRequestInvoker : HttpWebRequestInvoker 12 | { 13 | /// 14 | /// Create a new instance of the . 15 | /// 16 | public HttpRequestInvoker() { } 17 | 18 | /// The default TransportClient implementation. Uses on the current .NET desktop framework. 19 | internal HttpRequestInvoker(ResponseFactory responseFactory) : base(responseFactory) { } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/TransportClient/RequestInvokerHelpers.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Diagnostics; 6 | using Elastic.Transport.Diagnostics; 7 | 8 | namespace Elastic.Transport; 9 | 10 | internal static class RequestInvokerHelpers 11 | { 12 | public static void SetOtelAttributes(BoundConfiguration boundConfiguration, TResponse response) where TResponse : TransportResponse 13 | { 14 | if (!OpenTelemetry.CurrentSpanIsElasticTransportOwnedAndHasListeners || (!(Activity.Current?.IsAllDataRequested ?? false))) 15 | return; 16 | 17 | var attributes = boundConfiguration.ConnectionSettings.ProductRegistration.ParseOpenTelemetryAttributesFromApiCallDetails(response.ApiCallDetails); 18 | 19 | if (attributes is null) return; 20 | 21 | foreach (var attribute in attributes) 22 | Activity.Current?.SetTag(attribute.Key, attribute.Value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Components/TransportClient/WebProxy.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | #if !NETFRAMEWORK 6 | using System; 7 | using System.Net; 8 | 9 | namespace Elastic.Transport; 10 | 11 | internal class WebProxy : IWebProxy 12 | { 13 | private readonly Uri _uri; 14 | 15 | public WebProxy(Uri uri) => _uri = uri; 16 | 17 | public ICredentials Credentials { get; set; } 18 | 19 | public Uri GetProxy(Uri destination) => _uri; 20 | 21 | public bool IsBypassed(Uri host) => false; 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Configuration/ConnectionInfo.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | #if !NETFRAMEWORK 7 | using System.Net.Http; 8 | #endif 9 | 10 | namespace Elastic.Transport; 11 | 12 | internal static class ConnectionInfo 13 | { 14 | public static bool UsingCurlHandler 15 | { 16 | get 17 | { 18 | // Not available after .NET 5.0 19 | #if NET6_0_OR_GREATER || NETFRAMEWORK 20 | #pragma warning disable IDE0025 // Use expression body for properties 21 | return false; 22 | #pragma warning restore IDE0025 // Use expression body for properties 23 | #else 24 | var curlHandlerExists = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.CurlHandler") != null; 25 | if (!curlHandlerExists) 26 | return false; 27 | 28 | var socketsHandlerExists = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.SocketsHttpHandler") != null; 29 | // running on a .NET core version with CurlHandler, before the existence of SocketsHttpHandler. 30 | // Must be using CurlHandler. 31 | if (!socketsHandlerExists) 32 | return true; 33 | 34 | if (AppContext.TryGetSwitch("System.Net.Http.UseSocketsHttpHandler", out var isEnabled)) 35 | return !isEnabled; 36 | 37 | var environmentVariable = 38 | Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER"); 39 | 40 | // SocketsHandler exists and no environment variable exists to disable it. 41 | // Must be using SocketsHandler and not CurlHandler 42 | if (environmentVariable == null) 43 | return false; 44 | 45 | return environmentVariable.Equals("false", StringComparison.OrdinalIgnoreCase) || 46 | environmentVariable.Equals("0"); 47 | #endif 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Configuration/Security/ApiKey.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// 8 | /// Credentials for Api Key Authentication 9 | /// 10 | public class ApiKey : AuthorizationHeader 11 | { 12 | private readonly string _apiKey; 13 | 14 | /// 15 | public ApiKey(string apiKey) => _apiKey = apiKey; 16 | 17 | /// 18 | public override string AuthScheme { get; } = "ApiKey"; 19 | 20 | /// 21 | public override bool TryGetAuthorizationParameters(out string value) 22 | { 23 | value = _apiKey; 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Configuration/Security/AuthorizationHeader.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// 8 | /// The HTTP authorization request header used to provide credentials that authenticate a user agent with a server. 9 | /// 10 | public abstract class AuthorizationHeader 11 | { 12 | /// 13 | /// The authentication scheme that defines how the credentials are encoded. 14 | /// 15 | public abstract string AuthScheme { get; } 16 | 17 | /// 18 | /// If this instance is valid, returns the authorization parameters to include in the header. 19 | /// 20 | public abstract bool TryGetAuthorizationParameters(out string value); 21 | } 22 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Configuration/Security/Base64ApiKey.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Text; 7 | 8 | namespace Elastic.Transport; 9 | 10 | /// 11 | /// Credentials for Api Key Authentication 12 | /// 13 | public class Base64ApiKey : ApiKey 14 | { 15 | /// 16 | public Base64ApiKey(string id, string apiKey) : 17 | base(Convert.ToBase64String(Encoding.UTF8.GetBytes($"{id}:{apiKey}"))) {} 18 | 19 | /// 20 | public Base64ApiKey(string base64EncodedApiKey) : base(base64EncodedApiKey) {} 21 | } 22 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Configuration/Security/BasicAuthenticationCredentials.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Text; 7 | 8 | namespace Elastic.Transport; 9 | 10 | /// 11 | /// Credentials for Basic Authentication. 12 | /// 13 | public sealed class BasicAuthentication : AuthorizationHeader 14 | { 15 | private readonly string _base64String; 16 | 17 | /// The default http header used for basic authentication 18 | public static string BasicAuthenticationScheme { get; } = "Basic"; 19 | 20 | /// 21 | public BasicAuthentication(string username, string password) 22 | { 23 | _base64String = GetBase64String($"{username}:{password}"); 24 | Username = username; 25 | } 26 | 27 | /// 28 | public override string AuthScheme { get; } = BasicAuthenticationScheme; 29 | internal string Username { get; } 30 | 31 | /// 32 | public override bool TryGetAuthorizationParameters(out string value) 33 | { 34 | value = _base64String; 35 | return true; 36 | } 37 | 38 | /// Get Base64 representation for string 39 | public static string GetBase64String(string header) => 40 | Convert.ToBase64String(Encoding.UTF8.GetBytes(header)); 41 | } 42 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Configuration/UserAgent.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Reflection; 7 | 8 | #if NETFRAMEWORK 9 | 10 | using Elastic.Transport.Extensions; 11 | 12 | #else 13 | 14 | using System.Runtime.InteropServices; 15 | 16 | #endif 17 | 18 | namespace Elastic.Transport; 19 | 20 | /// 21 | /// Represents the user agent string. Two constructors exists, one to aid with constructing elastic clients standard compliant 22 | /// user agents and one free form to allow any custom string to be set. 23 | /// 24 | public sealed class UserAgent 25 | { 26 | private readonly string _toString; 27 | 28 | private UserAgent(string reposName, Type typeVersionLookup, string[]? metadata = null) 29 | { 30 | var version = typeVersionLookup.Assembly 31 | .GetCustomAttribute() 32 | .InformationalVersion; 33 | 34 | var meta = string.Join("; ", metadata ?? []); 35 | var assemblyName = typeVersionLookup.Assembly.GetName().Name; 36 | 37 | _toString = $"{reposName}/{version} ({RuntimeInformation.OSDescription}; {RuntimeInformation.FrameworkDescription}; {assemblyName}{meta.Trim()})"; 38 | } 39 | 40 | private UserAgent(string fullUserAgentString) => _toString = fullUserAgentString; 41 | 42 | /// Create a user agent that adheres to the minimum information needed to be elastic standard compliant 43 | /// The repos name uniquely identifies the origin of the client 44 | /// 45 | /// Use 's assembly 46 | /// to inject version information into the header 47 | /// 48 | public static UserAgent Create(string reposName, Type typeVersionLookup) => new UserAgent(reposName, typeVersionLookup); 49 | 50 | /// 51 | public static UserAgent Create(string reposName, Type typeVersionLookup, string[] metadata) => new UserAgent(reposName, typeVersionLookup, metadata); 52 | 53 | /// Create a user string that does not confirm to elastic client standards 54 | public static UserAgent Create(string fullUserAgentString) => new UserAgent(fullUserAgentString); 55 | 56 | /// The pre=calculated string representation of this instance 57 | /// 58 | public override string ToString() => _toString; 59 | } 60 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/AuditDiagnosticObserver.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Elastic.Transport.Diagnostics.Auditing; 8 | 9 | namespace Elastic.Transport.Diagnostics; 10 | 11 | /// Provides a typed listener to events that emits 12 | internal sealed class AuditDiagnosticObserver : TypedDiagnosticObserver 13 | { 14 | /// 15 | public AuditDiagnosticObserver( 16 | Action> onNext, 17 | Action? onError = null, 18 | Action? onCompleted = null 19 | ) : base(onNext, onError, onCompleted) { } 20 | } 21 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/Auditing/Audit.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using Elastic.Transport.Extensions; 7 | 8 | namespace Elastic.Transport.Diagnostics.Auditing; 9 | 10 | /// An audit of the request made 11 | public sealed class Audit 12 | { 13 | internal Audit(AuditEvent type, DateTimeOffset started) 14 | { 15 | Event = type; 16 | Started = started; 17 | } 18 | 19 | /// 20 | /// The type of audit event. 21 | /// 22 | public AuditEvent Event { get; internal set; } 23 | 24 | /// 25 | /// The node on which the request was made. 26 | /// 27 | public Node? Node { get; internal init; } 28 | 29 | /// 30 | /// The end date and time of the audit. 31 | /// 32 | public DateTimeOffset Ended { get; internal set; } 33 | 34 | /// 35 | /// The start date and time of the audit. 36 | /// 37 | public DateTimeOffset Started { get; } 38 | 39 | /// 40 | /// The exception for the audit, if there was one. 41 | /// 42 | public Exception Exception { get; internal set; } 43 | 44 | /// 45 | /// Returns a string representation of the this audit. 46 | /// 47 | public override string ToString() 48 | { 49 | var took = Ended - Started; 50 | var tookString = string.Empty; 51 | if (took >= TimeSpan.Zero) tookString = $" Took: {took}"; 52 | 53 | return Node == null 54 | ? $"Event: {Event.ToStringFast()}{tookString}" 55 | : $"Event: {Event.ToStringFast()} Node: {Node?.Uri} NodeAlive: {Node?.IsAlive}Took: {tookString}"; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/Auditing/Auditable.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport.Diagnostics.Auditing; 8 | 9 | internal class Auditable : IDisposable 10 | { 11 | private readonly Audit _audit; 12 | 13 | private readonly DateTimeProvider _dateTimeProvider; 14 | 15 | public Auditable(AuditEvent type, DateTimeProvider dateTimeProvider, Node? node) 16 | { 17 | _dateTimeProvider = dateTimeProvider; 18 | 19 | var started = _dateTimeProvider.Now(); 20 | _audit = new Audit(type, started) 21 | { 22 | Node = node 23 | }; 24 | } 25 | 26 | public AuditEvent Event 27 | { 28 | set => Audit.Event = value; 29 | } 30 | 31 | public Exception Exception 32 | { 33 | set => Audit.Exception = value; 34 | } 35 | 36 | public Audit Audit => _audit; 37 | 38 | public void Dispose() => Audit.Ended = _dateTimeProvider.Now(); 39 | } 40 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/Auditing/Auditor.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using Elastic.Transport.Extensions; 8 | 9 | namespace Elastic.Transport.Diagnostics.Auditing; 10 | 11 | /// Collects events 12 | public class Auditor : IReadOnlyCollection 13 | { 14 | private readonly DateTimeProvider _dateTimeProvider; 15 | private List? _audits; 16 | 17 | internal Auditor(DateTimeProvider dateTimeProvider) => _dateTimeProvider = dateTimeProvider; 18 | 19 | /// 20 | public IEnumerator GetEnumerator() => 21 | _audits?.GetEnumerator() ?? (IEnumerator)new EmptyEnumerator(); 22 | 23 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 24 | 25 | internal Auditable Add(Auditable auditable) 26 | { 27 | _audits ??= new List(); 28 | _audits.Add(auditable.Audit); 29 | return auditable; 30 | } 31 | 32 | internal Auditable Add(AuditEvent type, DateTimeProvider dateTimeProvider, Node? node = null) 33 | { 34 | _audits ??= new List(); 35 | var auditable = new Auditable(type, dateTimeProvider, node); 36 | _audits.Add(auditable.Audit); 37 | return auditable; 38 | } 39 | 40 | /// Emits an event that does not need to track a duration 41 | public void Emit(AuditEvent type) => Add(type, _dateTimeProvider).Dispose(); 42 | 43 | /// Emits an event that does not need to track a duration 44 | public void Emit(AuditEvent type, Node node) => Add(type, _dateTimeProvider, node).Dispose(); 45 | 46 | /// 47 | public int Count => _audits?.Count ?? 0; 48 | } 49 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/Diagnostic.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Diagnostics; 7 | using System.Diagnostics.CodeAnalysis; 8 | 9 | namespace Elastic.Transport.Diagnostics; 10 | 11 | /// 12 | /// Internal subclass of that implements to 13 | /// make it easier to use. 14 | /// 15 | internal class Diagnostic<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TState> : Diagnostic 16 | { 17 | [RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)] 18 | public Diagnostic(string operationName, DiagnosticSource source, TState state) 19 | : base(operationName, source, state) => 20 | EndState = state; 21 | } 22 | 23 | internal class Diagnostic< 24 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TState, 25 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TStateEnd> : Activity, IDisposable 26 | { 27 | internal const string WriteOfTRequiresUnreferencedCode = "Only the properties of the T type will be preserved. Properties of referenced types and properties of derived types may be trimmed."; 28 | 29 | public static Diagnostic Default { get; } = new Diagnostic(); 30 | 31 | private readonly DiagnosticSource _source; 32 | private TStateEnd _endState; 33 | private readonly bool _default; 34 | private bool _disposed; 35 | 36 | private Diagnostic() : base("__NOOP__") => _default = true; 37 | 38 | [RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)] 39 | public Diagnostic(string operationName, DiagnosticSource source, TState state) : base(operationName) 40 | { 41 | _source = source; 42 | _source.StartActivity(SetStartTime(DateTime.UtcNow), state); 43 | } 44 | 45 | public TStateEnd EndState 46 | { 47 | get => _endState; 48 | internal set 49 | { 50 | //do not store state on default instance 51 | if (_default) return; 52 | _endState = value; 53 | } 54 | } 55 | 56 | protected override void Dispose(bool disposing) 57 | { 58 | if (_disposed) return; 59 | 60 | if (disposing) 61 | { 62 | #pragma warning disable IL2026 63 | //_source can be null if Default instance 64 | _source?.StopActivity(SetEndTime(DateTime.UtcNow), EndState); 65 | #pragma warning restore IL2026 66 | } 67 | 68 | _disposed = true; 69 | 70 | base.Dispose(disposing); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/HttpConnectionDiagnosticObserver.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace Elastic.Transport.Diagnostics; 9 | 10 | /// Provides a typed listener to the events that emits 11 | internal sealed class HttpConnectionDiagnosticObserver : TypedDiagnosticObserver 12 | { 13 | /// > 14 | public HttpConnectionDiagnosticObserver( 15 | Action> onNextStart, 16 | Action> onNextEnd, 17 | Action onError = null, 18 | Action onCompleted = null 19 | ) : base(onNextStart, onNextEnd, onError, onCompleted) { } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/OpenTelemetry/SemanticConventions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport.Diagnostics; 6 | 7 | /// 8 | /// Constants for OpenTelemetrySemanticConventions 9 | /// 10 | internal static class SemanticConventions 11 | { 12 | // DATABASE 13 | public const string DbSystem = "db.system"; 14 | public const string DbUser = "db.user"; 15 | 16 | // HTTP 17 | public const string HttpResponseStatusCode = "http.response.status_code"; 18 | public const string HttpRequestMethod = "http.request.method"; 19 | 20 | // SERVER 21 | public const string ServerAddress = "server.address"; 22 | public const string ServerPort = "server.port"; 23 | 24 | // URL 25 | public const string UrlFull = "url.full"; 26 | 27 | // URL 28 | public const string UserAgentOriginal = "user_agent.original"; 29 | } 30 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/RequestPipelineDiagnosticObserver.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace Elastic.Transport.Diagnostics; 9 | 10 | /// Provides a typed listener to actions that takes e.g sniff, ping, or making an API call ; 11 | internal sealed class RequestPipelineDiagnosticObserver : TypedDiagnosticObserver 12 | { 13 | /// 14 | public RequestPipelineDiagnosticObserver( 15 | Action> onNextStart, 16 | Action> onNextEnd, 17 | Action onError = null, 18 | Action onCompleted = null 19 | ) : base(onNextStart, onNextEnd, onError, onCompleted) { } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/SerializerDiagnosticObserver.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace Elastic.Transport.Diagnostics; 9 | 10 | /// Provides a typed listener any time an does a write or read 11 | internal sealed class SerializerDiagnosticObserver : TypedDiagnosticObserver 12 | { 13 | /// 14 | public SerializerDiagnosticObserver( 15 | Action> onNext, 16 | Action onError = null, 17 | Action onCompleted = null 18 | ) : base(onNext, onError, onCompleted) { } 19 | } 20 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Diagnostics/TcpStats.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.ObjectModel; 8 | using System.Net.NetworkInformation; 9 | 10 | namespace Elastic.Transport.Diagnostics; 11 | 12 | /// 13 | /// Gets statistics about TCP connections 14 | /// 15 | internal static class TcpStats 16 | { 17 | private static readonly int StateLength = Enum.GetNames(typeof(TcpState)).Length; 18 | private static readonly ReadOnlyDictionary Empty = new(new Dictionary()); 19 | 20 | /// 21 | /// Gets the active TCP connections 22 | /// 23 | /// TcpConnectionInformation[] 24 | /// Can return `null` when there is a permissions issue retrieving TCP connections. 25 | public static TcpConnectionInformation[]? GetActiveTcpConnections() 26 | { 27 | try 28 | { 29 | return IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections(); 30 | } 31 | catch (NetworkInformationException) // host might not allow this information to be fetched. 32 | { 33 | // ignored 34 | } 35 | 36 | return null; 37 | } 38 | 39 | /// 40 | /// Gets the sum for each state of the active TCP connections 41 | /// 42 | public static ReadOnlyDictionary GetStates() 43 | { 44 | var connections = GetActiveTcpConnections(); 45 | if (connections is null) 46 | { 47 | return Empty; 48 | } 49 | 50 | var states = new Dictionary(StateLength); 51 | 52 | for (var index = 0; index < connections.Length; index++) 53 | { 54 | var connection = connections[index]; 55 | if (states.TryGetValue(connection.State, out var count)) 56 | states[connection.State] = ++count; 57 | else 58 | states.Add(connection.State, 1); 59 | } 60 | 61 | return new ReadOnlyDictionary(states); 62 | } 63 | 64 | /// 65 | /// Gets the TCP statistics for a given network interface component 66 | /// 67 | public static TcpStatistics GetTcpStatistics(NetworkInterfaceComponent version) 68 | { 69 | var properties = IPGlobalProperties.GetIPGlobalProperties(); 70 | switch (version) 71 | { 72 | case NetworkInterfaceComponent.IPv4: 73 | return properties.GetTcpIPv4Statistics(); 74 | case NetworkInterfaceComponent.IPv6: 75 | return properties.GetTcpIPv6Statistics(); 76 | default: 77 | throw new ArgumentException("version"); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Exceptions/UnexpectedTransportException.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Elastic.Transport.Extensions; 8 | 9 | namespace Elastic.Transport; 10 | 11 | /// 12 | /// An exception occured that was not the result of one the well defined exit points as modelled by 13 | /// . This exception will always bubble out. 14 | /// 15 | public class UnexpectedTransportException : TransportException 16 | { 17 | /// 18 | public UnexpectedTransportException(Exception killerException, IReadOnlyCollection? seenExceptions) 19 | : base(PipelineFailure.Unexpected, killerException?.Message ?? "An unexpected exception occurred.", killerException) => 20 | SeenExceptions = seenExceptions ?? EmptyReadOnly.Collection; 21 | 22 | /// 23 | /// Seen Exceptions that we try to failover on before this was thrown. 24 | /// 25 | // ReSharper disable once MemberCanBePrivate.Global 26 | public IReadOnlyCollection SeenExceptions { get; } 27 | } 28 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Extensions/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | resharper_check_namespace_highlighting=warning 3 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Extensions/EmptyEnumerator.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace Elastic.Transport.Extensions; 9 | 10 | internal struct EmptyEnumerator : IEnumerator 11 | { 12 | public T Current => default; 13 | object IEnumerator.Current => Current!; 14 | public bool MoveNext() => false; 15 | 16 | public void Reset() 17 | { 18 | } 19 | 20 | public void Dispose() 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Extensions/EmptyReadonly.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.Linq; 8 | 9 | namespace Elastic.Transport.Extensions; 10 | 11 | internal static class EmptyReadOnlyExtensions 12 | { 13 | public static IReadOnlyCollection ToReadOnlyCollection(this IEnumerable enumerable) => 14 | enumerable == null ? EmptyReadOnly.Collection : new ReadOnlyCollection(enumerable.ToList()); 15 | 16 | public static IReadOnlyCollection ToReadOnlyCollection(this IList enumerable) => 17 | enumerable == null || enumerable.Count == 0 ? EmptyReadOnly.Collection : new ReadOnlyCollection(enumerable); 18 | } 19 | 20 | 21 | internal static class EmptyReadOnly 22 | { 23 | public static readonly IReadOnlyCollection Collection = new ReadOnlyCollection(new TElement[0]); 24 | public static readonly IReadOnlyList List = new List(); 25 | } 26 | 27 | internal static class EmptyReadOnly 28 | { 29 | public static readonly IReadOnlyDictionary Dictionary = new ReadOnlyDictionary(new Dictionary(0)); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Extensions/Fluent.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport.Extensions; 8 | 9 | internal static class Fluent 10 | { 11 | internal static TDescriptor Assign(TDescriptor self, TValue value, Action assign) 12 | where TDescriptor : class, TInterface 13 | { 14 | assign(self, value); 15 | return self; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Extensions/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | // Licensed to the .NET Foundation under one or more agreements. 6 | // The .NET Foundation licenses this file to you under the MIT license. 7 | // See the LICENSE file in the project root for more information 8 | // https://raw.githubusercontent.com/dotnet/core-setup/master/src/managed/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs 9 | 10 | #if NETFRAMEWORK 11 | using System.Runtime.InteropServices; 12 | 13 | namespace Elastic.Transport.Extensions 14 | { 15 | internal static class NativeMethods 16 | { 17 | public static class Windows 18 | { 19 | // This call avoids the shimming Windows does to report old versions 20 | [DllImport("ntdll")] 21 | private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); 22 | 23 | internal static string RtlGetVersion() 24 | { 25 | var osvi = new RTL_OSVERSIONINFOEX(); 26 | osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi); 27 | return RtlGetVersion(out osvi) == 0 28 | ? $"Microsoft Windows {osvi.dwMajorVersion}.{osvi.dwMinorVersion}.{osvi.dwBuildNumber}" 29 | : null; 30 | } 31 | 32 | [StructLayout(LayoutKind.Sequential)] 33 | // ReSharper disable once MemberCanBePrivate.Global 34 | // ReSharper disable InconsistentNaming 35 | // ReSharper disable FieldCanBeMadeReadOnly.Global 36 | internal struct RTL_OSVERSIONINFOEX 37 | { 38 | internal uint dwOSVersionInfoSize; 39 | internal uint dwMajorVersion; 40 | internal uint dwMinorVersion; 41 | internal uint dwBuildNumber; 42 | internal uint dwPlatformId; 43 | 44 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 45 | internal string szCSDVersion; 46 | } 47 | // ReSharper restore InconsistentNaming 48 | // ReSharper restore FieldCanBeMadeReadOnly.Global 49 | } 50 | } 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Extensions/RuntimeInformation.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | // Licensed to the .NET Foundation under one or more agreements. 6 | // The .NET Foundation licenses this file to you under the MIT license. 7 | // See the LICENSE file in the project root for more information 8 | // https://raw.githubusercontent.com/dotnet/core-setup/master/src/managed/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs 9 | 10 | #if NETFRAMEWORK 11 | using System; 12 | using System.Linq; 13 | using System.Reflection; 14 | 15 | namespace Elastic.Transport.Extensions 16 | { 17 | internal static class RuntimeInformation 18 | { 19 | private static string _frameworkDescription; 20 | private static string _osDescription; 21 | 22 | public static string FrameworkDescription 23 | { 24 | get 25 | { 26 | if (_frameworkDescription == null) 27 | { 28 | var assemblyFileVersionAttribute = 29 | ((AssemblyFileVersionAttribute[])Attribute.GetCustomAttributes( 30 | typeof(object).Assembly, 31 | typeof(AssemblyFileVersionAttribute))) 32 | .OrderByDescending(a => a.Version) 33 | .First(); 34 | _frameworkDescription = $".NET Framework {assemblyFileVersionAttribute.Version}"; 35 | } 36 | return _frameworkDescription; 37 | } 38 | } 39 | 40 | public static string OSDescription 41 | { 42 | get 43 | { 44 | if (_osDescription == null) 45 | { 46 | var platform = (int)Environment.OSVersion.Platform; 47 | var isWindows = platform != 4 && platform != 6 && platform != 128; 48 | if (isWindows) 49 | _osDescription = NativeMethods.Windows.RtlGetVersion() ?? "Microsoft Windows"; 50 | else 51 | _osDescription = Environment.OSVersion.VersionString; 52 | } 53 | return _osDescription; 54 | } 55 | } 56 | } 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | internal static class StringExtensions 8 | { 9 | internal static string ToCamelCase(this string s) 10 | { 11 | if (string.IsNullOrEmpty(s)) 12 | return s; 13 | 14 | if (!char.IsUpper(s[0])) 15 | return s; 16 | 17 | var camelCase = char.ToLowerInvariant(s[0]).ToString(); 18 | if (s.Length > 1) 19 | camelCase += s.Substring(1); 20 | 21 | return camelCase; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Extensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | // Adapted from https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/src/Shared/TaskExtensions.cs 6 | // Copyright (c) Microsoft Corporation. All rights reserved. 7 | // Licensed under the MIT License. 8 | 9 | using System; 10 | using System.Diagnostics; 11 | using System.Threading.Tasks; 12 | 13 | namespace Elastic.Transport; 14 | 15 | internal static class TaskExtensions 16 | { 17 | [Conditional("DEBUG")] 18 | private static void VerifyTaskCompleted(bool isCompleted) 19 | { 20 | if (!isCompleted) 21 | { 22 | if (Debugger.IsAttached) 23 | { 24 | Debugger.Break(); 25 | } 26 | // Throw an InvalidOperationException instead of using 27 | // Debug.Assert because that brings down xUnit immediately 28 | throw new InvalidOperationException("Task is not completed"); 29 | } 30 | } 31 | 32 | public static T EnsureCompleted(this ValueTask task) 33 | { 34 | #if DEBUG 35 | VerifyTaskCompleted(task.IsCompleted); 36 | #endif 37 | #pragma warning disable VSTHRD002 // Avoid problematic synchronous waits 38 | return task.GetAwaiter().GetResult(); 39 | #pragma warning restore VSTHRD002 // Avoid problematic synchronous waits 40 | } 41 | 42 | public static void EnsureCompleted(this ValueTask task) 43 | { 44 | #if DEBUG 45 | VerifyTaskCompleted(task.IsCompleted); 46 | #endif 47 | #pragma warning disable VSTHRD002 // Avoid problematic synchronous waits 48 | task.GetAwaiter().GetResult(); 49 | #pragma warning restore VSTHRD002 // Avoid problematic synchronous waits 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Elastic.Transport/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | #if !NET6_0_OR_GREATER 6 | 7 | namespace System.Runtime.CompilerServices; 8 | 9 | using System.ComponentModel; 10 | 11 | /// 12 | /// Reserved to be used by the compiler for tracking metadata. 13 | /// This class should not be used by developers in source code. 14 | /// 15 | [EditorBrowsable(EditorBrowsableState.Never)] 16 | internal static class IsExternalInit 17 | { 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | resharper_check_namespace_highlighting=warning 3 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/Elasticsearch/ElasticsearchConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport.Products.Elasticsearch; 8 | 9 | /// 10 | /// Allows you to control how behaves and where/how it connects to Elasticsearch 11 | /// 12 | public record ElasticsearchConfiguration : TransportConfiguration 13 | { 14 | /// 15 | /// Creates a new instance of 16 | /// 17 | /// The root of the Elastic stack product node we want to connect to. Defaults to http://localhost:9200 18 | public ElasticsearchConfiguration(Uri? uri = null) 19 | : base(new SingleNodePool(uri ?? new Uri("http://localhost:9200")), productRegistration: ElasticsearchProductRegistration.Default ) { } 20 | 21 | /// 22 | /// Sets up the client to communicate to Elastic Cloud using , 23 | /// documentation for more information on how to get your Cloud ID 24 | /// 25 | public ElasticsearchConfiguration(string cloudId, BasicAuthentication credentials) 26 | : base(new CloudNodePool(cloudId, credentials), productRegistration: ElasticsearchProductRegistration.Default) { } 27 | 28 | /// 29 | /// Sets up the client to communicate to Elastic Cloud using , 30 | /// documentation for more information on how to get your Cloud ID 31 | /// 32 | public ElasticsearchConfiguration(string cloudId, ApiKey credentials) 33 | : base(new CloudNodePool(cloudId, credentials), productRegistration: ElasticsearchProductRegistration.Default) { } 34 | 35 | /// Sets up the client to communicate to Elastic Cloud. 36 | public ElasticsearchConfiguration(Uri cloudEndpoint, BasicAuthentication credentials) 37 | : base(new CloudNodePool(cloudEndpoint, credentials), productRegistration: ElasticsearchProductRegistration.Default) { } 38 | 39 | /// Sets up the client to communicate to Elastic Cloud. 40 | public ElasticsearchConfiguration(Uri cloudEndpoint, ApiKey credentials) 41 | : base(new CloudNodePool(cloudEndpoint, credentials), productRegistration: ElasticsearchProductRegistration.Default) { } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/Elasticsearch/ElasticsearchNodeFeatures.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace Elastic.Transport.Products.Elasticsearch; 9 | 10 | /// 11 | /// Encodes the features will register on 12 | /// . These static strings make it easier to inspect if features are enabled 13 | /// using 14 | /// 15 | public static class ElasticsearchNodeFeatures 16 | { 17 | /// Indicates whether this node holds data, defaults to true when unknown/unspecified 18 | public const string HoldsData = "node.data"; 19 | /// Whether HTTP is enabled on the node or not 20 | public const string HttpEnabled = "node.http"; 21 | /// Indicates whether this node is allowed to run ingest pipelines, defaults to true when unknown/unspecified 22 | public const string IngestEnabled = "node.ingest"; 23 | /// Indicates whether this node is master eligible, defaults to true when unknown/unspecified 24 | public const string MasterEligible = "node.master"; 25 | 26 | /// The default collection of features, which enables ALL Features 27 | public static readonly IReadOnlyCollection Default = 28 | new[] { HoldsData, MasterEligible, IngestEnabled, HttpEnabled }.ToList().AsReadOnly(); 29 | 30 | /// The node only has the and features 31 | public static readonly IReadOnlyCollection MasterEligibleOnly = 32 | new[] { MasterEligible, HttpEnabled }.ToList().AsReadOnly(); 33 | 34 | /// The node has all features EXCEPT 35 | // ReSharper disable once UnusedMember.Global 36 | public static readonly IReadOnlyCollection NotMasterEligible = 37 | Default.Except(new[] { MasterEligible }).ToList().AsReadOnly(); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/Elasticsearch/ErrorSerializationContext.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | using System.Text.Json.Serialization.Metadata; 9 | 10 | namespace Elastic.Transport.Products.Elasticsearch; 11 | 12 | /// Adds support for serializing Elasticsearch errors using 13 | [JsonSerializable(typeof(JsonElement))] 14 | [JsonSerializable(typeof(Error))] 15 | [JsonSerializable(typeof(ErrorCause))] 16 | [JsonSerializable(typeof(IReadOnlyCollection))] 17 | [JsonSerializable(typeof(ElasticsearchServerError))] 18 | [JsonSerializable(typeof(ElasticsearchResponse))] 19 | [JsonSerializable(typeof(object[]))] 20 | [JsonSerializable(typeof(Dictionary))] 21 | [JsonSerializable(typeof(IReadOnlyDictionary))] 22 | [JsonSerializable(typeof(string))] 23 | [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Default, UseStringEnumConverter = true)] 24 | public partial class ElasticsearchTransportSerializerContext : JsonSerializerContext; 25 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/Elasticsearch/Failures/ShardFailure.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Runtime.Serialization; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace Elastic.Transport.Products.Elasticsearch; 9 | 10 | /// Represents a failure that occurred on a shard involved in the request 11 | [DataContract] 12 | public sealed class ShardFailure 13 | { 14 | /// This index this shard belongs to 15 | [JsonPropertyName("index")] 16 | public string Index { get; set; } 17 | 18 | /// The node the shard is currently allocated on 19 | [JsonPropertyName("node")] 20 | public string Node { get; set; } 21 | 22 | /// 23 | /// The java exception that caused the shard to fail 24 | /// 25 | [JsonPropertyName("reason")] 26 | public ErrorCause Reason { get; set; } 27 | 28 | /// The shard number that failed 29 | [JsonPropertyName("shard")] 30 | [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] 31 | public int? Shard { get; set; } 32 | 33 | /// The status of the shard when the exception occured 34 | [JsonPropertyName("status")] 35 | public string Status { get; set; } 36 | } 37 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/Elasticsearch/Sniff/NodeInfo.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text.Json; 8 | 9 | namespace Elastic.Transport.Products.Elasticsearch; 10 | 11 | internal sealed class NodeInfo 12 | { 13 | public string build_hash { get; set; } 14 | public string host { get; set; } 15 | public NodeInfoHttp http { get; set; } 16 | public string ip { get; set; } 17 | public string name { get; set; } 18 | public IList roles { get; set; } 19 | public IDictionary settings { get; set; } 20 | public string transport_address { get; set; } 21 | public string version { get; set; } 22 | internal bool HoldsData => roles?.Contains("data") ?? false; 23 | 24 | internal bool HttpEnabled 25 | { 26 | get 27 | { 28 | if (settings != null && settings.TryGetValue("http.enabled", out var httpEnabled)) 29 | { 30 | if (httpEnabled is JsonElement e) 31 | return e.GetBoolean(); 32 | return Convert.ToBoolean(httpEnabled); 33 | } 34 | 35 | return http != null; 36 | } 37 | } 38 | 39 | internal bool IngestEnabled => roles?.Contains("ingest") ?? false; 40 | 41 | internal bool MasterEligible => roles?.Contains("master") ?? false; 42 | } 43 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/Elasticsearch/Sniff/NodeInfoHttp.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace Elastic.Transport.Products.Elasticsearch; 8 | 9 | internal sealed class NodeInfoHttp 10 | { 11 | public IList bound_address { get; set; } 12 | public string publish_address { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/Elasticsearch/Sniff/SniffParser.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Text.RegularExpressions; 7 | using Elastic.Transport.Extensions; 8 | 9 | namespace Elastic.Transport.Products.Elasticsearch; 10 | 11 | /// 12 | /// Elasticsearch returns addresses in the form of 13 | /// [fqdn]/ip:port number 14 | /// This helper parses it to 15 | /// 16 | public static class SniffParser 17 | { 18 | /// A regular expression that captures fqdn, ip and por 19 | public static Regex AddressRegex { get; } = 20 | new Regex(@"^((?[^/]+)/)?(?[^:]+|\[[\da-fA-F:\.]+\]):(?\d+)$"); 21 | 22 | /// 23 | /// Elasticsearch returns addresses in the form of 24 | /// [fqdn]/ip:port number 25 | /// This helper parses it to 26 | /// 27 | public static Uri ParseToUri(string boundAddress, bool forceHttp) 28 | { 29 | if (boundAddress == null) throw new ArgumentNullException(nameof(boundAddress)); 30 | 31 | var suffix = forceHttp ? "s" : string.Empty; 32 | var match = AddressRegex.Match(boundAddress); 33 | if (!match.Success) throw new Exception($"Can not parse bound_address: {boundAddress} to Uri"); 34 | 35 | var fqdn = match.Groups["fqdn"].Value.Trim(); 36 | var ip = match.Groups["ip"].Value.Trim(); 37 | var port = match.Groups["port"].Value.Trim(); 38 | var host = !fqdn.IsNullOrEmpty() ? fqdn : ip; 39 | 40 | return new Uri($"http{suffix}://{host}:{port}"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Products/Elasticsearch/Sniff/SniffResponse.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.Linq; 8 | using static Elastic.Transport.Products.Elasticsearch.ElasticsearchNodeFeatures; 9 | 10 | namespace Elastic.Transport.Products.Elasticsearch; 11 | 12 | internal sealed class SniffResponse : TransportResponse 13 | { 14 | // ReSharper disable InconsistentNaming 15 | public string cluster_name { get; set; } 16 | 17 | public Dictionary nodes { get; set; } 18 | 19 | public IEnumerable ToNodes(bool forceHttp = false) 20 | { 21 | foreach (var kv in nodes.Where(n => n.Value.HttpEnabled)) 22 | { 23 | var info = kv.Value; 24 | var httpEndpoint = info.http?.publish_address; 25 | if (string.IsNullOrWhiteSpace(httpEndpoint)) 26 | httpEndpoint = kv.Value.http?.bound_address.FirstOrDefault(); 27 | if (string.IsNullOrWhiteSpace(httpEndpoint)) 28 | continue; 29 | 30 | var uri = SniffParser.ParseToUri(httpEndpoint, forceHttp); 31 | var features = new List(); 32 | if (info.MasterEligible) 33 | features.Add(MasterEligible); 34 | if (info.HoldsData) 35 | features.Add(HoldsData); 36 | if (info.IngestEnabled) 37 | features.Add(IngestEnabled); 38 | if (info.HttpEnabled) 39 | features.Add(HttpEnabled); 40 | 41 | var node = new Node(uri, features) 42 | { 43 | Name = info.name, Id = kv.Key, Settings = new ReadOnlyDictionary(info.settings) 44 | }; 45 | yield return node; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Properties/ClsCompliancy.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | [assembly: CLSCompliant(true)] 8 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/Body/PostData.ByteArray.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Elastic.Transport; 10 | 11 | public abstract partial class PostData 12 | { 13 | /// 14 | /// Create a instance that will write to the output 15 | /// 16 | // ReSharper disable once MemberCanBePrivate.Global 17 | public static PostData Bytes(byte[] bytes) => new PostDataByteArray(bytes); 18 | 19 | private class PostDataByteArray : PostData 20 | { 21 | protected internal PostDataByteArray(byte[] item) 22 | { 23 | WrittenBytes = item; 24 | Type = PostType.ByteArray; 25 | } 26 | 27 | public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) 28 | { 29 | if (WrittenBytes == null) return; 30 | 31 | MemoryStream? buffer = null; 32 | 33 | if (!disableDirectStreaming) 34 | writableStream.Write(WrittenBytes, 0, WrittenBytes.Length); 35 | else 36 | buffer = settings.MemoryStreamFactory.Create(WrittenBytes); 37 | 38 | FinishStream(writableStream, buffer, disableDirectStreaming); 39 | } 40 | 41 | public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) 42 | { 43 | if (WrittenBytes == null) return; 44 | 45 | MemoryStream? buffer = null; 46 | 47 | if (!disableDirectStreaming) 48 | await writableStream.WriteAsync(WrittenBytes, 0, WrittenBytes.Length, cancellationToken) 49 | .ConfigureAwait(false); 50 | else 51 | buffer = settings.MemoryStreamFactory.Create(WrittenBytes); 52 | 53 | await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/Body/PostData.ReadOnlyMemory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | #if !NETSTANDARD2_0 && !NETFRAMEWORK 6 | using System; 7 | using System.IO; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace Elastic.Transport 12 | { 13 | public abstract partial class PostData 14 | { 15 | /// 16 | /// Create a instance that will write the to the output 17 | /// 18 | public static PostData ReadOnlyMemory(ReadOnlyMemory bytes) => new PostDataReadOnlyMemory(bytes); 19 | 20 | private class PostDataReadOnlyMemory : PostData 21 | { 22 | private readonly ReadOnlyMemory _memoryOfBytes; 23 | 24 | protected internal PostDataReadOnlyMemory(ReadOnlyMemory item) 25 | { 26 | _memoryOfBytes = item; 27 | Type = PostType.ReadOnlyMemory; 28 | } 29 | 30 | public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) 31 | { 32 | if (_memoryOfBytes.IsEmpty) return; 33 | 34 | MemoryStream? buffer = null; 35 | 36 | if (!disableDirectStreaming) 37 | writableStream.Write(_memoryOfBytes.Span); 38 | else 39 | { 40 | WrittenBytes ??= _memoryOfBytes.Span.ToArray(); 41 | buffer = settings.MemoryStreamFactory.Create(WrittenBytes); 42 | } 43 | FinishStream(writableStream, buffer, disableDirectStreaming); 44 | } 45 | 46 | public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) 47 | { 48 | if (_memoryOfBytes.IsEmpty) return; 49 | 50 | MemoryStream? buffer = null; 51 | 52 | if (!disableDirectStreaming) 53 | writableStream.Write(_memoryOfBytes.Span); 54 | else 55 | { 56 | WrittenBytes ??= _memoryOfBytes.Span.ToArray(); 57 | buffer = settings.MemoryStreamFactory.Create(WrittenBytes); 58 | } 59 | 60 | await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); 61 | } 62 | } 63 | } 64 | } 65 | #endif 66 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/Body/PostData.Serializable.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using static Elastic.Transport.SerializationFormatting; 9 | 10 | namespace Elastic.Transport; 11 | 12 | public abstract partial class PostData 13 | { 14 | /// 15 | /// Create a instance that will serialize using 16 | /// 17 | /// 18 | public static PostData Serializable(T data) => new SerializableData(data); 19 | 20 | private class SerializableData : PostData 21 | { 22 | private readonly T _serializable; 23 | 24 | public SerializableData(T item) 25 | { 26 | Type = PostType.Serializable; 27 | _serializable = item; 28 | } 29 | 30 | public static implicit operator SerializableData(T serializableData) => new(serializableData); 31 | 32 | public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) 33 | { 34 | MemoryStream buffer = null; 35 | var stream = writableStream; 36 | BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref stream); 37 | 38 | var indent = settings.PrettyJson ? Indented : None; 39 | settings.RequestResponseSerializer.Serialize(_serializable, stream, indent); 40 | 41 | FinishStream(writableStream, buffer, disableDirectStreaming); 42 | } 43 | 44 | public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) 45 | { 46 | MemoryStream buffer = null; 47 | var stream = writableStream; 48 | BufferIfNeeded(settings.MemoryStreamFactory, disableDirectStreaming, ref buffer, ref stream); 49 | 50 | var indent = settings.PrettyJson ? Indented : None; 51 | await settings.RequestResponseSerializer 52 | .SerializeAsync(_serializable, stream, indent, cancellationToken) 53 | .ConfigureAwait(false); 54 | 55 | await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/Body/PostData.String.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Elastic.Transport.Extensions; 9 | 10 | namespace Elastic.Transport; 11 | 12 | public abstract partial class PostData 13 | { 14 | /// 15 | /// Create a instance that will write to the output 16 | /// 17 | // ReSharper disable once MemberCanBePrivate.Global 18 | public static PostData String(string serializedString) => new PostDataString(serializedString); 19 | 20 | /// 21 | /// string implicitly converts to so you do not have to use the static 22 | /// factory method 23 | /// 24 | public static implicit operator PostData(string literalString) => String(literalString); 25 | 26 | private class PostDataString : PostData 27 | { 28 | private readonly string _literalString; 29 | 30 | protected internal PostDataString(string item) 31 | { 32 | _literalString = item; 33 | Type = PostType.LiteralString; 34 | } 35 | 36 | public override void Write(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming) 37 | { 38 | if (string.IsNullOrEmpty(_literalString)) return; 39 | 40 | MemoryStream? buffer = null; 41 | 42 | var stringBytes = WrittenBytes ?? _literalString.Utf8Bytes(); 43 | WrittenBytes ??= stringBytes; 44 | if (!disableDirectStreaming) 45 | writableStream.Write(stringBytes, 0, stringBytes.Length); 46 | else 47 | buffer = settings.MemoryStreamFactory.Create(stringBytes); 48 | 49 | FinishStream(writableStream, buffer, disableDirectStreaming); 50 | } 51 | 52 | public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, bool disableDirectStreaming, CancellationToken cancellationToken) 53 | { 54 | if (string.IsNullOrEmpty(_literalString)) return; 55 | 56 | MemoryStream? buffer = null; 57 | 58 | var stringBytes = WrittenBytes ?? _literalString.Utf8Bytes(); 59 | WrittenBytes ??= stringBytes; 60 | if (!disableDirectStreaming) 61 | await writableStream.WriteAsync(stringBytes, 0, stringBytes.Length, cancellationToken) 62 | .ConfigureAwait(false); 63 | else 64 | buffer = settings.MemoryStreamFactory.Create(stringBytes); 65 | 66 | await FinishStreamAsync(writableStream, buffer, disableDirectStreaming, cancellationToken).ConfigureAwait(false); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/IUrlParameter.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// Implementers define an object that can be serialized as a query string parameter 8 | public interface IUrlParameter 9 | { 10 | /// Get the string representation using 11 | public string GetString(ITransportConfiguration settings); 12 | } 13 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/MetaData/MetaDataHeader.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Text; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | /// 11 | /// 12 | public sealed class MetaDataHeader 13 | { 14 | private const char _separator = ','; 15 | 16 | private readonly string _headerValue; 17 | 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | public MetaDataHeader(VersionInfo version, string serviceIdentifier, bool isAsync) 25 | { 26 | if (serviceIdentifier != "et") 27 | TransportVersion = ReflectionVersionInfo.Create().ToString(); 28 | 29 | ClientVersion = version.ToMetadataHeaderValue(); 30 | RuntimeVersion = new RuntimeVersionInfo().ToString(); 31 | ServiceIdentifier = serviceIdentifier; 32 | 33 | // This code is expected to be called infrequently so we're not concerned with over optimising this 34 | 35 | var builder = new StringBuilder(64) 36 | .Append(serviceIdentifier).Append('=').Append(ClientVersion).Append(_separator) 37 | .Append("a=").Append(isAsync ? '1' : '0').Append(_separator) 38 | .Append("net=").Append(RuntimeVersion).Append(_separator) 39 | .Append(_httpClientIdentifier).Append('=').Append(RuntimeVersion); 40 | 41 | if (!string.IsNullOrEmpty(TransportVersion)) 42 | builder.Append(_separator).Append("t=").Append(TransportVersion); 43 | 44 | _headerValue = builder.ToString(); 45 | } 46 | 47 | private static readonly string _httpClientIdentifier = 48 | #if !NETFRAMEWORK 49 | ConnectionInfo.UsingCurlHandler ? "cu" : "so"; 50 | #else 51 | "wr"; 52 | #endif 53 | 54 | /// 55 | /// 56 | /// 57 | public string ServiceIdentifier { get; private set; } 58 | 59 | /// 60 | /// 61 | /// 62 | public string ClientVersion { get; private set; } 63 | 64 | /// 65 | /// 66 | /// 67 | public string TransportVersion { get; private set; } 68 | 69 | /// 70 | /// 71 | /// 72 | public string RuntimeVersion { get; private set; } 73 | 74 | /// 75 | /// 76 | /// 77 | /// 78 | public override string ToString() => _headerValue; 79 | } 80 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/MetaData/MetaHeaderProvider.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// 8 | /// Injects metadata headers into all outgoing requests. 9 | /// 10 | public abstract class MetaHeaderProvider 11 | { 12 | /// 13 | /// The list of all s for the provider. 14 | /// 15 | public abstract MetaHeaderProducer[] Producers { get; } 16 | } 17 | 18 | /// 19 | /// Injects a metadata headers into all outgoing requests. 20 | /// 21 | public abstract class MetaHeaderProducer 22 | { 23 | /// 24 | /// The header name. 25 | /// 26 | public abstract string HeaderName { get; } 27 | 28 | /// 29 | /// Produces the header value based on current outgoing . 30 | /// 31 | public abstract string? ProduceHeaderValue(BoundConfiguration boundConfiguration, bool isAsync); 32 | } 33 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/MetaData/RequestMetaData.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | /// Holds meta data about a client request. 11 | /// 12 | public sealed class RequestMetaData 13 | { 14 | /// 15 | /// Reserved key for a meta data entry which identifies the helper which produced the request. 16 | /// 17 | internal const string HelperKey = "helper"; 18 | 19 | private Dictionary? _metaDataItems; 20 | 21 | internal bool TryAddMetaData(string key, string value) 22 | { 23 | _metaDataItems ??= []; 24 | 25 | #if NETSTANDARD2_1 || NET6_0_OR_GREATER 26 | return _metaDataItems.TryAdd(key, value); 27 | #else 28 | if (_metaDataItems.ContainsKey(key)) 29 | return false; 30 | 31 | _metaDataItems.Add(key, value); 32 | return true; 33 | #endif 34 | } 35 | 36 | /// Retrieves a read-only dictionary of metadata items associated with a client request. 37 | public IReadOnlyDictionary Items => _metaDataItems; 38 | } 39 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/MetaData/RequestMetaDataExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | /// 11 | /// 12 | public static class RequestMetaDataExtensions 13 | { 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | public static void AddHelper(this RequestMetaData metaData, string helperValue) 21 | { 22 | if (!metaData.TryAddMetaData(RequestMetaData.HelperKey, helperValue)) 23 | throw new InvalidOperationException("A helper value has already been added."); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Requests/MetaData/VersionInfo.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Transport.Extensions; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | /// 11 | /// 12 | public abstract class VersionInfo 13 | { 14 | private readonly SemVersion _version; 15 | 16 | /// 17 | /// 18 | /// 19 | public int Major => _version.Major; 20 | 21 | /// 22 | /// 23 | /// 24 | public int Minor => _version.Minor; 25 | 26 | /// 27 | /// 28 | /// 29 | public int Patch => _version.Patch; 30 | 31 | /// 32 | /// 33 | /// 34 | public string? Prerelease => _version.Prerelease; 35 | 36 | /// 37 | /// 38 | /// 39 | public string? Metadata => _version.Metadata; 40 | 41 | /// 42 | /// 43 | /// 44 | public bool IsPrerelease => !string.IsNullOrEmpty(_version.Prerelease); 45 | 46 | /// 47 | /// 48 | /// 49 | /// 50 | protected VersionInfo(SemVersion version) => _version = version; 51 | 52 | /// Returns the full version as a semantic version number 53 | public override string ToString() => _version.ToString(); 54 | 55 | /// Returns the version in a way that safe to emit as telemetry 56 | public string ToMetadataHeaderValue() 57 | { 58 | var prefix = $"{Major}.{Minor}.{Patch}"; 59 | 60 | return IsPrerelease ? $"{prefix}p" : prefix; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/BufferedResponseHelpers.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | 7 | namespace Elastic.Transport; 8 | 9 | internal class BufferedResponseHelpers 10 | { 11 | public const int BufferSize = 81920; 12 | 13 | public static byte[] SwapStreams(ref Stream responseStream, ref MemoryStream ms, bool disposeOriginal = false) 14 | { 15 | var bytes = ms.ToArray(); 16 | 17 | if (disposeOriginal) 18 | responseStream.Dispose(); 19 | 20 | responseStream = ms; 21 | responseStream.Position = 0; 22 | return bytes; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Dynamic/DynamicResponse.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Dynamic; 7 | 8 | namespace Elastic.Transport; 9 | 10 | /// 11 | /// A type of response that makes it easier to work with responses in an untyped fashion. 12 | /// 13 | /// It exposes the body as which is `dynamic` through 14 | /// 15 | /// Since `dynamic` can be scary in .NET this response also exposes a safe traversal mechanism under 16 | /// which support an xpath'esque syntax to fish for values in the returned json. 17 | /// 18 | /// 19 | public sealed class DynamicResponse : TransportResponse 20 | { 21 | /// 22 | public DynamicResponse() { } 23 | 24 | /// 25 | public DynamicResponse(DynamicDictionary dictionary) 26 | { 27 | Body = dictionary; 28 | Dictionary = dictionary; 29 | } 30 | 31 | private DynamicDictionary Dictionary { get; } 32 | 33 | /// 34 | /// Traverses data using path notation. 35 | /// e.g some.deep.nested.json.path 36 | /// A special lookup is available for ANY key using _arbitrary_key_ e.g some.deep._arbitrary_key_.json.path which will traverse into the first key 37 | /// 38 | /// path into the stored object, keys are separated with a dot and the last key is returned as T 39 | /// 40 | /// T or default 41 | public T Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string path) => Dictionary.Get(path); 42 | } 43 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/ErrorResponse.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// 8 | /// Base class for types representing client specific errors. This may be provided by clients to be used for deserialisation of the HTTP body for non-success status codes. 9 | /// 10 | public abstract class ErrorResponse 11 | { 12 | internal ErrorResponse() { } 13 | 14 | /// 15 | /// May be called by transport to establish whether the instance represents a valid, complete error. 16 | /// This may not always be the case if the error is partially deserialised on the response. 17 | /// 18 | public abstract bool HasError(); 19 | } 20 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Special/BytesResponse.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | 7 | namespace Elastic.Transport; 8 | 9 | /// 10 | /// A response that exposes the response as byte array 11 | /// 12 | public sealed class BytesResponse : TransportResponse 13 | { 14 | /// 15 | public BytesResponse() => Body = Array.Empty(); 16 | 17 | /// 18 | public BytesResponse(byte[] body) => Body = body; 19 | } 20 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Special/BytesResponseBuilder.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Elastic.Transport; 10 | 11 | internal class BytesResponseBuilder : TypedResponseBuilder 12 | { 13 | protected override BytesResponse Build(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, long contentLength) => 14 | BuildCoreAsync(false, apiCallDetails, boundConfiguration, responseStream).EnsureCompleted(); 15 | 16 | protected override Task BuildAsync(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, long contentLength, CancellationToken cancellationToken = default) => 17 | BuildCoreAsync(true, apiCallDetails, boundConfiguration, responseStream, cancellationToken).AsTask(); 18 | 19 | private static async ValueTask BuildCoreAsync(bool isAsync, ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, CancellationToken cancellationToken = default) 20 | { 21 | BytesResponse response; 22 | 23 | if (apiCallDetails.ResponseBodyInBytes is not null) 24 | { 25 | response = new BytesResponse(apiCallDetails.ResponseBodyInBytes); 26 | return response; 27 | } 28 | 29 | var tempStream = boundConfiguration.MemoryStreamFactory.Create(); 30 | await responseStream.CopyToAsync(tempStream, BufferedResponseHelpers.BufferSize, cancellationToken).ConfigureAwait(false); 31 | apiCallDetails.ResponseBodyInBytes = BufferedResponseHelpers.SwapStreams(ref responseStream, ref tempStream); 32 | response = new BytesResponse(apiCallDetails.ResponseBodyInBytes); 33 | 34 | #if NET6_0_OR_GREATER 35 | await responseStream.DisposeAsync().ConfigureAwait(false); 36 | #else 37 | responseStream.Dispose(); 38 | #endif 39 | 40 | return response; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Special/StreamResponse.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | 8 | namespace Elastic.Transport; 9 | 10 | /// 11 | /// A response that exposes the response as a . 12 | /// 13 | /// MUST be disposed after use to ensure the HTTP connection is freed for reuse. 14 | /// 15 | /// 16 | public sealed class StreamResponse : StreamResponseBase, IDisposable 17 | { 18 | /// 19 | public StreamResponse() : base(Stream.Null) => 20 | ContentType = string.Empty; 21 | 22 | /// 23 | public StreamResponse(Stream body, string? contentType) : base(body) => 24 | ContentType = contentType ?? string.Empty; 25 | 26 | /// 27 | /// The MIME type of the response, if present. 28 | /// 29 | public string ContentType { get; } 30 | 31 | /// 32 | /// The raw response stream. 33 | /// 34 | public Stream Body => Stream; 35 | 36 | /// 37 | protected internal override bool LeaveOpen => true; 38 | } 39 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Special/StreamResponseBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.IO; 7 | using Elastic.Transport.Extensions; 8 | 9 | namespace Elastic.Transport; 10 | 11 | /// 12 | /// A base class for implementing responses that access the raw response stream. 13 | /// 14 | public abstract class StreamResponseBase : TransportResponse, IDisposable 15 | { 16 | /// 17 | protected internal override bool LeaveOpen => true; 18 | 19 | /// 20 | /// The raw response stream from the HTTP layer. 21 | /// 22 | /// 23 | /// MUST be disposed to release the underlying HTTP connection for reuse. 24 | /// 25 | protected Stream Stream { get; } 26 | 27 | /// 28 | /// Indicates that the response has been disposed and it is not longer safe to access the stream. 29 | /// 30 | protected bool Disposed { get; private set; } 31 | 32 | /// 33 | public StreamResponseBase(Stream responseStream) 34 | { 35 | responseStream.ThrowIfNull(nameof(responseStream)); 36 | Stream = responseStream; 37 | } 38 | 39 | /// 40 | /// Disposes the underlying stream. 41 | /// 42 | /// 43 | protected virtual void Dispose(bool disposing) 44 | { 45 | if (!Disposed) 46 | { 47 | if (disposing) 48 | { 49 | Stream?.Dispose(); 50 | 51 | if (LinkedDisposables is not null) 52 | { 53 | foreach (var disposable in LinkedDisposables) 54 | disposable?.Dispose(); 55 | } 56 | } 57 | 58 | Disposed = true; 59 | } 60 | } 61 | 62 | /// 63 | /// Disposes the underlying stream. 64 | /// 65 | public void Dispose() 66 | { 67 | Dispose(disposing: true); 68 | GC.SuppressFinalize(this); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Special/StreamResponseBuilder.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Elastic.Transport; 10 | 11 | internal class StreamResponseBuilder : TypedResponseBuilder 12 | { 13 | protected override StreamResponse Build(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, long contentLength) => 14 | new(responseStream, contentType); 15 | 16 | protected override Task BuildAsync(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, 17 | long contentLength, CancellationToken cancellationToken = default) => 18 | Task.FromResult(new StreamResponse(responseStream, contentType)); 19 | } 20 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Special/StringResponse.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// 8 | /// A response that exposes the response as . 9 | /// 10 | public sealed class StringResponse : TransportResponse 11 | { 12 | /// 13 | public StringResponse() => Body = string.Empty; 14 | 15 | /// 16 | public StringResponse(string body) => Body = body; 17 | } 18 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Special/VoidResponse.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport; 6 | 7 | /// 8 | /// A special response that omits reading the response from the server after reading the headers. 9 | /// 10 | public sealed class VoidResponse : TransportResponse 11 | { 12 | /// 13 | public VoidResponse() => Body = new VoidBody(); 14 | 15 | /// 16 | /// A static instance that can be reused. 17 | /// 18 | public static VoidResponse Default { get; } = new VoidResponse(); 19 | 20 | /// 21 | /// A class that represents the absence of having read the servers response to completion. 22 | /// 23 | public class VoidBody { } 24 | } 25 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/Special/VoidResponseBuilder.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Elastic.Transport; 10 | 11 | internal class VoidResponseBuilder : TypedResponseBuilder 12 | { 13 | protected override VoidResponse Build(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, long contentLength) => 14 | VoidResponse.Default; 15 | 16 | protected override Task BuildAsync(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, long contentLength, 17 | CancellationToken cancellationToken = default) => 18 | Task.FromResult(VoidResponse.Default); 19 | } 20 | -------------------------------------------------------------------------------- /src/Elastic.Transport/Responses/TypedResponseBuilder.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Elastic.Transport; 10 | 11 | /// 12 | /// A builder for a specific type. 13 | /// 14 | /// 15 | public abstract class TypedResponseBuilder : IResponseBuilder 16 | { 17 | bool IResponseBuilder.CanBuild() => typeof(TResponse) == typeof(T); 18 | 19 | /// 20 | protected abstract TResponse? Build(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, long contentLength); 21 | 22 | T IResponseBuilder.Build(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, long contentLength) => 23 | Build(apiCallDetails, boundConfiguration, responseStream, contentType, contentLength) as T; 24 | 25 | /// 26 | protected abstract Task BuildAsync(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, 27 | string contentType, long contentLength, CancellationToken cancellationToken = default); 28 | 29 | Task IResponseBuilder.BuildAsync(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration, Stream responseStream, string contentType, 30 | long contentLength, CancellationToken cancellationToken) => 31 | BuildAsync(apiCallDetails, boundConfiguration, responseStream, contentType, contentLength, cancellationToken) as Task; 32 | } 33 | -------------------------------------------------------------------------------- /tests/Elastic.Elasticsearch.IntegrationTests/DefaultCluster.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Ephemeral; 6 | using Elastic.Elasticsearch.Managed; 7 | using Elastic.Elasticsearch.Xunit; 8 | using Elastic.Transport; 9 | using Elastic.Transport.Products.Elasticsearch; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | using static Elastic.Elasticsearch.Ephemeral.ClusterAuthentication; 13 | 14 | [assembly: TestFramework("Elastic.Elasticsearch.Xunit.Sdk.ElasticTestFramework", "Elastic.Elasticsearch.Xunit")] 15 | 16 | namespace Elastic.Elasticsearch.IntegrationTests; 17 | 18 | /// Declare our cluster that we want to inject into our test classes 19 | public class DefaultCluster : XunitClusterBase 20 | { 21 | public DefaultCluster() : this(new XunitClusterConfiguration(Version) { StartingPortNumber = 9202, AutoWireKnownProxies = true }) { } 22 | 23 | public DefaultCluster(XunitClusterConfiguration xunitClusterConfiguration) : base(xunitClusterConfiguration) { } 24 | 25 | protected static string Version => "8.7.0"; 26 | 27 | public ITransport CreateClient(ITestOutputHelper output) => 28 | this.GetOrAddClient(cluster => 29 | { 30 | var nodes = NodesUris(); 31 | var nodePool = new StaticNodePool(nodes); 32 | var settings = new TransportConfigurationDescriptor(nodePool, productRegistration: ElasticsearchProductRegistration.Default) 33 | .RequestTimeout(TimeSpan.FromSeconds(5)) 34 | .OnRequestCompleted(d => 35 | { 36 | try 37 | { 38 | output.WriteLine(d.DebugInformation); 39 | } 40 | catch 41 | { 42 | // ignored 43 | } 44 | }) 45 | .EnableDebugMode(); 46 | if (ClusterConfiguration.Features.HasFlag(ClusterFeatures.Security)) 47 | settings = settings.Authentication(new BasicAuthentication(Admin.Username, Admin.Password)); 48 | if (cluster.DetectedProxy != DetectedProxySoftware.None) 49 | settings = settings.Proxy(new Uri("http://localhost:8080")); 50 | if (ClusterConfiguration.Features.HasFlag(ClusterFeatures.SSL)) 51 | settings = settings.ServerCertificateValidationCallback(CertificateValidations.AllowAll); 52 | 53 | return new DistributedTransport(settings); 54 | }); 55 | } 56 | 57 | public class SecurityCluster : DefaultCluster 58 | { 59 | public SecurityCluster() : base(new XunitClusterConfiguration(Version, ClusterFeatures.Security | ClusterFeatures.SSL | ClusterFeatures.XPack) 60 | { 61 | StartingPortNumber = 9202 62 | }) { } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Elastic.Elasticsearch.IntegrationTests/DefaultClusterTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Transport; 6 | using FluentAssertions; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | using static Elastic.Transport.HttpMethod; 10 | 11 | namespace Elastic.Elasticsearch.IntegrationTests; 12 | 13 | public class DefaultClusterTests : IntegrationTestBase 14 | { 15 | public DefaultClusterTests(DefaultCluster cluster, ITestOutputHelper output) : base(cluster, output) { } 16 | 17 | [Fact] 18 | public async Task AsyncRequestDoesNotThrow() 19 | { 20 | var response = await RequestHandler.RequestAsync(GET, "/"); 21 | response.ApiCallDetails.Should().NotBeNull(); 22 | response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeTrue(); 23 | } 24 | 25 | [Fact] 26 | public void SyncRequestDoesNotThrow() 27 | { 28 | var response = RequestHandler.Request(GET, "/"); 29 | response.ApiCallDetails.Should().NotBeNull(); 30 | response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeTrue(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Elastic.Elasticsearch.IntegrationTests/Elastic.Elasticsearch.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | false 8 | xUnit1041 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/Elastic.Elasticsearch.IntegrationTests/IntegrationTestBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Elasticsearch.Xunit.XunitPlumbing; 6 | using Elastic.Transport; 7 | using Xunit.Abstractions; 8 | 9 | namespace Elastic.Elasticsearch.IntegrationTests; 10 | 11 | public abstract class IntegrationTestBase : IntegrationTestBase 12 | { 13 | protected IntegrationTestBase(DefaultCluster cluster, ITestOutputHelper output) : base(cluster, output) { } 14 | } 15 | 16 | public abstract class IntegrationTestBase : IClusterFixture 17 | where TCluster : DefaultCluster, new() 18 | { 19 | protected TCluster Cluster { get; } 20 | protected ITransport RequestHandler { get; } 21 | 22 | protected IntegrationTestBase(TCluster cluster, ITestOutputHelper output) 23 | { 24 | Cluster = cluster; 25 | RequestHandler = cluster.CreateClient(output); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Elastic.Transport; 6 | using FluentAssertions; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | using static Elastic.Transport.HttpMethod; 10 | 11 | namespace Elastic.Elasticsearch.IntegrationTests; 12 | 13 | public class SecurityClusterTests : IntegrationTestBase 14 | { 15 | public SecurityClusterTests(SecurityCluster cluster, ITestOutputHelper output) : base(cluster, output) { } 16 | 17 | private static readonly EndpointPath Root = new(GET, "/"); 18 | 19 | [Fact] 20 | public async Task AsyncRequestDoesNotThrow() 21 | { 22 | var response = await RequestHandler.RequestAsync(Root); 23 | response.ApiCallDetails.Should().NotBeNull(); 24 | response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeTrue(); 25 | } 26 | 27 | [Fact] 28 | public void SyncRequestDoesNotThrow() 29 | { 30 | var response = RequestHandler.Request(Root); 31 | response.ApiCallDetails.Should().NotBeNull(); 32 | response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeTrue(); 33 | } 34 | 35 | [Fact] 36 | public void SyncRequestDoesNotThrowOnBadAuth() 37 | { 38 | var response = RequestHandler.Request(Root, null, 39 | new RequestConfiguration 40 | { 41 | Authentication = new BasicAuthentication("unknown-user", "bad-password") 42 | } 43 | ); 44 | response.ApiCallDetails.Should().NotBeNull(); 45 | response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeFalse(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Elastic.Transport.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | True 6 | false 7 | CS8002;xUnit1041 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | 19 | 20 | 21 | 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | all 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Http/ApiCompatibilityHeaderTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Elastic.Transport.IntegrationTests.Plumbing; 8 | using Elastic.Transport.IntegrationTests.Plumbing.Stubs; 9 | using Elastic.Transport.Products.Elasticsearch; 10 | using FluentAssertions; 11 | using Microsoft.AspNetCore.Mvc; 12 | using Xunit; 13 | 14 | namespace Elastic.Transport.IntegrationTests.Http; 15 | 16 | public class ApiCompatibilityHeaderTests : AssemblyServerTestsBase 17 | { 18 | public ApiCompatibilityHeaderTests(TransportTestServer instance) : base(instance) { } 19 | 20 | [Fact] 21 | public async Task AddsExpectedVendorInformationForRestApiCompaitbility() 22 | { 23 | var requestInvoker = new TrackingRequestInvoker(responseMessage => 24 | { 25 | responseMessage.RequestMessage.Content.Headers.ContentType.MediaType.Should().Be("application/vnd.elasticsearch+json"); 26 | var parameter = responseMessage.RequestMessage.Content.Headers.ContentType.Parameters.Single(); 27 | parameter.Name.Should().Be("compatible-with"); 28 | parameter.Value.Should().Be("8"); 29 | 30 | var acceptValues = responseMessage.RequestMessage.Headers.GetValues("Accept"); 31 | acceptValues.Single().Replace(" ", "").Should().Be("application/vnd.elasticsearch+json;compatible-with=8"); 32 | 33 | var contentTypeValues = responseMessage.RequestMessage.Content.Headers.GetValues("Content-Type"); 34 | contentTypeValues.Single().Replace(" ", "").Should().Be("application/vnd.elasticsearch+json;compatible-with=8"); 35 | }); 36 | 37 | var nodePool = new SingleNodePool(Server.Uri); 38 | var config = new TransportConfiguration(nodePool, requestInvoker, productRegistration: new ElasticsearchProductRegistration(typeof(Clients.Elasticsearch.ElasticsearchClient))); 39 | var transport = new DistributedTransport(config); 40 | 41 | var response = await transport.PostAsync("/metaheader", PostData.String("{}")); 42 | } 43 | } 44 | 45 | [ApiController, Route("[controller]")] 46 | public class MetaHeaderController : ControllerBase 47 | { 48 | [HttpPost()] 49 | public async Task Post() => await Task.FromResult(100); 50 | } 51 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Plumbing/AssemblyServerTestsBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Xunit; 6 | using Xunit.Extensions.Ordering; 7 | 8 | namespace Elastic.Transport.IntegrationTests.Plumbing 9 | { 10 | public class AssemblyServerTestsBase(TServer instance) 11 | : IAssemblyFixture, IClassFixture where TServer : class, HttpTransportTestServer 12 | { 13 | protected TServer Server { get; } = instance; 14 | 15 | protected ITransport RequestHandler => Server.DefaultRequestHandler; 16 | } 17 | 18 | public class AssemblyServerTestsBase(TransportTestServer instance) 19 | : AssemblyServerTestsBase(instance) 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Plumbing/ClassServerTestsBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Xunit; 6 | 7 | namespace Elastic.Transport.IntegrationTests.Plumbing 8 | { 9 | public class ClassServerTestsBase(TServer instance) : IClassFixture where TServer : class, HttpTransportTestServer 10 | { 11 | protected TServer Server { get; } = instance; 12 | 13 | protected ITransport RequestHandler => Server.DefaultRequestHandler; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Plumbing/DefaultStartup.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Routing; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | 13 | namespace Elastic.Transport.IntegrationTests.Plumbing 14 | { 15 | public class DefaultStartup 16 | { 17 | public DefaultStartup(IConfiguration configuration) => Configuration = configuration; 18 | 19 | public IConfiguration Configuration { get; } 20 | 21 | public void ConfigureServices(IServiceCollection services) => services.AddControllers(); 22 | 23 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 24 | { 25 | if (env.IsDevelopment()) 26 | { 27 | app.UseDeveloperExceptionPage(); 28 | } 29 | else 30 | { 31 | app.UseHsts(); 32 | } 33 | 34 | app.UseHttpsRedirection(); 35 | app.UseRouting(); 36 | app.UseEndpoints(endpoints => 37 | { 38 | MapEndpoints(endpoints); 39 | endpoints.MapControllerRoute("default", "{controller=Default}/{id?}"); 40 | }); 41 | } 42 | 43 | protected virtual void MapEndpoints(IEndpointRouteBuilder endpoints) { } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Plumbing/Examples/ControllerIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.AspNetCore.Routing; 11 | using Microsoft.Extensions.Configuration; 12 | using Xunit; 13 | 14 | // Feel free to delete these tests at some point, these are here as examples while we built out our test suite 15 | // A lot of integration tests need to be ported from elastic/elasticsearch-net 16 | namespace Elastic.Transport.IntegrationTests.Plumbing.Examples 17 | { 18 | /// 19 | /// Tests that the test framework loads a controller and the exposed transport can talk to its endpoints. 20 | /// Tests runs against a server that started up once and its server instance shared among many test classes 21 | /// 22 | public class ControllerIntegrationTests : AssemblyServerTestsBase 23 | { 24 | public ControllerIntegrationTests(TransportTestServer instance) : base(instance) { } 25 | 26 | [Fact] 27 | public async Task CanCallIntoController() 28 | { 29 | var response = await RequestHandler.GetAsync("/dummy/20"); 30 | response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeTrue("{0}", response.ApiCallDetails.DebugInformation); 31 | } 32 | } 33 | 34 | [ApiController, Route("[controller]")] 35 | public class DummyController : ControllerBase 36 | { 37 | [HttpGet("{id}")] 38 | public async Task Get(int id) => await Task.FromResult(id * 3); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Plumbing/Examples/EndpointIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Routing; 10 | using Microsoft.Extensions.Configuration; 11 | using Xunit; 12 | 13 | namespace Elastic.Transport.IntegrationTests.Plumbing.Examples 14 | { 15 | /// 16 | /// Spins up a server just for the tests in this class, using a custom startup we attach a special endpoint handler 17 | /// Since it extends it server is only shared between the tests inside this class. 18 | /// The server is also started and stopped after all the tests in this class run. 19 | /// 20 | public class EndpointIntegrationTests : ClassServerTestsBase> 21 | { 22 | public EndpointIntegrationTests(TransportTestServer instance) : base(instance) { } 23 | 24 | [Fact] 25 | public async Task CanCallIntoEndpoint() 26 | { 27 | var response = await RequestHandler.GetAsync(DummyStartup.Endpoint); 28 | response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeTrue("{0}", response.ApiCallDetails.DebugInformation); 29 | } 30 | } 31 | 32 | public class DummyStartup : DefaultStartup 33 | { 34 | public DummyStartup(IConfiguration configuration) : base(configuration) { } 35 | 36 | public static string Endpoint { get; } = "buffered"; 37 | 38 | protected override void MapEndpoints(IEndpointRouteBuilder endpoints) => 39 | endpoints.MapGet("/buffered", async context => 40 | { 41 | var name = context.Request.RouteValues["id"]; 42 | await context.Response.WriteAsync($"Hello {name}!"); 43 | await Task.Delay(1); 44 | await context.Response.WriteAsync($"World!"); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Plumbing/Stubs/TestableClientHandler.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Net.Http; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Elastic.Transport.IntegrationTests.Plumbing.Stubs 11 | { 12 | public class TestableClientHandler : DelegatingHandler 13 | { 14 | private readonly Action _responseAction; 15 | 16 | public TestableClientHandler(HttpMessageHandler handler, Action responseAction) : base(handler) => 17 | _responseAction = responseAction; 18 | 19 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 20 | { 21 | var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); 22 | _responseAction?.Invoke(response); 23 | return response; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Plumbing/Stubs/TrackingRequestInvoker.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Net.Http; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Elastic.Transport.IntegrationTests.Plumbing.Stubs; 11 | 12 | public class TrackingRequestInvoker : IRequestInvoker 13 | { 14 | private readonly Action _response; 15 | private TestableClientHandler _handler; 16 | public int CallCount { get; private set; } 17 | public HttpClientHandler LastHttpClientHandler => (HttpClientHandler)_handler.InnerHandler; 18 | 19 | public ResponseFactory ResponseFactory => _requestInvoker.ResponseFactory; 20 | 21 | private readonly HttpRequestInvoker _requestInvoker; 22 | 23 | public TrackingRequestInvoker(Action response) : this() => _response = response; 24 | 25 | public TrackingRequestInvoker() => 26 | _requestInvoker = new HttpRequestInvoker((defaultHandler, _) => 27 | { 28 | _handler = new TestableClientHandler(defaultHandler, _response); 29 | return _handler; 30 | }); 31 | 32 | public TResponse Request(Endpoint endpoint, BoundConfiguration boundConfiguration, PostData postData) 33 | where TResponse : TransportResponse, new() 34 | { 35 | CallCount++; 36 | return _requestInvoker.Request(endpoint, boundConfiguration, postData); 37 | } 38 | 39 | public Task RequestAsync(Endpoint endpoint, BoundConfiguration boundConfiguration, PostData postData, CancellationToken cancellationToken) 40 | where TResponse : TransportResponse, new() 41 | { 42 | CallCount++; 43 | return _requestInvoker.RequestAsync(endpoint, boundConfiguration, postData, cancellationToken); 44 | } 45 | 46 | public void Dispose() 47 | { 48 | _handler?.Dispose(); 49 | _requestInvoker?.Dispose(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.IntegrationTests/Plumbing/WebHostExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Text.RegularExpressions; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Hosting.Server.Features; 10 | 11 | namespace Elastic.Transport.IntegrationTests.Plumbing 12 | { 13 | internal static class WebHostExtensions 14 | { 15 | internal static int GetServerPort(this IWebHost server) 16 | { 17 | var address = server.ServerFeatures.Get().Addresses.First(); 18 | var match = Regex.Match(address, @"^.+:(\d+)$"); 19 | 20 | if (!match.Success) throw new Exception($"Unable to parse port from address: {address}"); 21 | 22 | var port = int.TryParse(match.Groups[1].Value, out var p); 23 | return port ? p : throw new Exception($"Unable to parse port to integer from address: {address}"); 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests.Shared/Elastic.Transport.Tests.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net481 5 | false 6 | CS8002 7 | enable 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests.Shared/TrackDisposeStream.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport.Tests.Shared; 6 | 7 | public class TrackDisposeStream : MemoryStream 8 | { 9 | private readonly bool _canSeek; 10 | 11 | public TrackDisposeStream(bool canSeek = true) : base() => _canSeek = canSeek; 12 | 13 | public TrackDisposeStream(byte[] bytes, bool canSeek = true) : base(bytes) => _canSeek = canSeek; 14 | 15 | public TrackDisposeStream(byte[] bytes, int index, int count, bool canSeek = true) : base(bytes, index, count) => _canSeek = canSeek; 16 | 17 | public override bool CanSeek => _canSeek; 18 | 19 | public bool IsDisposed { get; private set; } 20 | 21 | protected override void Dispose(bool disposing) 22 | { 23 | IsDisposed = true; 24 | base.Dispose(disposing); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests.Shared/TrackingMemoryStreamFactory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport.Tests.Shared; 6 | 7 | public class TrackingMemoryStreamFactory() : MemoryStreamFactory 8 | { 9 | public IList Created { get; private set; } = []; 10 | 11 | public override MemoryStream Create() 12 | { 13 | var stream = new TrackDisposeStream(); 14 | Created.Add(stream); 15 | return stream; 16 | } 17 | 18 | public override MemoryStream Create(byte[] bytes) 19 | { 20 | var stream = new TrackDisposeStream(bytes); 21 | Created.Add(stream); 22 | return stream; 23 | } 24 | 25 | public override MemoryStream Create(byte[] bytes, int index, int count) 26 | { 27 | var stream = new TrackDisposeStream(bytes, index, count); 28 | Created.Add(stream); 29 | return stream; 30 | } 31 | 32 | public void Reset() => Created = []; 33 | } 34 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/AddressParsing.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using FluentAssertions; 6 | using Elastic.Transport.Products.Elasticsearch; 7 | using Xunit; 8 | 9 | namespace Elastic.Transport.Tests 10 | { 11 | public class AddressParsing 12 | { 13 | [Fact] public void IsMatched() 14 | { 15 | //based on examples from http://www.ietf.org/rfc/rfc2732.txt 16 | var testcases = new[,] 17 | { 18 | {"[::1]:9200", "[::1]", "9200"}, 19 | {"192.168.2.1:231", "192.168.2.1", "231"}, 20 | {"[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80", "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]", "80"}, 21 | {"[1080:0:0:0:8:800:200C:417A]:1234", "[1080:0:0:0:8:800:200C:417A]", "1234"}, 22 | {"[3ffe:2a00:100:7031::1]:1", "[3ffe:2a00:100:7031::1]", "1"}, 23 | {"[1080::8:800:200C:417A]:123", "[1080::8:800:200C:417A]", "123"}, 24 | {"[::192.9.5.5]:12", "[::192.9.5.5]", "12"}, 25 | {"[::FFFF:129.144.52.38]:80", "[::FFFF:129.144.52.38]", "80"}, 26 | {"[2010:836B:4179::836B:4179]:34533", "[2010:836B:4179::836B:4179]", "34533"} 27 | }; 28 | 29 | for (var i = 0; i < testcases.GetLength(0); i++) 30 | { 31 | var address = testcases[i, 0]; 32 | var ip = testcases[i, 1]; 33 | var port = testcases[i, 2]; 34 | 35 | var match = SniffParser.AddressRegex.Match(address); 36 | 37 | match.Success.Should().BeTrue(); 38 | 39 | match.Groups["ip"].Value.Should().BeEquivalentTo(ip); 40 | match.Groups["port"].Value.Should().BeEquivalentTo(port); 41 | } 42 | } 43 | 44 | [Fact] public void FqdnIsReadCorrectly() 45 | { 46 | //based on examples from http://www.ietf.org/rfc/rfc2732.txt 47 | var testcases = new[,] 48 | { 49 | {"helloworld/[::1]:9200", "helloworld", "[::1]", "9200"}, 50 | {"elastic.co/192.168.2.1:231", "elastic.co", "192.168.2.1", "231"} 51 | }; 52 | 53 | for (var i = 0; i < testcases.GetLength(0); i++) 54 | { 55 | var address = testcases[i, 0]; 56 | var fqdn = testcases[i, 1]; 57 | var ip = testcases[i, 2]; 58 | var port = testcases[i, 3]; 59 | 60 | var match = SniffParser.AddressRegex.Match(address); 61 | 62 | match.Success.Should().BeTrue(); 63 | 64 | match.Groups["fqdn"].Value.Should().BeEquivalentTo(fqdn); 65 | match.Groups["ip"].Value.Should().BeEquivalentTo(ip); 66 | match.Groups["port"].Value.Should().BeEquivalentTo(port); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Components/NodePool/StaticNodePoolTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using Xunit; 6 | using System; 7 | using FluentAssertions; 8 | using System.Linq; 9 | 10 | namespace Elastic.Transport.Tests.Components.NodePool 11 | { 12 | public class StaticNodePoolTests 13 | { 14 | [Fact] 15 | public void MultipleRequests_WhenOnlyASingleEndpointIsConfigured_AndTheEndpointIsUnavailable_DoNotThrowAnException() 16 | { 17 | Node[] nodes = [new Uri("http://localhost:9200")]; 18 | var pool = new StaticNodePool(nodes); 19 | var transport = new DistributedTransport(new TransportConfiguration(pool)); 20 | 21 | var response = transport.Request(HttpMethod.GET, "/", null, null); 22 | 23 | response.ApiCallDetails.SuccessOrKnownError.Should().BeFalse(); 24 | response.ApiCallDetails.AuditTrail.Count.Should().Be(1); 25 | 26 | var audit = response.ApiCallDetails.AuditTrail.First(); 27 | audit.Event.Should().Be(Diagnostics.Auditing.AuditEvent.BadRequest); 28 | audit.Node.FailedAttempts.Should().Be(1); 29 | audit.Node.IsAlive.Should().BeFalse(); 30 | 31 | response = transport.Request(HttpMethod.GET, "/", null, null); 32 | 33 | response.ApiCallDetails.SuccessOrKnownError.Should().BeFalse(); 34 | 35 | var eventCount = 0; 36 | 37 | foreach (var a in response.ApiCallDetails.AuditTrail) 38 | { 39 | eventCount++; 40 | 41 | if (eventCount == 1) 42 | { 43 | a.Event.Should().Be(Diagnostics.Auditing.AuditEvent.AllNodesDead); 44 | } 45 | 46 | if (eventCount == 2) 47 | { 48 | a.Event.Should().Be(Diagnostics.Auditing.AuditEvent.Resurrection); 49 | } 50 | 51 | if (eventCount == 3) 52 | { 53 | a.Event.Should().Be(Diagnostics.Auditing.AuditEvent.BadRequest); 54 | audit.Node.FailedAttempts.Should().Be(2); 55 | audit.Node.IsAlive.Should().BeFalse(); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Components/Serialization/SerializationTestBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | 7 | namespace Elastic.Transport.Tests.Components.Serialization; 8 | 9 | public abstract class SerializerTestBase 10 | { 11 | protected static Stream SerializeToStream(T data) 12 | { 13 | var stream = new MemoryStream(); 14 | LowLevelRequestResponseSerializer.Instance.Serialize(data, stream); 15 | stream.Position = 0; 16 | return stream; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Configuration/HeadersListTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.Linq; 6 | using FluentAssertions; 7 | using Xunit; 8 | 9 | namespace Elastic.Transport.Tests.Configuration 10 | { 11 | public class HeadersListTests 12 | { 13 | [Fact] 14 | public void SupportsEnumerationWhenEmpty() 15 | { 16 | var sut = new HeadersList(); 17 | 18 | foreach (var header in sut) 19 | { 20 | } 21 | } 22 | 23 | [Fact] 24 | public void Ctor_SkipsDuplicates_FromSingleEnumerable() 25 | { 26 | var sut = new HeadersList(new[] { "header-one", "header-two", "header-TWO" }); 27 | 28 | sut.Count.Should().Be(2); 29 | sut.First().Should().Be("header-one"); 30 | sut.Last().Should().Be("header-two"); 31 | } 32 | 33 | [Fact] 34 | public void Ctor_SkipsDuplicates_FromSingleEnumerable_AndSingleHeader() 35 | { 36 | var sut = new HeadersList(new[] { "header-one", "header-two" }, "header-TWO"); 37 | 38 | sut.Count.Should().Be(2); 39 | sut.First().Should().Be("header-one"); 40 | sut.Last().Should().Be("header-two"); 41 | } 42 | 43 | [Fact] 44 | public void Ctor_SkipsDuplicates_FromTwoEnumerables() 45 | { 46 | var sut = new HeadersList(new[] { "header-ONE", "header-two" }, new[] { "header-one", "header-THREE", "HEADER-TWO" }); 47 | 48 | sut.Count.Should().Be(3); 49 | 50 | var count = 0; 51 | foreach (var header in sut) 52 | { 53 | count++; 54 | 55 | switch (count) 56 | { 57 | case 1: 58 | header.Should().Be("header-ONE"); 59 | break; 60 | case 2: 61 | header.Should().Be("header-two"); 62 | break; 63 | case 3: 64 | header.Should().Be("header-THREE"); 65 | break; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Configuration/RequestConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Security.Cryptography.X509Certificates; 7 | using System.Threading; 8 | using FluentAssertions; 9 | using Xunit; 10 | #if !NETFRAMEWORK 11 | using Soenneker.Utils.AutoBogus; 12 | #endif 13 | 14 | namespace Elastic.Transport.Tests.Configuration; 15 | 16 | public class RequestConfigurationTests 17 | { 18 | [Fact] 19 | public void CopiesAllDefaults() 20 | { 21 | var config = new RequestConfiguration(); 22 | var newConfig = new RequestConfiguration(config); 23 | 24 | config.Should().BeEquivalentTo(newConfig); 25 | } 26 | 27 | [Fact] 28 | public void SameDefaults() 29 | { 30 | IRequestConfiguration config = new RequestConfiguration(); 31 | IRequestConfiguration newConfig = new RequestConfigurationDescriptor(); 32 | 33 | config.Should().BeEquivalentTo(newConfig); 34 | } 35 | 36 | #if !NETFRAMEWORK 37 | [Fact] 38 | public void CopiesAllProperties() 39 | { 40 | var autoFaker = new AutoFaker(); 41 | autoFaker.RuleFor(x => x.ClientCertificates, f => []); 42 | 43 | var config = autoFaker.Generate(); 44 | config.Accept.Should().NotBeEmpty(); 45 | config.ClientCertificates.Should().NotBeNull(); 46 | 47 | IRequestConfiguration newConfig = new RequestConfiguration(config); 48 | config.Should().BeEquivalentTo(newConfig); 49 | 50 | IRequestConfiguration newDescriptor = new RequestConfigurationDescriptor(config); 51 | config.Should().BeEquivalentTo(newDescriptor); 52 | } 53 | #endif 54 | } 55 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Configuration/TransportConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Security.Cryptography.X509Certificates; 7 | using System.Threading; 8 | using FluentAssertions; 9 | using Xunit; 10 | #if !NETFRAMEWORK 11 | using Soenneker.Utils.AutoBogus; 12 | #endif 13 | 14 | namespace Elastic.Transport.Tests.Configuration; 15 | 16 | public class TransportConfigurationTests 17 | { 18 | [Fact] 19 | public void CopiesAllDefaults() 20 | { 21 | var config = new TransportConfiguration(); 22 | var newConfig = new TransportConfiguration(config); 23 | 24 | config.Should().BeEquivalentTo(newConfig); 25 | } 26 | 27 | [Fact] 28 | public void SameDefaults() 29 | { 30 | ITransportConfiguration config = new TransportConfiguration(); 31 | ITransportConfiguration newConfig = new TransportConfigurationDescriptor(); 32 | 33 | config.Should().BeEquivalentTo(newConfig, c => c 34 | .Excluding(p=>p.BootstrapLock) 35 | ); 36 | 37 | config.BootstrapLock.CurrentCount.Should().Be(newConfig.BootstrapLock.CurrentCount); 38 | } 39 | 40 | #if !NETFRAMEWORK 41 | [Fact] 42 | public void CopiesAllProperties() 43 | { 44 | var autoFaker = new AutoFaker(); 45 | autoFaker.RuleFor(x => x.BootstrapLock, f => new SemaphoreSlim(1, 1)); 46 | autoFaker.RuleFor(x => x.ClientCertificates, f => new X509CertificateCollection()); 47 | 48 | var config = autoFaker.Generate(); 49 | config.Accept.Should().NotBeEmpty(); 50 | config.ClientCertificates.Should().NotBeNull(); 51 | 52 | ITransportConfiguration newConfig = new TransportConfiguration(config); 53 | config.Should().BeEquivalentTo(newConfig); 54 | 55 | ITransportConfiguration newDescriptor = new TransportConfigurationDescriptor(config); 56 | config.Should().BeEquivalentTo(newDescriptor); 57 | } 58 | #endif 59 | } 60 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Elastic.Transport.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net481 5 | True 6 | false 7 | CS8002 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/InstantiationsTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using FluentAssertions; 6 | using Xunit; 7 | 8 | namespace Elastic.Transport.Tests 9 | { 10 | public class InstantiationsTests 11 | { 12 | public class A {} 13 | 14 | [Fact] 15 | public void SerializableMultiJson() 16 | { 17 | var p = PostData.MultiJson(new [] {new A()}); 18 | p.Type.Should().Be(PostType.EnumerableOfObject); 19 | } 20 | 21 | [Fact] 22 | public void StringMultiJson() 23 | { 24 | var p = PostData.MultiJson(new [] {""}); 25 | p.Type.Should().Be(PostType.EnumerableOfString); 26 | } 27 | 28 | [Fact] 29 | public void ObjectMultiJson() 30 | { 31 | var p = PostData.MultiJson(new object[] {new A()}); 32 | p.Type.Should().Be(PostType.EnumerableOfObject); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Plumbing/InMemoryConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using Elastic.Transport.Products; 7 | 8 | namespace Elastic.Transport.Tests.Plumbing 9 | { 10 | public static class InMemoryConnectionFactory 11 | { 12 | public static TransportConfiguration Create(ProductRegistration productRegistration = null) 13 | { 14 | var invoker = new InMemoryRequestInvoker(); 15 | var pool = new SingleNodePool(new Uri("http://localhost:9200")); 16 | var settings = new TransportConfiguration(pool, invoker, productRegistration: productRegistration); 17 | return settings; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Plumbing/TestResponse.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | namespace Elastic.Transport.Tests.Plumbing 6 | { 7 | public class TestResponse : TransportResponse { } 8 | } 9 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Responses/Dynamic/DynamicResponseBuilderTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using FluentAssertions; 9 | using Xunit; 10 | 11 | namespace Elastic.Transport.Tests.Responses.Dynamic; 12 | 13 | public class DynamicResponseBuilderTests 14 | { 15 | [Fact] 16 | public async Task ReturnsExpectedResponse_ForJsonData() 17 | { 18 | IResponseBuilder sut = new DynamicResponseBuilder(); 19 | 20 | var config = new TransportConfiguration(); 21 | var apiCallDetails = new ApiCallDetails(); 22 | var boundConfiguration = new BoundConfiguration(config); 23 | 24 | var data = Encoding.UTF8.GetBytes("{\"_index\":\"my-index\",\"_id\":\"pZqC6JIB9RdSpcF8-3lq\",\"_version\":1,\"result\":\"created\",\"_shards\":{\"total\":1,\"successful\":1,\"failed\":0},\"_seq_no\":2,\"_primary_term\":1}"); 25 | var stream = new MemoryStream(data); 26 | 27 | var result = await sut.BuildAsync(apiCallDetails, boundConfiguration, stream, BoundConfiguration.DefaultContentType, data.Length); 28 | result.Body.Get("_index").Should().Be("my-index"); 29 | 30 | stream.Position = 0; 31 | 32 | result = sut.Build(apiCallDetails, boundConfiguration, stream, BoundConfiguration.DefaultContentType, data.Length); 33 | result.Body.Get("_index").Should().Be("my-index"); 34 | } 35 | 36 | [Fact] 37 | public async Task ReturnsExpectedResponse_ForNonJsonData() 38 | { 39 | IResponseBuilder sut = new DynamicResponseBuilder(); 40 | 41 | var config = new TransportConfiguration(); 42 | var apiCallDetails = new ApiCallDetails(); 43 | var boundConfiguration = new BoundConfiguration(config); 44 | 45 | var data = Encoding.UTF8.GetBytes("This is not JSON"); 46 | var stream = new MemoryStream(data); 47 | 48 | var result = await sut.BuildAsync(apiCallDetails, boundConfiguration, stream, "text/plain", data.Length); 49 | result.Body.Get("body").Should().Be("This is not JSON"); 50 | 51 | stream.Position = 0; 52 | 53 | result = sut.Build(apiCallDetails, boundConfiguration, stream, "text/plain", data.Length); 54 | result.Body.Get("body").Should().Be("This is not JSON"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/Responses/Special/VoidResponseBuilderTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System.IO; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using FluentAssertions; 9 | using Xunit; 10 | using static Elastic.Transport.VoidResponse; 11 | 12 | namespace Elastic.Transport.Tests.Responses.Special; 13 | 14 | public class VoidResponseBuilderTests 15 | { 16 | private static readonly byte[] Data = Encoding.UTF8.GetBytes("{\"_index\":\"my-index\",\"_id\":\"pZqC6JIB9RdSpcF8-3lq\",\"_version\":1,\"result\"" + 17 | ":\"created\",\"_shards\":{\"total\":1,\"successful\":1,\"failed\":0},\"_seq_no\":2,\"_primary_term\":1}"); 18 | 19 | [Fact] 20 | public async Task ReturnsExpectedResponse() 21 | { 22 | IResponseBuilder sut = new VoidResponseBuilder(); 23 | 24 | var config = new TransportConfiguration(); 25 | var apiCallDetails = new ApiCallDetails(); 26 | var boundConfiguration = new BoundConfiguration(config); 27 | var stream = new MemoryStream(Data); 28 | 29 | var result = await sut.BuildAsync(apiCallDetails, boundConfiguration, stream, BoundConfiguration.DefaultContentType, Data.Length); 30 | result.Body.Should().BeOfType(typeof(VoidBody)); 31 | 32 | result = sut.Build(apiCallDetails, boundConfiguration, stream, BoundConfiguration.DefaultContentType, Data.Length); 33 | result.Body.Should().BeOfType(typeof(VoidBody)); 34 | } 35 | 36 | [Fact] 37 | public async Task ReturnsExpectedResponse_WhenDisableDirectStreaming() 38 | { 39 | IResponseBuilder sut = new VoidResponseBuilder(); 40 | 41 | var config = new TransportConfiguration() { DisableDirectStreaming = true }; 42 | var apiCallDetails = new ApiCallDetails() { ResponseBodyInBytes = Data }; 43 | var boundConfiguration = new BoundConfiguration(config); 44 | var stream = new MemoryStream(Data); 45 | 46 | var result = await sut.BuildAsync(apiCallDetails, boundConfiguration, stream, BoundConfiguration.DefaultContentType, Data.Length); 47 | result.Body.Should().BeOfType(typeof(VoidBody)); 48 | 49 | result = sut.Build(apiCallDetails, boundConfiguration, stream, BoundConfiguration.DefaultContentType, Data.Length); 50 | result.Body.Should().BeOfType(typeof(VoidBody)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Elastic.Transport.Tests/VolatileUpdates.cs: -------------------------------------------------------------------------------- 1 | // Licensed to Elasticsearch B.V under one or more agreements. 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading; 9 | using FluentAssertions; 10 | using Xunit; 11 | 12 | namespace Elastic.Transport.Tests 13 | { 14 | public class VolatileUpdates 15 | { 16 | private readonly int _numberOfNodes = 10; 17 | private readonly Random _random = new Random(); 18 | 19 | private readonly List _update = Enumerable.Range(9200, 10) 20 | .Select(p => new Uri("http://localhost:" + p)) 21 | .Select(u => new Node(u)) 22 | .ToList(); 23 | 24 | [Fact] public void SniffingPoolWithstandsConcurrentReadAndWrites() 25 | { 26 | var uris = Enumerable.Range(9200, _numberOfNodes).Select(p => new Uri("http://localhost:" + p)); 27 | var sniffingPool = new SniffingNodePool(uris, false); 28 | 29 | var callSniffing = () => AssertCreateView(sniffingPool); 30 | 31 | callSniffing.Should().NotThrow(); 32 | } 33 | 34 | [Fact] public void StaticPoolWithstandsConcurrentReadAndWrites() 35 | { 36 | var uris = Enumerable.Range(9200, _numberOfNodes).Select(p => new Uri("http://localhost:" + p)); 37 | var staticPool = new StaticNodePool(uris, false); 38 | 39 | var callStatic = () => AssertCreateView(staticPool); 40 | 41 | callStatic.Should().NotThrow(); 42 | } 43 | 44 | private void AssertCreateView(NodePool pool) 45 | { 46 | var threads = Enumerable.Range(0, 50) 47 | .Select(i => CreateReadAndUpdateThread(pool)) 48 | .ToList(); 49 | 50 | foreach (var t in threads) t.Start(); 51 | foreach (var t in threads) t.Join(); 52 | } 53 | 54 | private Thread CreateReadAndUpdateThread(NodePool pool) => new Thread(() => 55 | { 56 | for (var i = 0; i < 1000; i++) 57 | foreach (var _ in CallGetNext(pool)) 58 | if (_random.Next(10) % 2 == 0) 59 | pool.Reseed(_update); 60 | }); 61 | 62 | private IEnumerable CallGetNext(NodePool pool) 63 | { 64 | foreach (var n in pool.CreateView()) yield return n.Uri.Port; 65 | } 66 | } 67 | } 68 | --------------------------------------------------------------------------------