protected EventStoreClientBase(EventStoreClientSettings?settings,
                                       IDictionary <string, Func <RpcException, Exception> > exceptionMap)
        {
            Settings     = settings ?? new EventStoreClientSettings();
            _httpHandler = Settings.CreateHttpMessageHandler?.Invoke() ?? new HttpClientHandler();

            var connectionName = Settings.ConnectionName ?? $"ES-{Guid.NewGuid()}";
            Action <Exception>?exceptionNotificationHook = null;

            if (Settings.ConnectivitySettings.GossipSeeds.Length > 0)
            {
                _httpHandler = ClusterAwareHttpHandler.Create(Settings, _httpHandler);
            }

            _channel = GrpcChannel.ForAddress(Settings.ConnectivitySettings.Address, new GrpcChannelOptions {
                HttpClient = new HttpClient(_httpHandler)
                {
                    Timeout = Timeout.InfiniteTimeSpan,
                    DefaultRequestVersion = new Version(2, 0),
                },
                LoggerFactory = Settings.LoggerFactory
            });

            CallInvoker = (Settings.Interceptors ?? Array.Empty <Interceptor>()).Aggregate(
                _channel.CreateCallInvoker()
                .Intercept(new TypedExceptionInterceptor(exceptionMap, exceptionNotificationHook))
                .Intercept(new ConnectionNameInterceptor(connectionName)),
                (invoker, interceptor) => invoker.Intercept(interceptor));
        }
        protected EventStoreClientBase(EventStoreClientSettings?settings,
                                       IDictionary <string, Func <RpcException, Exception> > exceptionMap)
        {
            Settings = settings ?? new EventStoreClientSettings();

            var connectionName = Settings.ConnectionName ?? $"ES-{Guid.NewGuid()}";

            if (Settings.ConnectivitySettings.Address.Scheme == Uri.UriSchemeHttp ||
                !Settings.ConnectivitySettings.GossipOverHttps)
            {
                //this must be switched on before creation of the HttpMessageHandler
                AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
            }

#if NETCOREAPP3_1
            _innerHttpHandler = Settings.CreateHttpMessageHandler?.Invoke() ?? new SocketsHttpHandler();
#elif NETSTANDARD2_1
            _innerHttpHandler = Settings.CreateHttpMessageHandler?.Invoke() ?? new HttpClientHandler();
#endif

            _httpHandler = Settings.ConnectivitySettings.IsSingleNode
                                ? (HttpMessageHandler) new SingleNodeHttpHandler(Settings)
            {
                InnerHandler = _innerHttpHandler
            }
                                : ClusterAwareHttpHandler.Create(Settings, _innerHttpHandler);

#if NETSTANDARD2_1
            _httpHandler = new DefaultRequestVersionHandler(_httpHandler);
#endif

            _channel = GrpcChannel.ForAddress(new UriBuilder(Settings.ConnectivitySettings.Address)
            {
                Scheme = Uri.UriSchemeHttps
            }.Uri, new GrpcChannelOptions {
                HttpClient = new HttpClient(_httpHandler)
                {
                    Timeout = Timeout.InfiniteTimeSpan,
#if NETCOREAPP3_1
                    DefaultRequestVersion = new Version(2, 0),
#endif
                },
                LoggerFactory = Settings.LoggerFactory,
                Credentials   = Settings.ChannelCredentials
            });

            Action <Exception>?exceptionNotificationHook =
                _httpHandler is ClusterAwareHttpHandler h
                                        ? h.ExceptionOccurred
                                        : new Action <Exception>(ex => { });

            CallInvoker = (Settings.Interceptors ?? Array.Empty <Interceptor>()).Aggregate(
                _channel.CreateCallInvoker()
                .Intercept(new TypedExceptionInterceptor(exceptionMap, exceptionNotificationHook))
                .Intercept(new ConnectionNameInterceptor(connectionName)),
                (invoker, interceptor) => invoker.Intercept(interceptor));
        }
        public EventStoreClient(EventStoreClientSettings settings = null)
        {
            _settings = settings ?? new EventStoreClientSettings();
            var connectionName = _settings.ConnectionName ?? $"ES-{Guid.NewGuid()}";
            Action <Exception> exceptionNotificationHook = null;
            var httpHandler = _settings.CreateHttpMessageHandler?.Invoke() ?? new HttpClientHandler();

            if (_settings.ConnectivitySettings.GossipSeeds.Length > 0)
            {
                ConfigureClusterAwareHandler();
            }

            _channel = GrpcChannel.ForAddress(_settings.ConnectivitySettings.Address, new GrpcChannelOptions {
                HttpClient = new HttpClient(httpHandler)
                {
                    Timeout = Timeout.InfiniteTimeSpan,
                    DefaultRequestVersion = new Version(2, 0),
                },
                LoggerFactory = _settings.LoggerFactory
            });
            var callInvoker = (_settings.Interceptors ?? Array.Empty <Interceptor>()).Aggregate(
                _channel.CreateCallInvoker()
                .Intercept(new TypedExceptionInterceptor(exceptionNotificationHook))
                .Intercept(new ConnectionNameInterceptor(connectionName)),
                (invoker, interceptor) => invoker.Intercept(interceptor));

            _client = new Streams.Streams.StreamsClient(callInvoker);
            PersistentSubscriptions = new EventStorePersistentSubscriptionsClient(callInvoker, _settings);
            ProjectionsManager      = new EventStoreProjectionManagerClient(callInvoker);
            UsersManager            = new EventStoreUserManagerClient(callInvoker);
            _log = _settings.LoggerFactory?.CreateLogger <EventStoreClient>() ?? new NullLogger <EventStoreClient>();

            void ConfigureClusterAwareHandler()
            {
                var clusterAwareHttpHandler = new ClusterAwareHttpHandler(
                    _settings.ConnectivitySettings.NodePreference == NodePreference.Leader,
                    new ClusterEndpointDiscoverer(
                        _settings.ConnectivitySettings.MaxDiscoverAttempts,
                        _settings.ConnectivitySettings.GossipSeeds,
                        _settings.ConnectivitySettings.GossipTimeout,
                        _settings.ConnectivitySettings.DiscoveryInterval,
                        _settings.ConnectivitySettings.NodePreference,
                        httpHandler))
                {
                    InnerHandler = httpHandler
                };

                exceptionNotificationHook = clusterAwareHttpHandler.ExceptionOccurred;
                httpHandler = clusterAwareHttpHandler;
            }
        }
        public async Task should_set_requires_leader_header(bool requiresLeader)
        {
            var sut = new ClusterAwareHttpHandler(
                requiresLeader, new FakeEndpointDiscoverer(() => new IPEndPoint(IPAddress.Parse("0.0.0.0"), 2113)))
            {
                InnerHandler = new TestMessageHandler()
            };

            var client = new HttpClient(sut);

            var request = new HttpRequestMessage(HttpMethod.Get, new UriBuilder().Uri);

            await client.SendAsync(request);

            Assert.True(request.Headers.TryGetValues("requires-leader", out var value));
            Assert.True(bool.Parse(value.First()) == requiresLeader);
        }
        public async Task should_set_endpoint_to_leader_endpoint_on_exception(bool useHttps, EndPoint endpoint)
        {
            var sut = new ClusterAwareHttpHandler(
                useHttps, true, new FakeEndpointDiscoverer(() => new IPEndPoint(IPAddress.Parse("0.0.0.0"), 2113)))
            {
                InnerHandler = new TestMessageHandler()
            };

            var client = new HttpClient(sut);

            var request = new HttpRequestMessage(HttpMethod.Get, new UriBuilder().Uri);

            sut.ExceptionOccurred(new NotLeaderException(endpoint.GetHost(), endpoint.GetPort()));
            await client.SendAsync(request);

            Assert.Equal(endpoint.GetHost(), request.RequestUri.Host);
            Assert.Equal(endpoint.GetPort(), request.RequestUri.Port);
        }
        public async Task should_issue_request_to_discovered_endpoint()
        {
            var discoveredEndpoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 2113);

            var sut = new ClusterAwareHttpHandler(
                true, new FakeEndpointDiscoverer(() => discoveredEndpoint))
            {
                InnerHandler = new TestMessageHandler()
            };

            var client = new HttpClient(sut);

            await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, new UriBuilder().Uri));

            var request = new HttpRequestMessage(HttpMethod.Get, new UriBuilder().Uri);
            await client.SendAsync(request);

            Assert.Equal(discoveredEndpoint.Address.ToString(), request.RequestUri.Host);
            Assert.Equal(discoveredEndpoint.Port, request.RequestUri.Port);
        }
        public async Task should_attempt_endpoint_discovery_on_next_request_when_request_fails()
        {
            int discoveryAttempts = 0;

            var sut = new ClusterAwareHttpHandler(
                true, new FakeEndpointDiscoverer(() => {
                discoveryAttempts++;
                throw new Exception();
            }))
            {
                InnerHandler = new TestMessageHandler()
            };

            var client = new HttpClient(sut);

            await Assert.ThrowsAsync <Exception>(() =>
                                                 client.SendAsync(new HttpRequestMessage(HttpMethod.Get, new UriBuilder().Uri)));

            await Assert.ThrowsAsync <Exception>(() =>
                                                 client.SendAsync(new HttpRequestMessage(HttpMethod.Get, new UriBuilder().Uri)));

            Assert.Equal(2, discoveryAttempts);
        }