Adding rasing Connected and Closed events
This commit is contained in:
parent
966470d269
commit
4f6306f352
|
|
@ -25,6 +25,10 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
|
|
||||||
public Uri Url { get; }
|
public Uri Url { get; }
|
||||||
|
|
||||||
|
public event Action Connected;
|
||||||
|
public event Action<byte[], Format> Received;
|
||||||
|
public event Action<Exception> Closed;
|
||||||
|
|
||||||
public Connection(Uri url)
|
public Connection(Uri url)
|
||||||
: this(url, null)
|
: this(url, null)
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -64,6 +68,13 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Exchange(ref _connectionState, ConnectionState.Connected);
|
Interlocked.Exchange(ref _connectionState, ConnectionState.Connected);
|
||||||
|
|
||||||
|
// Do not "simplify" - events can be removed from a different thread
|
||||||
|
var connectedEventHandler = Connected;
|
||||||
|
if (connectedEventHandler != null)
|
||||||
|
{
|
||||||
|
connectedEventHandler();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Uri> GetConnectUrl(Uri url, HttpClient httpClient, ILogger logger)
|
private static async Task<Uri> GetConnectUrl(Uri url, HttpClient httpClient, ILogger logger)
|
||||||
|
|
@ -110,7 +121,17 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
|
|
||||||
_transportChannel = new ChannelConnection<Message>(applicationToTransport, transportToApplication);
|
_transportChannel = new ChannelConnection<Message>(applicationToTransport, transportToApplication);
|
||||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||||
Input.Completion.ContinueWith(t => Interlocked.Exchange(ref _connectionState, ConnectionState.Disconnected));
|
Input.Completion.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _connectionState, ConnectionState.Disconnected);
|
||||||
|
|
||||||
|
// Do not "simplify" - events can be removed from a different thread
|
||||||
|
var closedEventHandler = Closed;
|
||||||
|
if (closedEventHandler != null)
|
||||||
|
{
|
||||||
|
closedEventHandler(t.IsFaulted ? t.Exception.InnerException : null);
|
||||||
|
}
|
||||||
|
});
|
||||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||||
|
|
||||||
// Start the transport, giving it one end of the pipeline
|
// Start the transport, giving it one end of the pipeline
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,166 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
Assert.False(await connection.SendAsync(new byte[0], MessageType.Binary));
|
Assert.False(await connection.SendAsync(new byte[0], MessageType.Binary));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConnectedEventRaisedWhenTheClientIsConnected()
|
||||||
|
{
|
||||||
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) };
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
using (var longPollingTransport = new LongPollingTransport(httpClient, new LoggerFactory()))
|
||||||
|
using (var connection = new Connection(new Uri("http://fakeuri.org/")))
|
||||||
|
{
|
||||||
|
var connectedEventRaised = false;
|
||||||
|
connection.Connected += () => connectedEventRaised = true;
|
||||||
|
|
||||||
|
await connection.StartAsync(longPollingTransport, httpClient);
|
||||||
|
|
||||||
|
Assert.True(connectedEventRaised);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConnectedEventNotRaisedWhenTransportFailsToStart()
|
||||||
|
{
|
||||||
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) };
|
||||||
|
});
|
||||||
|
|
||||||
|
var mockTransport = new Mock<ITransport>();
|
||||||
|
mockTransport.Setup(t => t.StartAsync(It.IsAny<Uri>(), It.IsAny<IChannelConnection<Message>>()))
|
||||||
|
.Returns(Task.FromException(new InvalidOperationException("Transport failed to start")));
|
||||||
|
|
||||||
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
using (var connection = new Connection(new Uri("http://fakeuri.org/")))
|
||||||
|
{
|
||||||
|
var connectedEventRaised = false;
|
||||||
|
connection.Connected += () => connectedEventRaised = true;
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
async () => await connection.StartAsync(mockTransport.Object, httpClient));
|
||||||
|
|
||||||
|
Assert.False(connectedEventRaised);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Skip = "Need draining to make it work. Receive event may fix that.")]
|
||||||
|
public async Task ClosedEventRaisedWhenTheClientIsStopped()
|
||||||
|
{
|
||||||
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) };
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
using (var longPollingTransport = new LongPollingTransport(httpClient, new LoggerFactory()))
|
||||||
|
using (var connection = new Connection(new Uri("http://fakeuri.org/")))
|
||||||
|
{
|
||||||
|
var closedEventTcs = new TaskCompletionSource<Exception>();
|
||||||
|
connection.Closed += e => closedEventTcs.SetResult(e);
|
||||||
|
|
||||||
|
await connection.StartAsync(longPollingTransport, httpClient);
|
||||||
|
await connection.StopAsync();
|
||||||
|
|
||||||
|
|
||||||
|
Assert.Equal(closedEventTcs.Task, await Task.WhenAny(Task.Delay(1000), closedEventTcs.Task));
|
||||||
|
// in case of clean disconnect error should be null
|
||||||
|
Assert.Null(await closedEventTcs.Task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Skip = "Need draining to make it work. Receive event may fix that.")]
|
||||||
|
public async Task ClosedEventRaisedWhenTheClientIsDisposed()
|
||||||
|
{
|
||||||
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) };
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
using (var longPollingTransport = new LongPollingTransport(httpClient, new LoggerFactory()))
|
||||||
|
{
|
||||||
|
var connection = new Connection(new Uri("http://fakeuri.org/"));
|
||||||
|
var closedEventTcs = new TaskCompletionSource<Exception>();
|
||||||
|
connection.Closed += e => closedEventTcs.TrySetResult(e);
|
||||||
|
|
||||||
|
using (connection)
|
||||||
|
{
|
||||||
|
await connection.StartAsync(longPollingTransport, httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(closedEventTcs.Task, await Task.WhenAny(Task.Delay(1000), closedEventTcs.Task));
|
||||||
|
Assert.Null(await closedEventTcs.Task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClosedEventRaisedWhenConnectionToServerLost()
|
||||||
|
{
|
||||||
|
var allowPollTcs = new TaskCompletionSource<object>();
|
||||||
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
if (request.RequestUri.AbsolutePath.EndsWith("/poll"))
|
||||||
|
{
|
||||||
|
await allowPollTcs.Task;
|
||||||
|
return new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent(string.Empty) };
|
||||||
|
}
|
||||||
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) };
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
using (var longPollingTransport = new LongPollingTransport(httpClient, new LoggerFactory()))
|
||||||
|
using (var connection = new Connection(new Uri("http://fakeuri.org/")))
|
||||||
|
{
|
||||||
|
var closedEventTcs = new TaskCompletionSource<Exception>();
|
||||||
|
connection.Closed += e => closedEventTcs.TrySetResult(e);
|
||||||
|
await connection.StartAsync(longPollingTransport, httpClient);
|
||||||
|
|
||||||
|
var receiveTask = connection.ReceiveAsync(new ReceiveData());
|
||||||
|
allowPollTcs.TrySetResult(null);
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await receiveTask);
|
||||||
|
|
||||||
|
Assert.Equal(closedEventTcs.Task, await Task.WhenAny(Task.Delay(1000), closedEventTcs.Task));
|
||||||
|
Assert.IsType<HttpRequestException>(await closedEventTcs.Task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClosedEventNotRaisedWhenTheClientIsStoppedButWasNeverStarted()
|
||||||
|
{
|
||||||
|
using (var connection = new Connection(new Uri("http://fakeuri.org/")))
|
||||||
|
{
|
||||||
|
bool closedEventRaised = false;
|
||||||
|
connection.Closed += e => closedEventRaised = true;
|
||||||
|
|
||||||
|
await connection.StopAsync();
|
||||||
|
Assert.False(closedEventRaised);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TransportIsStoppedWhenConnectionIsStopped()
|
public async Task TransportIsStoppedWhenConnectionIsStopped()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue