Adding connection state
This commit is contained in:
parent
3ba95b98af
commit
966470d269
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
{
|
{
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private int _connectionState = ConnectionState.Initial;
|
||||||
private IChannelConnection<Message> _transportChannel;
|
private IChannelConnection<Message> _transportChannel;
|
||||||
private ITransport _transport;
|
private ITransport _transport;
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
_logger = _loggerFactory.CreateLogger<Connection>();
|
_logger = _loggerFactory.CreateLogger<Connection>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(Uri url, ITransport transport) => StartAsync((ITransport)null, null);
|
public Task StartAsync() => StartAsync(null, null);
|
||||||
public Task StartAsync(HttpClient httpClient) => StartAsync(null, httpClient);
|
public Task StartAsync(HttpClient httpClient) => StartAsync(null, httpClient);
|
||||||
public Task StartAsync(ITransport transport) => StartAsync(transport, null);
|
public Task StartAsync(ITransport transport) => StartAsync(transport, null);
|
||||||
|
|
||||||
|
|
@ -45,24 +46,24 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
// TODO: make transport optional
|
// TODO: make transport optional
|
||||||
_transport = transport ?? throw new ArgumentNullException(nameof(transport));
|
_transport = transport ?? throw new ArgumentNullException(nameof(transport));
|
||||||
|
|
||||||
var connectUrl = await GetConnectUrl(Url, httpClient, _logger);
|
if (Interlocked.CompareExchange(ref _connectionState, ConnectionState.Connecting, ConnectionState.Initial)
|
||||||
|
!= ConnectionState.Initial)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot start a connection that is not in the Initial state.");
|
||||||
|
}
|
||||||
|
|
||||||
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
|
||||||
var transportToApplication = Channel.CreateUnbounded<Message>();
|
|
||||||
var applicationSide = new ChannelConnection<Message>(transportToApplication, applicationToTransport);
|
|
||||||
_transportChannel = new ChannelConnection<Message>(applicationToTransport, transportToApplication);
|
|
||||||
|
|
||||||
|
|
||||||
// Start the transport, giving it one end of the pipeline
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await transport.StartAsync(connectUrl, applicationSide);
|
var connectUrl = await GetConnectUrl(Url, httpClient, _logger);
|
||||||
|
await StartTransport(connectUrl);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
_logger.LogError("Failed to start connection. Error starting transport '{0}': {1}", transport.GetType().Name, ex);
|
Interlocked.Exchange(ref _connectionState, ConnectionState.Disconnected);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Interlocked.Exchange(ref _connectionState, ConnectionState.Connected);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Uri> GetConnectUrl(Uri url, HttpClient httpClient, ILogger logger)
|
private static async Task<Uri> GetConnectUrl(Uri url, HttpClient httpClient, ILogger logger)
|
||||||
|
|
@ -101,6 +102,29 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartTransport(Uri connectUrl)
|
||||||
|
{
|
||||||
|
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
||||||
|
var transportToApplication = Channel.CreateUnbounded<Message>();
|
||||||
|
var applicationSide = new ChannelConnection<Message>(transportToApplication, applicationToTransport);
|
||||||
|
|
||||||
|
_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
|
||||||
|
Input.Completion.ContinueWith(t => Interlocked.Exchange(ref _connectionState, ConnectionState.Disconnected));
|
||||||
|
#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
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _transport.StartAsync(connectUrl, applicationSide);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Failed to start connection. Error starting transport '{0}': {1}", _transport.GetType().Name, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task<bool> ReceiveAsync(ReceiveData receiveData)
|
public Task<bool> ReceiveAsync(ReceiveData receiveData)
|
||||||
{
|
{
|
||||||
return ReceiveAsync(receiveData, CancellationToken.None);
|
return ReceiveAsync(receiveData, CancellationToken.None);
|
||||||
|
|
@ -156,6 +180,12 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
|
|
||||||
public async Task<bool> SendAsync(byte[] data, MessageType type, CancellationToken cancellationToken)
|
public async Task<bool> SendAsync(byte[] data, MessageType type, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// TODO: data == null?
|
||||||
|
if (_connectionState != ConnectionState.Connected)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var message = new Message(ReadableBuffer.Create(data).Preserve(), type);
|
var message = new Message(ReadableBuffer.Create(data).Preserve(), type);
|
||||||
|
|
||||||
while (await Output.WaitToWriteAsync(cancellationToken))
|
while (await Output.WaitToWriteAsync(cancellationToken))
|
||||||
|
|
@ -171,6 +201,8 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
|
|
||||||
public async Task StopAsync()
|
public async Task StopAsync()
|
||||||
{
|
{
|
||||||
|
Interlocked.Exchange(ref _connectionState, ConnectionState.Disconnected);
|
||||||
|
|
||||||
if (_transportChannel != null)
|
if (_transportChannel != null)
|
||||||
{
|
{
|
||||||
Output.TryComplete();
|
Output.TryComplete();
|
||||||
|
|
@ -184,6 +216,8 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Interlocked.Exchange(ref _connectionState, ConnectionState.Disconnected);
|
||||||
|
|
||||||
if (_transportChannel != null)
|
if (_transportChannel != null)
|
||||||
{
|
{
|
||||||
Output.TryComplete();
|
Output.TryComplete();
|
||||||
|
|
@ -194,5 +228,13 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
_transport.Dispose();
|
_transport.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ConnectionState
|
||||||
|
{
|
||||||
|
public const int Initial = 0;
|
||||||
|
public const int Connecting = 1;
|
||||||
|
public const int Connected = 2;
|
||||||
|
public const int Disconnected = 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,114 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
using (new Connection(new Uri("http://fakeuri.org"))) { }
|
using (new Connection(new Uri("http://fakeuri.org"))) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotStartRunningConnection()
|
||||||
|
{
|
||||||
|
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/")))
|
||||||
|
{
|
||||||
|
await connection.StartAsync(longPollingTransport, httpClient);
|
||||||
|
var exception =
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
async () => await connection.StartAsync(longPollingTransport));
|
||||||
|
Assert.Equal("Cannot start a connection that is not in the Initial state.", exception.Message);
|
||||||
|
|
||||||
|
await connection.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotStartStoppedConnection()
|
||||||
|
{
|
||||||
|
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/")))
|
||||||
|
{
|
||||||
|
await connection.StartAsync(longPollingTransport, httpClient);
|
||||||
|
await connection.StopAsync();
|
||||||
|
var exception =
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
async () => await connection.StartAsync(longPollingTransport));
|
||||||
|
|
||||||
|
Assert.Equal("Cannot start a connection that is not in the Initial state.", exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotStartDisposedConnection()
|
||||||
|
{
|
||||||
|
using (var httpClient = new HttpClient())
|
||||||
|
using (var longPollingTransport = new LongPollingTransport(httpClient, new LoggerFactory()))
|
||||||
|
{
|
||||||
|
var connection = new Connection(new Uri("http://fakeuri.org/"));
|
||||||
|
connection.Dispose();
|
||||||
|
var exception =
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
async () => await connection.StartAsync(longPollingTransport));
|
||||||
|
|
||||||
|
Assert.Equal("Cannot start a connection that is not in the Initial state.", exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendReturnsFalseIfConnectionIsNotStarted()
|
||||||
|
{
|
||||||
|
using (var connection = new Connection(new Uri("http://fakeuri.org/")))
|
||||||
|
{
|
||||||
|
Assert.False(await connection.SendAsync(new byte[0], MessageType.Binary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendReturnsFalseIfConnectionIsStopped()
|
||||||
|
{
|
||||||
|
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/")))
|
||||||
|
{
|
||||||
|
await connection.StartAsync(longPollingTransport, httpClient);
|
||||||
|
await connection.StopAsync();
|
||||||
|
|
||||||
|
Assert.False(await connection.SendAsync(new byte[0], MessageType.Binary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendReturnsFalseIfConnectionIsDisposed()
|
||||||
|
{
|
||||||
|
var connection = new Connection(new Uri("http://fakeuri.org/"));
|
||||||
|
connection.Dispose();
|
||||||
|
Assert.False(await connection.SendAsync(new byte[0], MessageType.Binary));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TransportIsStoppedWhenConnectionIsStopped()
|
public async Task TransportIsStoppedWhenConnectionIsStopped()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue