public void ClientFailsToStartInStreamingModeWith401Error() { using (var streamServer = HttpServer.Start(Error401Response)) { var config = BasicConfig() .DataSource(Components.StreamingDataSource()) .ServiceEndpoints(Components.ServiceEndpoints().Streaming(streamServer.Uri)) .StartWaitTime(TimeSpan.FromSeconds(5)) .Build(); using (var client = new LdClient(config)) { Assert.False(client.Initialized); Assert.Equal(DataSourceState.Off, client.DataSourceStatusProvider.Status.State); var value = client.BoolVariation(AlwaysTrueFlag.Key, BasicUser, false); Assert.False(value); var request = streamServer.Recorder.RequireRequest(); Assert.Equal(BasicSdkKey, request.Headers.Get("Authorization")); Assert.NotEmpty(LogCapture.GetMessages().Where( m => m.Level == Logging.LogLevel.Error && m.Text.Contains("error 401") && m.Text.Contains("giving up permanently"))); } } }
public void ClientRetriesConnectionInStreamingModeWithNonFatalError() { var failThenSucceedHandler = Handlers.Sequential(Error503Response, ValidStreamingResponse); using (var streamServer = HttpServer.Start(failThenSucceedHandler)) { var config = BasicConfig() .DataSource(Components.StreamingDataSource()) .ServiceEndpoints(Components.ServiceEndpoints().Streaming(streamServer.Uri)) .StartWaitTime(TimeSpan.FromSeconds(5)) .Build(); using (var client = new LdClient(config)) { Assert.True(client.Initialized); Assert.Equal(DataSourceState.Valid, client.DataSourceStatusProvider.Status.State); var value = client.BoolVariation(AlwaysTrueFlag.Key, BasicUser, false); Assert.True(value); var request1 = streamServer.Recorder.RequireRequest(); var request2 = streamServer.Recorder.RequireRequest(); Assert.Equal(BasicSdkKey, request1.Headers.Get("Authorization")); Assert.Equal(BasicSdkKey, request2.Headers.Get("Authorization")); Assert.NotEmpty(LogCapture.GetMessages().Where( m => m.Level == Logging.LogLevel.Warn && m.Text.Contains("error 503") && m.Text.Contains("will retry"))); Assert.Empty(LogCapture.GetMessages().Where(m => m.Level == Logging.LogLevel.Error)); } } }
public void DefaultStreamingDataSourceBaseUri() { using (var client = new LdClient( BasicConfig() .DataSource(Components.StreamingDataSource()) .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) .Build())) { var req = _stubHandler.Requests.ExpectValue(); Assert.Equal(StandardEndpoints.DefaultStreamingBaseUri, BaseUriOf(req.RequestUri)); } }
public void StreamingClientHasStreamProcessor() { var config = BasicConfig() .DataSource(Components.StreamingDataSource()) .ServiceEndpoints(FakeEndpoints) .Build(); using (var client = new LdClient(config)) { Assert.IsType <StreamProcessor>(client._dataSource); } }
public void StreamingClientStartupMessage() { var config = BasicConfig() .DataSource(Components.StreamingDataSource()) .ServiceEndpoints(FakeEndpoints) .Build(); using (var client = new LdClient(config)) { AssertLogMessage(false, LogLevel.Warn, "You should only disable the streaming API if instructed to do so by LaunchDarkly support"); } }
public void CustomConfigForStreaming() { TestDiagnosticConfig( c => c.DataSource( Components.StreamingDataSource() .InitialReconnectDelay(TimeSpan.FromSeconds(2)) ) .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), null, ExpectedConfigProps.Base() .Set("reconnectTimeMillis", 2000) ); }
public void StreamingClientHasStreamProcessor() { var config = Configuration.Builder(sdkKey) .DataSource(Components.StreamingDataSource().BaseUri(new Uri("http://fake"))) .Events(Components.NoEvents) .StartWaitTime(TimeSpan.Zero) .Logging(Components.Logging(testLogging)) .Build(); using (var client = new LdClient(config)) { Assert.IsType <StreamProcessor>(client._dataSource); } }
public void CustomStreamingDataSourceBaseUriWithDeprecatedMethod() { using (var client = new LdClient( BasicConfig() .DataSource(Components.StreamingDataSource().BaseUri(CustomUri)) .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) .Build())) { var req = _stubHandler.Requests.ExpectValue(); Assert.Equal(CustomUri, BaseUriOf(req.RequestUri)); AssertLogMessageRegex(false, LogLevel.Error, "You have set custom ServiceEndpoints without specifying"); } }
public void StreamingClientStartupMessage() { var config = Configuration.Builder(sdkKey) .Logging(Components.Logging(testLogging)) .DataSource(Components.StreamingDataSource().BaseUri(new Uri("http://fake"))) .Events(Components.NoEvents) .StartWaitTime(TimeSpan.Zero) .Build(); using (var client = new LdClient(config)) { Assert.False(logCapture.HasMessageWithText(LogLevel.Warn, "You should only disable the streaming API if instructed to do so by LaunchDarkly support"), logCapture.ToString()); } }
public void ErrorIsLoggedIfANecessaryUriIsNotSetWhenOtherCustomUrisAreSet() { var logCapture1 = Logs.Capture(); using (var client = new LdClient( BasicConfig() .DataSource(Components.StreamingDataSource()) .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) .Logging(logCapture1) .ServiceEndpoints(Components.ServiceEndpoints().Polling(CustomUri)) .Build())) { Assert.True(logCapture1.HasMessageWithRegex(LogLevel.Error, "You have set custom ServiceEndpoints without specifying the Streaming base URI")); } var logCapture2 = Logs.Capture(); using (var client = new LdClient( BasicConfig() .DataSource(Components.PollingDataSource()) .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) .Logging(logCapture2) .ServiceEndpoints(Components.ServiceEndpoints().Events(CustomUri)) .Build())) { Assert.True(logCapture2.HasMessageWithRegex(LogLevel.Error, "You have set custom ServiceEndpoints without specifying the Polling base URI")); } var logCapture3 = Logs.Capture(); using (var client = new LdClient( BasicConfig() .Events(Components.SendEvents()) .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) .Logging(logCapture3) .ServiceEndpoints(Components.ServiceEndpoints().Streaming(CustomUri)) .Build())) { Assert.True(logCapture3.HasMessageWithRegex(LogLevel.Error, "You have set custom ServiceEndpoints without specifying the Events base URI")); } }
public void ClientStartsInStreamingMode() { using (var streamServer = HttpServer.Start(ValidStreamingResponse)) { var config = BasicConfig() .DataSource(Components.StreamingDataSource()) .ServiceEndpoints(Components.ServiceEndpoints().Streaming(streamServer.Uri)) .StartWaitTime(TimeSpan.FromSeconds(5)) .Build(); using (var client = new LdClient(config)) { VerifyClientStartedAndHasExpectedData(client, streamServer); Assert.Empty(LogCapture.GetMessages().Where(m => m.Level == Logging.LogLevel.Warn)); Assert.Empty(LogCapture.GetMessages().Where(m => m.Level == Logging.LogLevel.Error)); } } }
public void HttpConfigurationIsAppliedToStreaming() { TestHttpUtils.TestWithSpecialHttpConfigurations( ValidStreamingResponse, (targetUri, httpConfig, server) => { var config = BasicConfig() .DataSource(Components.StreamingDataSource()) .Http(httpConfig) .ServiceEndpoints(Components.ServiceEndpoints().Streaming(targetUri)) .StartWaitTime(TimeSpan.FromSeconds(5)) .Build(); using (var client = new LdClient(config)) { VerifyClientStartedAndHasExpectedData(client, server); } }, TestLogger ); }
public void CustomConfigForStreaming() { TestDiagnosticConfig( c => c.DataSource( Components.StreamingDataSource() .InitialReconnectDelay(TimeSpan.FromSeconds(2)) ) .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), null, ExpectedConfigProps.Base() .WithStoreDefaults() .WithEventsDefaults() .Add("customBaseURI", false) .Add("customStreamURI", false) .Add("streamingDisabled", false) .Add("reconnectTimeMillis", 2000) .Add("usingRelayDaemon", false) ); TestDiagnosticConfig( c => c.DataSource( Components.StreamingDataSource() .BaseUri(new Uri("http://custom")) .InitialReconnectDelay(TimeSpan.FromSeconds(2)) ) .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), null, ExpectedConfigProps.Base() .WithStoreDefaults() .WithEventsDefaults() .Add("customBaseURI", false) .Add("customStreamURI", true) .Add("streamingDisabled", false) .Add("reconnectTimeMillis", 2000) .Add("usingRelayDaemon", false) ); }
/// <summary> /// Creates a new client to connect to LaunchDarkly with a custom configuration. /// </summary> /// <remarks> /// <para> /// Applications should instantiate a single instance for the lifetime of the application. In /// unusual cases where an application needs to evaluate feature flags from different LaunchDarkly /// projects or environments, you may create multiple clients, but they should still be retained /// for the lifetime of the application rather than created per request or per thread. /// </para> /// <para> /// Normally, the client will begin attempting to connect to LaunchDarkly as soon as you call the /// constructor. The constructor returns as soon as any of the following things has happened: /// </para> /// <list type="number"> /// <item><description> It has successfully connected to LaunchDarkly and received feature flag data. In this /// case, <see cref="Initialized"/> will be true, and the <see cref="DataSourceStatusProvider"/> /// will return a state of <see cref="DataSourceState.Valid"/>. </description></item> /// <item><description> It has not succeeded in connecting within the <see cref="ConfigurationBuilder.StartWaitTime(TimeSpan)"/> /// timeout (the default for this is 5 seconds). This could happen due to a network problem or a /// temporary service outage. In this case, <see cref="Initialized"/> will be false, and the /// <see cref="DataSourceStatusProvider"/> will return a state of <see cref="DataSourceState.Initializing"/>, /// indicating that the SDK will still continue trying to connect in the background. </description></item> /// <item><description> It has encountered an unrecoverable error: for instance, LaunchDarkly has rejected the /// SDK key. Since an invalid key will not become valid, the SDK will not retry in this case. /// <see cref="Initialized"/> will be false, and the <see cref="DataSourceStatusProvider"/> will /// return a state of <see cref="DataSourceState.Off"/>. </description></item> /// </list> /// <para> /// If you have specified <see cref="ConfigurationBuilder.Offline"/> mode or /// <see cref="Components.ExternalUpdatesOnly"/>, the constructor returns immediately without /// trying to connect to LaunchDarkly. /// </para> /// <para> /// Failure to connect to LaunchDarkly will never cause the constructor to throw an exception. /// Under any circumstance where it is not able to get feature flag data from LaunchDarkly (and /// therefore <see cref="Initialized"/> is false), if it does not have any other source of data /// (such as a persistent data store) then feature flag evaluations will behave the same as if /// the flags were not found: that is, they will return whatever default value is specified in /// your code. /// </para> /// </remarks> /// <param name="config">a client configuration object (which includes an SDK key)</param> /// <example> /// <code> /// var config = Configuration.Builder("my-sdk-key") /// .AllAttributesPrivate(true) /// .EventCapacity(1000) /// .Build(); /// var client = new LDClient(config); /// </code> /// </example> /// <seealso cref="LdClient.LdClient(string)"/> public LdClient(Configuration config) { _configuration = config; var logConfig = (config.LoggingConfigurationFactory ?? Components.Logging()) .CreateLoggingConfiguration(); _log = logConfig.LogAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.DefaultBase); _log.Info("Starting LaunchDarkly client {0}", AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))); _evalLog = _log.SubLogger(LogNames.EvaluationSubLog); var basicConfig = new BasicConfiguration(config, _log); var httpConfig = (config.HttpConfigurationFactory ?? Components.HttpConfiguration()) .CreateHttpConfiguration(basicConfig); ServerDiagnosticStore diagnosticStore = _configuration.DiagnosticOptOut ? null : new ServerDiagnosticStore(_configuration, basicConfig, httpConfig); var taskExecutor = new TaskExecutor(this, _log); var clientContext = new LdClientContext(basicConfig, httpConfig, diagnosticStore, taskExecutor); var dataStoreUpdates = new DataStoreUpdatesImpl(taskExecutor, _log.SubLogger(LogNames.DataStoreSubLog)); _dataStore = (_configuration.DataStoreFactory ?? Components.InMemoryDataStore) .CreateDataStore(clientContext, dataStoreUpdates); _dataStoreStatusProvider = new DataStoreStatusProviderImpl(_dataStore, dataStoreUpdates); var bigSegmentsConfig = (_configuration.BigSegmentsConfigurationFactory ?? Components.BigSegments(null)) .CreateBigSegmentsConfiguration(clientContext); _bigSegmentStoreWrapper = bigSegmentsConfig.Store is null ? null : new BigSegmentStoreWrapper( bigSegmentsConfig, taskExecutor, _log.SubLogger(LogNames.BigSegmentsSubLog) ); _bigSegmentStoreStatusProvider = new BigSegmentStoreStatusProviderImpl(_bigSegmentStoreWrapper); _evaluator = new Evaluator( GetFlag, GetSegment, _bigSegmentStoreWrapper == null ? (Func <string, BigSegmentsInternalTypes.BigSegmentsQueryResult>)null : _bigSegmentStoreWrapper.GetUserMembership, _log ); var eventProcessorFactory = config.Offline ? Components.NoEvents : (_configuration.EventProcessorFactory ?? Components.SendEvents()); _eventProcessor = eventProcessorFactory.CreateEventProcessor(clientContext); var dataSourceUpdates = new DataSourceUpdatesImpl(_dataStore, _dataStoreStatusProvider, taskExecutor, _log, logConfig.LogDataSourceOutageAsErrorAfter); IDataSourceFactory dataSourceFactory = config.Offline ? Components.ExternalUpdatesOnly : (_configuration.DataSourceFactory ?? Components.StreamingDataSource()); _dataSource = dataSourceFactory.CreateDataSource(clientContext, dataSourceUpdates); _dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdates); _flagTracker = new FlagTrackerImpl(dataSourceUpdates, (string key, User user) => JsonVariation(key, user, LdValue.Null)); var initTask = _dataSource.Start(); if (!(_dataSource is ComponentsImpl.NullDataSource)) { _log.Info("Waiting up to {0} milliseconds for LaunchDarkly client to start...", _configuration.StartWaitTime.TotalMilliseconds); } try { var success = initTask.Wait(_configuration.StartWaitTime); if (!success) { _log.Warn("Timeout encountered waiting for LaunchDarkly client initialization"); } } catch (AggregateException) { // StreamProcessor may throw an exception if initialization fails, because we want that behavior // in the Xamarin client. However, for backward compatibility we do not want to throw exceptions // from the LdClient constructor in the .NET client, so we'll just swallow this. } }
public void TestConfigForServiceEndpoints() { TestDiagnosticConfig( c => c.ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://custom")) .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), null, ExpectedConfigProps.Base() .Set("customBaseURI", false) // this is the polling base URI, not relevant in streaming mode .Set("customStreamURI", true) .Set("customEventsURI", true) ); TestDiagnosticConfig( c => c.ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://custom")) .DataSource(Components.PollingDataSource()) .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())), null, ExpectedConfigProps.Base() .WithPollingDefaults() .Set("customBaseURI", true) .Set("customEventsURI", true) ); TestDiagnosticConfig( c => c.ServiceEndpoints(Components.ServiceEndpoints() .Streaming("http://custom-streaming") .Polling("http://custom-polling") .Events("http://custom-events")) .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), null, ExpectedConfigProps.Base() .Set("customBaseURI", false) // this is the polling base URI, not relevant in streaming mode .Set("customStreamURI", true) .Set("customEventsURI", true) ); TestDiagnosticConfig( c => c.DataSource( #pragma warning disable CS0618 // using deprecated symbol Components.StreamingDataSource() .BaseUri(new Uri("http://custom")) #pragma warning restore CS0618 ) .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), null, ExpectedConfigProps.Base() .Set("customStreamURI", true) ); TestDiagnosticConfig( c => c.DataSource( #pragma warning disable CS0618 // using deprecated symbol Components.PollingDataSource().BaseUri(new Uri("http://custom")) #pragma warning restore CS0618 ) .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())), null, ExpectedConfigProps.Base() .WithPollingDefaults() .Set("customBaseURI", true) ); }