Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

Commit 23230c7

Browse files
authored
Add streaming functionality (Bump to 3.4.0) (#56)
1 parent dee93a6 commit 23230c7

File tree

11 files changed

+437
-78
lines changed

11 files changed

+437
-78
lines changed

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
All notable changes to the LaunchDarkly .NET SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
44

5+
## [3.4.0] - 2017-11-29
6+
### Added
7+
- SSE Streaming functionality as an alternative to Polling. :rocket:
8+
- New builder parameters to complement streaming functionality
9+
- `WithIsStreamingEnabled`: Set whether streaming mode should be enabled, `true` by default.
10+
- `WithStreamUri`: Set the base URL of the LaunchDarkly streaming server. May be used in conjunction with the [LaunchDarkly Relay Proxy](https://github.com/launchdarkly/ld-relay).
11+
- `WithReadTimeout`: The timeout when reading data from the streaming API. Defaults to 5 minutes
12+
- `WithReconnectTime`: The time to wait before attempting to reconnect to the streaming API. Defaults to 1 second
13+
- Apache 2.0 License
14+
15+
### Changed
16+
- Streaming is now used to retrieve feature flag configurations by default.
17+
- Minimum (and default) polling interval changed from 1 second to 30 seconds.
18+
- `PollingProcessor` no longer retries failed feature flag polling attempts.
19+
520
## [3.3.2] - 2017-08-30
621
### Changed
722
- Updated dependency versions. Thanks @ISkomorokh!
@@ -19,7 +34,7 @@ All notable changes to the LaunchDarkly .NET SDK will be documented in this file
1934
- Removed NETStandard.Library from dependencies so it isn't brought in by non-.NET core projects.
2035
- Project files migrated to current `*.csproj` standard
2136
- Fixed release that inadvertently removed the ability to set a custom HttpClientHandler
22-
37+
2338
## [3.2.0] - 2017-05-25
2439
### Added
2540
- Config option to use custom implementation of IFeatureStore

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2017 Catamorphic, Co.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

src/LaunchDarkly.Client/Configuration.cs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,74 @@ namespace LaunchDarkly.Client
88
public class Configuration
99
{
1010
public Uri BaseUri { get; internal set; }
11+
public Uri StreamUri { get; internal set; }
1112
public Uri EventsUri { get; internal set; }
1213
public string SdkKey { get; internal set; }
14+
/// <summary>
15+
/// Whether or not the streaming API should be used to receive flag updates. This is true by default.
16+
/// Streaming should only be disabled on the advice of LaunchDarkly support.
17+
/// </summary>
18+
public bool IsStreamingEnabled { get; internal set; }
1319
public int EventQueueCapacity { get; internal set; }
1420
public TimeSpan EventQueueFrequency { get; internal set; }
21+
/// <summary>
22+
/// Set the polling interval (when streaming is disabled). Values less than the default of 30 seconds will be set to 30 seconds.
23+
/// </summary>
1524
public TimeSpan PollingInterval { get; internal set; }
1625
public TimeSpan StartWaitTime { get; internal set; }
26+
/// <summary>
27+
/// The timeout when reading data from the EventSource API. If null, defaults to 5 minutes.
28+
/// </summary>
29+
public TimeSpan ReadTimeout { get; internal set; }
30+
/// <summary>
31+
/// The time to wait before attempting to reconnect to the EventSource API. If null, defaults to 1 second.
32+
/// </summary>
33+
public TimeSpan ReconnectTime { get; internal set; }
34+
/// <summary>
35+
/// The connection timeout. If null, defaults to 10 seconds.
36+
/// </summary>
1737
public TimeSpan HttpClientTimeout { get; internal set; }
1838
public HttpClientHandler HttpClientHandler { get; internal set; }
1939
public bool Offline { get; internal set; }
2040
internal IFeatureStore FeatureStore { get; set; }
2141

22-
public static TimeSpan DefaultPollingInterval = TimeSpan.FromSeconds(1);
2342

2443
internal static readonly string Version = ((AssemblyInformationalVersionAttribute) typeof(LdClient)
2544
.GetTypeInfo()
2645
.Assembly
2746
.GetCustomAttribute(typeof(AssemblyInformationalVersionAttribute)))
2847
.InformationalVersion;
2948

49+
public static TimeSpan DefaultPollingInterval = TimeSpan.FromSeconds(30);
3050
internal static readonly Uri DefaultUri = new Uri("https://app.launchdarkly.com");
51+
private static readonly Uri DefaultStreamUri = new Uri("https://stream.launchdarkly.com");
3152
private static readonly Uri DefaultEventsUri = new Uri("https://events.launchdarkly.com");
3253
private static readonly int DefaultEventQueueCapacity = 500;
3354
private static readonly TimeSpan DefaultEventQueueFrequency = TimeSpan.FromSeconds(5);
3455
private static readonly TimeSpan DefaultStartWaitTime = TimeSpan.FromSeconds(10);
56+
private static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromMinutes(5);
57+
private static readonly TimeSpan DefaultReconnectTime = TimeSpan.FromSeconds(1);
3558
private static readonly TimeSpan DefaultHttpClientTimeout = TimeSpan.FromSeconds(10);
3659

3760
public static Configuration Default(string sdkKey)
3861
{
3962
var defaultConfiguration = new Configuration
4063
{
4164
BaseUri = DefaultUri,
65+
StreamUri = DefaultStreamUri,
4266
EventsUri = DefaultEventsUri,
4367
EventQueueCapacity = DefaultEventQueueCapacity,
4468
EventQueueFrequency = DefaultEventQueueFrequency,
4569
PollingInterval = DefaultPollingInterval,
4670
StartWaitTime = DefaultStartWaitTime,
71+
ReadTimeout = DefaultReadTimeout,
72+
ReconnectTime = DefaultReconnectTime,
4773
HttpClientTimeout = DefaultHttpClientTimeout,
4874
HttpClientHandler = new HttpClientHandler(),
4975
Offline = false,
5076
SdkKey = sdkKey,
51-
FeatureStore = new InMemoryFeatureStore()
77+
FeatureStore = new InMemoryFeatureStore(),
78+
IsStreamingEnabled = true
5279
};
5380

5481
return defaultConfiguration;
@@ -81,6 +108,22 @@ public static Configuration WithUri(this Configuration configuration, Uri uri)
81108
return configuration;
82109
}
83110

111+
public static Configuration WithStreamUri(this Configuration configuration, string uri)
112+
{
113+
if (uri != null)
114+
configuration.StreamUri = new Uri(uri);
115+
116+
return configuration;
117+
}
118+
119+
public static Configuration WithStreamUri(this Configuration configuration, Uri uri)
120+
{
121+
if (uri != null)
122+
configuration.StreamUri = uri;
123+
124+
return configuration;
125+
}
126+
84127
public static Configuration WithEventsUri(this Configuration configuration, string uri)
85128
{
86129
if (uri != null)
@@ -165,6 +208,19 @@ public static Configuration WithHttpClientTimeout(this Configuration configurati
165208
return configuration;
166209
}
167210

211+
212+
public static Configuration WithReadTimeout(this Configuration configuration, TimeSpan timeSpan)
213+
{
214+
configuration.ReadTimeout = timeSpan;
215+
return configuration;
216+
}
217+
218+
public static Configuration WithReconnectTime(this Configuration configuration, TimeSpan timeSpan)
219+
{
220+
configuration.ReconnectTime = timeSpan;
221+
return configuration;
222+
}
223+
168224
public static Configuration WithFeatureStore(this Configuration configuration, IFeatureStore featureStore)
169225
{
170226
if (featureStore != null)
@@ -179,5 +235,11 @@ public static Configuration WithHttpClientHandler(this Configuration configurati
179235
configuration.HttpClientHandler = httpClientHandler;
180236
return configuration;
181237
}
238+
239+
public static Configuration WithIsStreamingEnabled(this Configuration configuration, bool enableStream)
240+
{
241+
configuration.IsStreamingEnabled = enableStream;
242+
return configuration;
243+
}
182244
}
183-
}
245+
}

src/LaunchDarkly.Client/FeatureRequestor.cs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,43 @@ internal FeatureRequestor(Configuration config)
2727

2828
// Returns a dictionary of the latest flags, or null if they have not been modified. Throws an exception if there
2929
// was a problem getting flags.
30-
internal async Task<IDictionary<string, FeatureFlag>> MakeAllRequestAsync()
30+
internal async Task<IDictionary<string, FeatureFlag>> GetAllFlagsAsync()
3131
{
3232
var cts = new CancellationTokenSource(_config.HttpClientTimeout);
33+
string content = null;
3334
try
3435
{
35-
return await FetchFeatureFlagsAsync(cts);
36+
content = await Get(cts, _uri);
37+
if(string.IsNullOrEmpty(content)) {
38+
return null;
39+
}
40+
var flags = JsonConvert.DeserializeObject<IDictionary<string, FeatureFlag>>(content);
41+
Logger.LogDebug("Get all flags returned " + flags.Keys.Count + " feature flags");
42+
return flags;
43+
}
44+
catch (Exception e)
45+
{
46+
// Using a new client after errors because: https://github.com/dotnet/corefx/issues/11224
47+
_httpClient?.Dispose();
48+
_httpClient = _config.HttpClient();
49+
Logger.LogError("Error getting feature flags: " + Util.ExceptionMessage(e));
50+
Logger.LogDebug(e.ToString());
51+
return null;
52+
}
53+
}
54+
55+
// Returns the latest version of a flag, or null if it has not been modified. Throws an exception if there
56+
// was a problem getting flags.
57+
internal async Task<FeatureFlag> GetFlagAsync(string featureKey)
58+
{
59+
var cts = new CancellationTokenSource(_config.HttpClientTimeout);
60+
string content = null;
61+
Uri flagPath = new Uri(_uri + "/" + featureKey);
62+
try
63+
{
64+
content = await Get(cts, flagPath);
65+
var flag = JsonConvert.DeserializeObject<FeatureFlag>(content);
66+
return flag;
3667
}
3768
catch (Exception e)
3869
{
@@ -46,7 +77,9 @@ internal async Task<IDictionary<string, FeatureFlag>> MakeAllRequestAsync()
4677
cts = new CancellationTokenSource(_config.HttpClientTimeout);
4778
try
4879
{
49-
return await FetchFeatureFlagsAsync(cts);
80+
content = await Get(cts, flagPath);
81+
var flag = JsonConvert.DeserializeObject<FeatureFlag>(content);
82+
return flag;
5083
}
5184
catch (TaskCanceledException tce)
5285
{
@@ -60,10 +93,10 @@ internal async Task<IDictionary<string, FeatureFlag>> MakeAllRequestAsync()
6093
throw;
6194
}
6295
//Otherwise this was a request timeout.
63-
throw new Exception("Get Features with URL: " + _uri.AbsoluteUri + " timed out after : " +
96+
throw new Exception("Get Feature with URL: " + flagPath + " timed out after : " +
6497
_config.HttpClientTimeout);
6598
}
66-
catch (Exception ex)
99+
catch (Exception)
67100
{
68101
// Using a new client after errors because: https://github.com/dotnet/corefx/issues/11224
69102
_httpClient?.Dispose();
@@ -72,11 +105,10 @@ internal async Task<IDictionary<string, FeatureFlag>> MakeAllRequestAsync()
72105
}
73106
}
74107
}
75-
76-
private async Task<IDictionary<string, FeatureFlag>> FetchFeatureFlagsAsync(CancellationTokenSource cts)
108+
private async Task<string> Get(CancellationTokenSource cts, Uri path)
77109
{
78-
Logger.LogDebug("Getting all flags with uri: " + _uri.AbsoluteUri);
79-
var request = new HttpRequestMessage(HttpMethod.Get, _uri);
110+
Logger.LogDebug("Getting flags with uri: " + path.AbsoluteUri);
111+
var request = new HttpRequestMessage(HttpMethod.Get, path);
80112
if (_etag != null)
81113
{
82114
request.Headers.IfNoneMatch.Add(_etag);
@@ -92,10 +124,7 @@ private async Task<IDictionary<string, FeatureFlag>> FetchFeatureFlagsAsync(Canc
92124
_etag = response.Headers.ETag;
93125
//We ensure the status code after checking for 304, because 304 isn't considered success
94126
response.EnsureSuccessStatusCode();
95-
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
96-
var flags = JsonConvert.DeserializeObject<IDictionary<string, FeatureFlag>>(content);
97-
Logger.LogDebug("Get all flags returned " + flags.Keys.Count + " feature flags");
98-
return flags;
127+
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
99128
}
100129
}
101130
}

src/LaunchDarkly.Client/IUpdateProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace LaunchDarkly.Client
55
{
66
internal interface IUpdateProcessor : IDisposable
77
{
8-
TaskCompletionSource<bool> Start();
8+
Task<bool> Start();
99
bool Initialized();
1010
}
1111
}
Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,45 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
2-
3-
<PropertyGroup>
4-
<VersionPrefix>3.3.2</VersionPrefix>
5-
<TargetFrameworks>netstandard1.6;net45</TargetFrameworks>
6-
<DebugType>portable</DebugType>
7-
<AssemblyName>LaunchDarkly.Client</AssemblyName>
8-
<OutputType>Library</OutputType>
9-
<PackageId>LaunchDarkly.Client</PackageId>
10-
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
11-
<GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
12-
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
13-
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
14-
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
15-
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
16-
</PropertyGroup>
17-
18-
<ItemGroup>
19-
<None Include="app.config" />
20-
</ItemGroup>
21-
22-
<ItemGroup>
23-
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.2" />
24-
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.2" />
25-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
26-
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="1.1.1" />
27-
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
28-
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
29-
<PackageReference Include="Microsoft.Extensions.Primitives" Version="1.1.1" />
30-
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
31-
</ItemGroup>
32-
33-
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
34-
<Reference Include="System" />
35-
<Reference Include="Microsoft.CSharp" />
36-
<PackageReference Include="System.Runtime" Version="4.3.0" />
37-
<PackageReference Include="System.Net.Http" Version="4.3.1" />
38-
</ItemGroup>
39-
40-
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
41-
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="4.3.0" />
42-
</ItemGroup>
43-
44-
<PropertyGroup>
45-
<AssemblyOriginatorKeyFile>../../LaunchDarkly.snk</AssemblyOriginatorKeyFile>
46-
<SignAssembly>true</SignAssembly>
47-
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
48-
</PropertyGroup>
49-
</Project>
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<VersionPrefix>3.4.0</VersionPrefix>
4+
<TargetFrameworks>netstandard1.6;net45</TargetFrameworks>
5+
<DebugType>portable</DebugType>
6+
<AssemblyName>LaunchDarkly.Client</AssemblyName>
7+
<OutputType>Library</OutputType>
8+
<PackageId>LaunchDarkly.Client</PackageId>
9+
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
10+
<GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
11+
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
12+
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
13+
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
14+
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
15+
</PropertyGroup>
16+
<ItemGroup>
17+
<None Include="app.config" />
18+
</ItemGroup>
19+
<ItemGroup>
20+
<PackageReference Include="LaunchDarkly.EventSource" Version="2.1.0" />
21+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.2" />
22+
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.2" />
23+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
24+
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="1.1.1" />
25+
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
26+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
27+
<PackageReference Include="Microsoft.Extensions.Primitives" Version="1.1.1" />
28+
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
29+
</ItemGroup>
30+
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
31+
<Reference Include="System" />
32+
<Reference Include="Microsoft.CSharp" />
33+
<PackageReference Include="System.Runtime" Version="4.3.0" />
34+
<PackageReference Include="System.Net.Http" Version="4.3.2" />
35+
</ItemGroup>
36+
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
37+
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="4.3.0" />
38+
</ItemGroup>
39+
40+
<PropertyGroup>
41+
<AssemblyOriginatorKeyFile>../../LaunchDarkly.snk</AssemblyOriginatorKeyFile>
42+
<SignAssembly>true</SignAssembly>
43+
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
44+
</PropertyGroup>
45+
</Project>

0 commit comments

Comments
 (0)