Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/LaunchDarkly.EventSource/EventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,10 @@ private async Task ConnectToEventSourceAsync(CancellationToken cancellationToken
_eventDataUtf8ByteBuffer = null;

var svc = GetEventSourceService(_configuration);

svc.ConnectionOpened += (o, e) => {
_lastSuccessfulConnectionTime = DateTime.Now;
SetReadyState(ReadyState.Open, OnOpened);
SetReadyState(ReadyState.Open, OnOpened, e.Headers);
};
svc.ConnectionClosed += (o, e) => { SetReadyState(ReadyState.Closed, OnClosed); };

Expand All @@ -321,7 +321,7 @@ private void Close(ReadyState state)
_logger.Debug("Close({0}) - state was {1}", state, ReadyState);
SetReadyState(state, OnClosed);
}

private void ProcessResponseLineString(string content)
{
if (content == null)
Expand Down Expand Up @@ -353,20 +353,22 @@ private void ProcessResponseLineUtf8(Utf8ByteSpan content)
}


private void SetReadyState(ReadyState state, Action<StateChangedEventArgs> action = null)
private void SetReadyState(ReadyState state, Action<StateChangedEventArgs> action = null,
IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers = null)
{
lock (this)
{
if (_readyState == state || _readyState == ReadyState.Shutdown)
{
return;
}

_readyState = state;
}

if (action != null)
{
action(new StateChangedEventArgs(state));
action(new StateChangedEventArgs(state, headers));
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/LaunchDarkly.EventSource/EventSourceOpenedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;

namespace LaunchDarkly.EventSource
{
/// <summary>
/// Event arguments for the EventSourceService opened event.
/// </summary>
internal class EventSourceOpenedEventArgs: EventArgs
{
/// <summary>
/// Construct new event arguments.
/// </summary>
/// <param name="headers">headers from the HTTP response</param>
public EventSourceOpenedEventArgs(IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers)
{
Headers = headers;
}

/// <summary>
/// Response headers from the underlying HTTP request.
/// </summary>
public IEnumerable<KeyValuePair<string,IEnumerable<string>>> Headers { get; }
}
}
9 changes: 5 additions & 4 deletions src/LaunchDarkly.EventSource/EventSourceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ internal class EventSourceService
/// <summary>
/// Occurs when the connection to the EventSource API has been opened.
/// </summary>
public event EventHandler<EventArgs> ConnectionOpened;
public event EventHandler<EventSourceOpenedEventArgs> ConnectionOpened;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EventSource service is internal, so there isn't any external impact from this change.

/// <summary>
/// Occurs when the connection to the EventSource API has been closed.
/// </summary>
Expand Down Expand Up @@ -108,7 +108,8 @@ CancellationToken cancellationToken
throw new EventSourceServiceCancelledException(
string.Format(Resources.ErrorWrongEncoding, encoding.HeaderName));
}
OnConnectionOpened();

OnConnectionOpened(response.Headers);

if (_configuration.PreferDataAsUtf8Bytes)
{
Expand Down Expand Up @@ -257,9 +258,9 @@ private void ValidateResponse(HttpResponseMessage response)
}
}

private void OnConnectionOpened()
private void OnConnectionOpened(HttpHeaders headers)
{
ConnectionOpened?.Invoke(this, EventArgs.Empty);
ConnectionOpened?.Invoke(this, new EventSourceOpenedEventArgs(headers));
}

private void OnConnectionClosed()
Expand Down
15 changes: 13 additions & 2 deletions src/LaunchDarkly.EventSource/StateChangedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace LaunchDarkly.EventSource
{
Expand All @@ -16,13 +17,23 @@ public class StateChangedEventArgs : EventArgs
/// </value>
public ReadyState ReadyState { get; }

/// <summary>
/// Get the response headers. Only populated for <see cref="ReadyState.Open"/>.
/// <value>
/// A collection of header values when the ReadyState is Open, or null.
/// </value>
/// </summary>
public IEnumerable<KeyValuePair<string,IEnumerable<string>>> Headers { get; }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the public facing change.


/// <summary>
/// Initializes a new instance of the <see cref="StateChangedEventArgs"/> class.
/// </summary>
/// <param name="readyState">One of the <see cref="EventSource.ReadyState"/> values, which represents the state of the EventSource connection.</param>
public StateChangedEventArgs(ReadyState readyState)
/// <param name="headers">Response headers when the <see cref="StateChangedEventArgs.ReadyState"/> is <see cref="ReadyState.Open"/>. Otherwise null.</param>
public StateChangedEventArgs(ReadyState readyState, IEnumerable<KeyValuePair<string,IEnumerable<string>>> headers = null)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constructor is public, but isn't something someone would really need to use in normal operation.

Likely it should have been internal.

{
ReadyState = readyState;
Headers = headers;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,36 @@
}
}

[Fact]
public async void OpenedEventIncludesHeaders()

Check warning on line 169 in test/LaunchDarkly.EventSource.Tests/EventSourceHttpBehaviorTest.cs

View workflow job for this annotation

GitHub Actions / build-and-run (windows-latest)

Support for 'async void' unit tests is being removed from xUnit.net v3. To simplify upgrading, convert the test to 'async Task' instead. (https://xunit.net/xunit.analyzers/rules/xUnit1048)

Check warning on line 169 in test/LaunchDarkly.EventSource.Tests/EventSourceHttpBehaviorTest.cs

View workflow job for this annotation

GitHub Actions / build-and-run (ubuntu-latest)

Support for 'async void' unit tests is being removed from xUnit.net v3. To simplify upgrading, convert the test to 'async Task' instead. (https://xunit.net/xunit.analyzers/rules/xUnit1048)
{
var taskComplete = new TaskCompletionSource<bool>();

Handler streamHandler = Handlers.StartChunks("text/event-stream")
.Then(async ctx =>
{
await Handlers.WriteChunkString("data:potato\n\n")(ctx);
await taskComplete.Task;
});
using (var server = HttpServer.Start(streamHandler))
{
using (var es = MakeEventSource(server.Uri))
{
es.Opened += (sender, args) =>
{
// The transfer-encoding header should always be present for an SSE stream.
taskComplete.SetResult(
args.Headers.FirstOrDefault(item => string.Equals(item.Key, "transfer-encoding", StringComparison.OrdinalIgnoreCase)).Value != null);
};
_ = Task.Run(es.StartAsync);

await taskComplete.Task;
}
}

Assert.True(taskComplete.Task.Result);

Check warning on line 195 in test/LaunchDarkly.EventSource.Tests/EventSourceHttpBehaviorTest.cs

View workflow job for this annotation

GitHub Actions / build-and-run (windows-latest)

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

Check warning on line 195 in test/LaunchDarkly.EventSource.Tests/EventSourceHttpBehaviorTest.cs

View workflow job for this annotation

GitHub Actions / build-and-run (ubuntu-latest)

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)
}

[Fact]
public void ReceiveEventStreamInChunks()
{
Expand Down
Loading