Clean up client ITransport interface (#1893)
- Transports own the pipe creation. The fact they are 2 sides is an implementation detail.
This commit is contained in:
parent
86083c0302
commit
e3da7feab4
|
|
@ -9,7 +9,9 @@ using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
|
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
|
||||||
|
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Internal;
|
using Microsoft.AspNetCore.Http.Connections.Internal;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -30,8 +32,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
||||||
private bool _started;
|
private bool _started;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private IDuplexPipe _transportPipe;
|
|
||||||
|
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly HttpOptions _httpOptions;
|
private readonly HttpOptions _httpOptions;
|
||||||
private ITransport _transport;
|
private ITransport _transport;
|
||||||
|
|
@ -49,11 +49,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
if (_transportPipe == null)
|
if (_transport == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Cannot access the {nameof(Transport)} pipe before the connection has started.");
|
throw new InvalidOperationException($"Cannot access the {nameof(Transport)} pipe before the connection has started.");
|
||||||
}
|
}
|
||||||
return _transportPipe;
|
return _transport;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
||||||
|
|
||||||
public async Task DisposeAsync() => await DisposeAsyncCore().ForceAsync();
|
public async Task DisposeAsync() => await DisposeAsyncCore().ForceAsync();
|
||||||
|
|
||||||
private async Task DisposeAsyncCore(Exception exception = null)
|
private async Task DisposeAsyncCore()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
|
|
@ -167,10 +167,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
||||||
{
|
{
|
||||||
Log.DisposingHttpConnection(_logger);
|
Log.DisposingHttpConnection(_logger);
|
||||||
|
|
||||||
// Complete our ends of the pipes.
|
|
||||||
_transportPipe.Input.Complete(exception);
|
|
||||||
_transportPipe.Output.Complete(exception);
|
|
||||||
|
|
||||||
// Stop the transport, but we don't care if it throws.
|
// Stop the transport, but we don't care if it throws.
|
||||||
// The transport should also have completed the pipe with this exception.
|
// The transport should also have completed the pipe with this exception.
|
||||||
try
|
try
|
||||||
|
|
@ -331,34 +327,30 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
||||||
|
|
||||||
private async Task StartTransport(Uri connectUrl, HttpTransportType transportType, TransferFormat transferFormat)
|
private async Task StartTransport(Uri connectUrl, HttpTransportType transportType, TransferFormat transferFormat)
|
||||||
{
|
{
|
||||||
// Create the pipe pair (Application's writer is connected to Transport's reader, and vice versa)
|
|
||||||
var options = new PipeOptions(writerScheduler: PipeScheduler.ThreadPool, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false, pauseWriterThreshold: 0, resumeWriterThreshold: 0);
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
|
||||||
|
|
||||||
// Construct the transport
|
// Construct the transport
|
||||||
var transport = _transportFactory.CreateTransport(transportType);
|
var transport = _transportFactory.CreateTransport(transportType);
|
||||||
|
|
||||||
// Start the transport, giving it one end of the pipe
|
// Start the transport, giving it one end of the pipe
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await transport.StartAsync(connectUrl, pair.Application, transferFormat, this);
|
await transport.StartAsync(connectUrl, transferFormat);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.ErrorStartingTransport(_logger, transport, ex);
|
Log.ErrorStartingTransport(_logger, transport, ex);
|
||||||
|
|
||||||
// Clean up pipes and null out transport when we fail to start.
|
|
||||||
pair.Transport.Input.Complete();
|
|
||||||
pair.Transport.Output.Complete();
|
|
||||||
pair.Application.Input.Complete();
|
|
||||||
pair.Application.Output.Complete();
|
|
||||||
_transport = null;
|
_transport = null;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transportType == HttpTransportType.LongPolling)
|
||||||
|
{
|
||||||
|
// Disable keep alives for long polling
|
||||||
|
Features.Set<IConnectionInherentKeepAliveFeature>(new ConnectionInherentKeepAliveFeature(_httpClient.Timeout));
|
||||||
|
}
|
||||||
|
|
||||||
// We successfully started, set the transport properties (we don't want to set these until the transport is definitely running).
|
// We successfully started, set the transport properties (we don't want to set these until the transport is definitely running).
|
||||||
_transport = transport;
|
_transport = transport;
|
||||||
_transportPipe = pair.Transport;
|
|
||||||
|
|
||||||
Log.TransportStarted(_logger, _transport);
|
Log.TransportStarted(_logger, _transport);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ using Microsoft.AspNetCore.Connections;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http.Connections.Client
|
namespace Microsoft.AspNetCore.Http.Connections.Client
|
||||||
{
|
{
|
||||||
public interface ITransport
|
public interface ITransport : IDuplexPipe
|
||||||
{
|
{
|
||||||
Task StartAsync(Uri url, IDuplexPipe application, TransferFormat transferFormat, IConnection connection);
|
Task StartAsync(Uri url, TransferFormat transferFormat);
|
||||||
Task StopAsync();
|
Task StopAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
|
{
|
||||||
|
public static class ClientPipeOptions
|
||||||
|
{
|
||||||
|
public static PipeOptions DefaultOptions = new PipeOptions(writerScheduler: PipeScheduler.ThreadPool, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false, pauseWriterThreshold: 0, resumeWriterThreshold: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private IDuplexPipe _application;
|
private IDuplexPipe _application;
|
||||||
|
private IDuplexPipe _transport;
|
||||||
// Volatile so that the poll loop sees the updated value set from a different thread
|
// Volatile so that the poll loop sees the updated value set from a different thread
|
||||||
private volatile Exception _error;
|
private volatile Exception _error;
|
||||||
|
|
||||||
|
|
@ -27,6 +28,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
|
|
||||||
public Task Running { get; private set; } = Task.CompletedTask;
|
public Task Running { get; private set; } = Task.CompletedTask;
|
||||||
|
|
||||||
|
public PipeReader Input => _transport.Input;
|
||||||
|
|
||||||
|
public PipeWriter Output => _transport.Output;
|
||||||
|
|
||||||
public LongPollingTransport(HttpClient httpClient)
|
public LongPollingTransport(HttpClient httpClient)
|
||||||
: this(httpClient, null)
|
: this(httpClient, null)
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -37,19 +42,22 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<LongPollingTransport>();
|
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<LongPollingTransport>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(Uri url, IDuplexPipe application, TransferFormat transferFormat, IConnection connection)
|
public Task StartAsync(Uri url, TransferFormat transferFormat)
|
||||||
{
|
{
|
||||||
if (transferFormat != TransferFormat.Binary && transferFormat != TransferFormat.Text)
|
if (transferFormat != TransferFormat.Binary && transferFormat != TransferFormat.Text)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"The '{transferFormat}' transfer format is not supported by this transport.", nameof(transferFormat));
|
throw new ArgumentException($"The '{transferFormat}' transfer format is not supported by this transport.", nameof(transferFormat));
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.Features.Set<IConnectionInherentKeepAliveFeature>(new ConnectionInherentKeepAliveFeature(_httpClient.Timeout));
|
|
||||||
|
|
||||||
_application = application;
|
|
||||||
|
|
||||||
Log.StartTransport(_logger, transferFormat);
|
Log.StartTransport(_logger, transferFormat);
|
||||||
|
|
||||||
|
// Create the pipe pair (Application's writer is connected to Transport's reader, and vice versa)
|
||||||
|
var options = ClientPipeOptions.DefaultOptions;
|
||||||
|
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||||
|
|
||||||
|
_transport = pair.Transport;
|
||||||
|
_application = pair.Application;
|
||||||
|
|
||||||
Running = ProcessAsync(url);
|
Running = ProcessAsync(url);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
@ -89,6 +97,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
{
|
{
|
||||||
Log.TransportStopping(_logger);
|
Log.TransportStopping(_logger);
|
||||||
|
|
||||||
|
_transport.Output.Complete();
|
||||||
|
_transport.Input.Complete();
|
||||||
|
|
||||||
_application.Input.CancelPendingRead();
|
_application.Input.CancelPendingRead();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
private volatile Exception _error;
|
private volatile Exception _error;
|
||||||
private readonly CancellationTokenSource _transportCts = new CancellationTokenSource();
|
private readonly CancellationTokenSource _transportCts = new CancellationTokenSource();
|
||||||
private readonly ServerSentEventsMessageParser _parser = new ServerSentEventsMessageParser();
|
private readonly ServerSentEventsMessageParser _parser = new ServerSentEventsMessageParser();
|
||||||
|
private IDuplexPipe _transport;
|
||||||
private IDuplexPipe _application;
|
private IDuplexPipe _application;
|
||||||
|
|
||||||
public Task Running { get; private set; } = Task.CompletedTask;
|
public Task Running { get; private set; } = Task.CompletedTask;
|
||||||
|
|
||||||
|
public PipeReader Input => _transport.Input;
|
||||||
|
|
||||||
|
public PipeWriter Output => _transport.Output;
|
||||||
|
|
||||||
public ServerSentEventsTransport(HttpClient httpClient)
|
public ServerSentEventsTransport(HttpClient httpClient)
|
||||||
: this(httpClient, null)
|
: this(httpClient, null)
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -42,15 +46,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<ServerSentEventsTransport>();
|
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<ServerSentEventsTransport>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(Uri url, IDuplexPipe application, TransferFormat transferFormat, IConnection connection)
|
public async Task StartAsync(Uri url, TransferFormat transferFormat)
|
||||||
{
|
{
|
||||||
if (transferFormat != TransferFormat.Text)
|
if (transferFormat != TransferFormat.Text)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"The '{transferFormat}' transfer format is not supported by this transport.", nameof(transferFormat));
|
throw new ArgumentException($"The '{transferFormat}' transfer format is not supported by this transport.", nameof(transferFormat));
|
||||||
}
|
}
|
||||||
|
|
||||||
_application = application;
|
|
||||||
|
|
||||||
Log.StartTransport(_logger, transferFormat);
|
Log.StartTransport(_logger, transferFormat);
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
|
@ -72,6 +74,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the pipe pair (Application's writer is connected to Transport's reader, and vice versa)
|
||||||
|
var options = ClientPipeOptions.DefaultOptions;
|
||||||
|
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||||
|
|
||||||
|
_transport = pair.Transport;
|
||||||
|
_application = pair.Application;
|
||||||
|
|
||||||
Running = ProcessAsync(url, response);
|
Running = ProcessAsync(url, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,6 +203,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
{
|
{
|
||||||
Log.TransportStopping(_logger);
|
Log.TransportStopping(_logger);
|
||||||
|
|
||||||
|
_transport.Output.Complete();
|
||||||
|
_transport.Input.Complete();
|
||||||
|
|
||||||
_application.Input.CancelPendingRead();
|
_application.Input.CancelPendingRead();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,14 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
private readonly TimeSpan _closeTimeout;
|
private readonly TimeSpan _closeTimeout;
|
||||||
private volatile bool _aborted;
|
private volatile bool _aborted;
|
||||||
|
|
||||||
|
private IDuplexPipe _transport;
|
||||||
|
|
||||||
public Task Running { get; private set; } = Task.CompletedTask;
|
public Task Running { get; private set; } = Task.CompletedTask;
|
||||||
|
|
||||||
|
public PipeReader Input => _transport.Input;
|
||||||
|
|
||||||
|
public PipeWriter Output => _transport.Output;
|
||||||
|
|
||||||
public WebSocketsTransport()
|
public WebSocketsTransport()
|
||||||
: this(null, null)
|
: this(null, null)
|
||||||
{
|
{
|
||||||
|
|
@ -89,24 +95,18 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<WebSocketsTransport>();
|
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<WebSocketsTransport>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(Uri url, IDuplexPipe application, TransferFormat transferFormat, IConnection connection)
|
public async Task StartAsync(Uri url, TransferFormat transferFormat)
|
||||||
{
|
{
|
||||||
if (url == null)
|
if (url == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(url));
|
throw new ArgumentNullException(nameof(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (application == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(application));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transferFormat != TransferFormat.Binary && transferFormat != TransferFormat.Text)
|
if (transferFormat != TransferFormat.Binary && transferFormat != TransferFormat.Text)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"The '{transferFormat}' transfer format is not supported by this transport.", nameof(transferFormat));
|
throw new ArgumentException($"The '{transferFormat}' transfer format is not supported by this transport.", nameof(transferFormat));
|
||||||
}
|
}
|
||||||
|
|
||||||
_application = application;
|
|
||||||
_webSocketMessageType = transferFormat == TransferFormat.Binary
|
_webSocketMessageType = transferFormat == TransferFormat.Binary
|
||||||
? WebSocketMessageType.Binary
|
? WebSocketMessageType.Binary
|
||||||
: WebSocketMessageType.Text;
|
: WebSocketMessageType.Text;
|
||||||
|
|
@ -117,6 +117,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
|
|
||||||
await _webSocket.ConnectAsync(resolvedUrl, CancellationToken.None);
|
await _webSocket.ConnectAsync(resolvedUrl, CancellationToken.None);
|
||||||
|
|
||||||
|
// Create the pipe pair (Application's writer is connected to Transport's reader, and vice versa)
|
||||||
|
var options = ClientPipeOptions.DefaultOptions;
|
||||||
|
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||||
|
|
||||||
|
_transport = pair.Transport;
|
||||||
|
_application = pair.Application;
|
||||||
|
|
||||||
// TODO: Handle TCP connection errors
|
// TODO: Handle TCP connection errors
|
||||||
// https://github.com/SignalR/SignalR/blob/1fba14fa3437e24c204dfaf8a18db3fce8acad3c/src/Microsoft.AspNet.SignalR.Core/Owin/WebSockets/WebSocketHandler.cs#L248-L251
|
// https://github.com/SignalR/SignalR/blob/1fba14fa3437e24c204dfaf8a18db3fce8acad3c/src/Microsoft.AspNet.SignalR.Core/Owin/WebSockets/WebSocketHandler.cs#L248-L251
|
||||||
Running = ProcessSocketAsync(_webSocket);
|
Running = ProcessSocketAsync(_webSocket);
|
||||||
|
|
@ -359,6 +366,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
{
|
{
|
||||||
Log.TransportStopping(_logger);
|
Log.TransportStopping(_logger);
|
||||||
|
|
||||||
|
_transport.Output.Complete();
|
||||||
|
_transport.Input.Complete();
|
||||||
|
|
||||||
// Cancel any pending reads from the application, this should start the entire shutdown process
|
// Cancel any pending reads from the application, this should start the entire shutdown process
|
||||||
_application.Input.CancelPendingRead();
|
_application.Input.CancelPendingRead();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,28 +159,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PipesAreDisposedAfterTransportFailsToStart()
|
|
||||||
{
|
|
||||||
using (StartLog(out var loggerFactory))
|
|
||||||
{
|
|
||||||
var writerTcs = new TaskCompletionSource<object>();
|
|
||||||
var readerTcs = new TaskCompletionSource<object>();
|
|
||||||
await WithConnectionAsync(
|
|
||||||
CreateConnection(
|
|
||||||
loggerFactory: loggerFactory,
|
|
||||||
transport: new FakeTransport(writerTcs, readerTcs)),
|
|
||||||
async (connection) =>
|
|
||||||
{
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => connection.StartAsync(TransferFormat.Text));
|
|
||||||
Assert.Equal("Unable to connect to the server with any of the available transports.", ex.Message);
|
|
||||||
|
|
||||||
Assert.True(writerTcs.Task.IsCompleted);
|
|
||||||
Assert.True(readerTcs.Task.IsCompleted);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanDisposeUnstartedConnection()
|
public async Task CanDisposeUnstartedConnection()
|
||||||
{
|
{
|
||||||
|
|
@ -378,35 +356,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeTransport : ITransport
|
|
||||||
{
|
|
||||||
private IDuplexPipe _application;
|
|
||||||
private TaskCompletionSource<object> _writerTcs;
|
|
||||||
private TaskCompletionSource<object> _readerTcs;
|
|
||||||
|
|
||||||
public FakeTransport(TaskCompletionSource<object> writerTcs, TaskCompletionSource<object> readerTcs)
|
|
||||||
{
|
|
||||||
_writerTcs = writerTcs;
|
|
||||||
_readerTcs = readerTcs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(Uri url, IDuplexPipe application, TransferFormat transferFormat, IConnection connection)
|
|
||||||
{
|
|
||||||
_application = application;
|
|
||||||
Action<Exception, object> onCompletedCallback = (ex, tcs) => { ((TaskCompletionSource<object>)tcs).TrySetResult(null); };
|
|
||||||
_application.Input.OnWriterCompleted(onCompletedCallback, _writerTcs);
|
|
||||||
_application.Output.OnReaderCompleted(onCompletedCallback, _readerTcs);
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync()
|
|
||||||
{
|
|
||||||
_application.Output.Complete();
|
|
||||||
_application.Input.Complete();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task AssertDisposedAsync(HttpConnection connection)
|
private static async Task AssertDisposedAsync(HttpConnection connection)
|
||||||
{
|
{
|
||||||
var exception =
|
var exception =
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
|
||||||
|
|
||||||
transportActiveTask = longPollingTransport.Running;
|
transportActiveTask = longPollingTransport.Running;
|
||||||
|
|
||||||
|
|
@ -76,14 +75,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
|
||||||
|
|
||||||
await longPollingTransport.Running.OrTimeout();
|
await longPollingTransport.Running.OrTimeout();
|
||||||
|
|
||||||
Assert.True(pair.Transport.Input.TryRead(out var result));
|
Assert.True(longPollingTransport.Input.TryRead(out var result));
|
||||||
Assert.True(result.IsCompleted);
|
Assert.True(result.IsCompleted);
|
||||||
pair.Transport.Input.AdvanceTo(result.Buffer.End);
|
longPollingTransport.Input.AdvanceTo(result.Buffer.End);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -130,10 +128,9 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
|
||||||
|
|
||||||
var data = await pair.Transport.Input.ReadAllAsync().OrTimeout();
|
var data = await longPollingTransport.Input.ReadAllAsync().OrTimeout();
|
||||||
await longPollingTransport.Running.OrTimeout();
|
await longPollingTransport.Running.OrTimeout();
|
||||||
Assert.Equal(Encoding.UTF8.GetBytes("HelloWorld"), data);
|
Assert.Equal(Encoding.UTF8.GetBytes("HelloWorld"), data);
|
||||||
}
|
}
|
||||||
|
|
@ -161,15 +158,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
|
||||||
|
|
||||||
var exception =
|
var exception =
|
||||||
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||||
{
|
{
|
||||||
async Task ReadAsync()
|
async Task ReadAsync()
|
||||||
{
|
{
|
||||||
await pair.Transport.Input.ReadAsync();
|
await longPollingTransport.Input.ReadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
await ReadAsync().OrTimeout();
|
await ReadAsync().OrTimeout();
|
||||||
|
|
@ -203,14 +199,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
|
||||||
|
|
||||||
await pair.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello World"));
|
await longPollingTransport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello World"));
|
||||||
|
|
||||||
await longPollingTransport.Running.OrTimeout();
|
await longPollingTransport.Running.OrTimeout();
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<HttpRequestException>(async () => await pair.Transport.Input.ReadAllAsync().OrTimeout());
|
var exception = await Assert.ThrowsAsync<HttpRequestException>(async () => await longPollingTransport.Input.ReadAllAsync().OrTimeout());
|
||||||
Assert.Contains(" 500 ", exception.Message);
|
Assert.Contains(" 500 ", exception.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
@ -237,14 +232,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
|
||||||
|
|
||||||
pair.Transport.Output.Complete();
|
longPollingTransport.Output.Complete();
|
||||||
|
|
||||||
await longPollingTransport.Running.OrTimeout();
|
await longPollingTransport.Running.OrTimeout();
|
||||||
|
|
||||||
await pair.Transport.Input.ReadAllAsync().OrTimeout();
|
await longPollingTransport.Input.ReadAllAsync().OrTimeout();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -283,16 +277,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
|
|
||||||
// Start the transport
|
// Start the transport
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
|
|
||||||
// Wait for the transport to finish
|
// Wait for the transport to finish
|
||||||
await longPollingTransport.Running.OrTimeout();
|
await longPollingTransport.Running.OrTimeout();
|
||||||
|
|
||||||
// Pull Messages out of the channel
|
// Pull Messages out of the channel
|
||||||
var message = await pair.Transport.Input.ReadAllAsync();
|
var message = await longPollingTransport.Input.ReadAllAsync();
|
||||||
|
|
||||||
// Check the provided request
|
// Check the provided request
|
||||||
Assert.Equal(2, sentRequests.Count);
|
Assert.Equal(2, sentRequests.Count);
|
||||||
|
|
@ -331,19 +323,16 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
|
|
||||||
// Pre-queue some messages
|
|
||||||
await pair.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello"));
|
|
||||||
await pair.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("World"));
|
|
||||||
|
|
||||||
// Start the transport
|
// Start the transport
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
|
|
||||||
pair.Transport.Output.Complete();
|
await longPollingTransport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello"));
|
||||||
|
await longPollingTransport.Output.WriteAsync(Encoding.UTF8.GetBytes("World"));
|
||||||
|
|
||||||
|
longPollingTransport.Output.Complete();
|
||||||
|
|
||||||
await longPollingTransport.Running.OrTimeout();
|
await longPollingTransport.Running.OrTimeout();
|
||||||
await pair.Transport.Input.ReadAllAsync();
|
await longPollingTransport.Input.ReadAllAsync();
|
||||||
|
|
||||||
Assert.Single(sentRequests);
|
Assert.Single(sentRequests);
|
||||||
Assert.Equal(new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)'W', (byte)'o', (byte)'r', (byte)'l', (byte)'d'
|
Assert.Equal(new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)'W', (byte)'o', (byte)'r', (byte)'l', (byte)'d'
|
||||||
|
|
@ -376,9 +365,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), transferFormat);
|
||||||
|
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, transferFormat, connection: new TestConnection());
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -405,7 +392,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||||
longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), null, transferFormat, connection: new TestConnection()));
|
longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), transferFormat));
|
||||||
|
|
||||||
Assert.Contains($"The '{transferFormat}' transfer format is not supported by this transport.", exception.Message);
|
Assert.Contains($"The '{transferFormat}' transfer format is not supported by this transport.", exception.Message);
|
||||||
Assert.Equal("transferFormat", exception.ParamName);
|
Assert.Equal("transferFormat", exception.ParamName);
|
||||||
|
|
@ -440,8 +427,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary);
|
||||||
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: new TestConnection());
|
|
||||||
|
|
||||||
var completedTask = await Task.WhenAny(completionTcs.Task, longPollingTransport.Running).OrTimeout();
|
var completedTask = await Task.WhenAny(completionTcs.Task, longPollingTransport.Running).OrTimeout();
|
||||||
Assert.Equal(completionTcs.Task, completedTask);
|
Assert.Equal(completionTcs.Task, completedTask);
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
{
|
{
|
||||||
var sseTransport = new ServerSentEventsTransport(httpClient);
|
var sseTransport = new ServerSentEventsTransport(httpClient);
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
await sseTransport.StartAsync(
|
await sseTransport.StartAsync(
|
||||||
new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Text, connection: Mock.Of<IConnection>()).OrTimeout();
|
new Uri("http://fakeuri.org"), TransferFormat.Text).OrTimeout();
|
||||||
|
|
||||||
await eventStreamTcs.Task.OrTimeout();
|
await eventStreamTcs.Task.OrTimeout();
|
||||||
await sseTransport.StopAsync().OrTimeout();
|
await sseTransport.StopAsync().OrTimeout();
|
||||||
|
|
@ -68,7 +67,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SSETransportStopsSendAndReceiveLoopsWhenTransportStopped()
|
public async Task SSETransportStopsSendAndReceiveLoopsWhenTransportStopped()
|
||||||
{
|
{
|
||||||
var eventStreamCts = new CancellationTokenSource();
|
|
||||||
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
mockHttpHandler.Protected()
|
mockHttpHandler.Protected()
|
||||||
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
|
@ -79,11 +77,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
.Setup(s => s.CopyToAsync(It.IsAny<Stream>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
.Setup(s => s.CopyToAsync(It.IsAny<Stream>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns<Stream, int, CancellationToken>(async (stream, bufferSize, t) =>
|
.Returns<Stream, int, CancellationToken>(async (stream, bufferSize, t) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
|
||||||
var buffer = Encoding.ASCII.GetBytes("data: 3:abc\r\n\r\n");
|
var buffer = Encoding.ASCII.GetBytes("data: 3:abc\r\n\r\n");
|
||||||
while (!eventStreamCts.IsCancellationRequested)
|
while (!t.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await stream.WriteAsync(buffer, 0, buffer.Length).OrTimeout();
|
await stream.WriteAsync(buffer, 0, buffer.Length).OrTimeout();
|
||||||
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mockStream.Setup(s => s.CanRead).Returns(true);
|
mockStream.Setup(s => s.CanRead).Returns(true);
|
||||||
|
|
@ -99,14 +97,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
|
|
||||||
await sseTransport.StartAsync(
|
await sseTransport.StartAsync(
|
||||||
new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Text, connection: Mock.Of<IConnection>()).OrTimeout();
|
new Uri("http://fakeuri.org"), TransferFormat.Text).OrTimeout();
|
||||||
|
|
||||||
transportActiveTask = sseTransport.Running;
|
transportActiveTask = sseTransport.Running;
|
||||||
Assert.False(transportActiveTask.IsCompleted);
|
Assert.False(transportActiveTask.IsCompleted);
|
||||||
var message = await pair.Transport.Input.ReadSingleAsync().OrTimeout();
|
var message = await sseTransport.Input.ReadSingleAsync().OrTimeout();
|
||||||
Assert.StartsWith("3:abc", Encoding.ASCII.GetString(message));
|
Assert.StartsWith("3:abc", Encoding.ASCII.GetString(message));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
@ -115,7 +111,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
await transportActiveTask.OrTimeout();
|
await transportActiveTask.OrTimeout();
|
||||||
eventStreamCts.Cancel();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,11 +141,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var sseTransport = new ServerSentEventsTransport(httpClient);
|
var sseTransport = new ServerSentEventsTransport(httpClient);
|
||||||
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
await sseTransport.StartAsync(
|
await sseTransport.StartAsync(
|
||||||
new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Text, connection: Mock.Of<IConnection>()).OrTimeout();
|
new Uri("http://fakeuri.org"), TransferFormat.Text).OrTimeout();
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<FormatException>(() => pair.Transport.Input.ReadAllAsync());
|
var exception = await Assert.ThrowsAsync<FormatException>(() => sseTransport.Input.ReadAllAsync());
|
||||||
|
|
||||||
await sseTransport.Running.OrTimeout();
|
await sseTransport.Running.OrTimeout();
|
||||||
|
|
||||||
|
|
@ -192,15 +186,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var sseTransport = new ServerSentEventsTransport(httpClient);
|
var sseTransport = new ServerSentEventsTransport(httpClient);
|
||||||
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
|
|
||||||
await sseTransport.StartAsync(
|
await sseTransport.StartAsync(
|
||||||
new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Text, connection: Mock.Of<IConnection>()).OrTimeout();
|
new Uri("http://fakeuri.org"), TransferFormat.Text).OrTimeout();
|
||||||
await eventStreamTcs.Task;
|
await eventStreamTcs.Task;
|
||||||
|
|
||||||
await pair.Transport.Output.WriteAsync(new byte[] { 0x42 });
|
await sseTransport.Output.WriteAsync(new byte[] { 0x42 });
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => pair.Transport.Input.ReadAllAsync().OrTimeout());
|
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => sseTransport.Input.ReadAllAsync().OrTimeout());
|
||||||
Assert.Contains("500", exception.Message);
|
Assert.Contains("500", exception.Message);
|
||||||
|
|
||||||
// Errors are only communicated through the pipe
|
// Errors are only communicated through the pipe
|
||||||
|
|
@ -237,13 +229,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var sseTransport = new ServerSentEventsTransport(httpClient);
|
var sseTransport = new ServerSentEventsTransport(httpClient);
|
||||||
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
|
|
||||||
await sseTransport.StartAsync(
|
await sseTransport.StartAsync(
|
||||||
new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Text, connection: Mock.Of<IConnection>()).OrTimeout();
|
new Uri("http://fakeuri.org"), TransferFormat.Text).OrTimeout();
|
||||||
await eventStreamTcs.Task.OrTimeout();
|
await eventStreamTcs.Task.OrTimeout();
|
||||||
|
|
||||||
pair.Transport.Output.Complete();
|
sseTransport.Output.Complete();
|
||||||
|
|
||||||
await sseTransport.Running.OrTimeout();
|
await sseTransport.Running.OrTimeout();
|
||||||
}
|
}
|
||||||
|
|
@ -265,12 +255,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var sseTransport = new ServerSentEventsTransport(httpClient);
|
var sseTransport = new ServerSentEventsTransport(httpClient);
|
||||||
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
|
|
||||||
await sseTransport.StartAsync(
|
await sseTransport.StartAsync(
|
||||||
new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Text, connection: Mock.Of<IConnection>()).OrTimeout();
|
new Uri("http://fakeuri.org"), TransferFormat.Text).OrTimeout();
|
||||||
|
|
||||||
var message = await pair.Transport.Input.ReadSingleAsync().OrTimeout();
|
var message = await sseTransport.Input.ReadSingleAsync().OrTimeout();
|
||||||
Assert.Equal("3:abc", Encoding.ASCII.GetString(message));
|
Assert.Equal("3:abc", Encoding.ASCII.GetString(message));
|
||||||
|
|
||||||
await sseTransport.Running.OrTimeout();
|
await sseTransport.Running.OrTimeout();
|
||||||
|
|
@ -293,8 +281,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var sseTransport = new ServerSentEventsTransport(httpClient);
|
var sseTransport = new ServerSentEventsTransport(httpClient);
|
||||||
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
var ex = await Assert.ThrowsAsync<ArgumentException>(() => sseTransport.StartAsync(new Uri("http://fakeuri.org"), TransferFormat.Binary).OrTimeout());
|
||||||
var ex = await Assert.ThrowsAsync<ArgumentException>(() => sseTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, TransferFormat.Binary, connection: Mock.Of<IConnection>()).OrTimeout());
|
|
||||||
Assert.Equal($"The 'Binary' transfer format is not supported by this transport.{Environment.NewLine}Parameter name: transferFormat", ex.Message);
|
Assert.Equal($"The 'Binary' transfer format is not supported by this transport.{Environment.NewLine}Parameter name: transferFormat", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -318,7 +305,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var sseTransport = new ServerSentEventsTransport(httpClient);
|
var sseTransport = new ServerSentEventsTransport(httpClient);
|
||||||
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||||
sseTransport.StartAsync(new Uri("http://fakeuri.org"), null, transferFormat, connection: Mock.Of<IConnection>()));
|
sseTransport.StartAsync(new Uri("http://fakeuri.org"), transferFormat));
|
||||||
|
|
||||||
Assert.Contains($"The '{transferFormat}' transfer format is not supported by this transport.", exception.Message);
|
Assert.Contains($"The '{transferFormat}' transfer format is not supported by this transport.", exception.Message);
|
||||||
Assert.Equal("transferFormat", exception.ParamName);
|
Assert.Equal("transferFormat", exception.ParamName);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.IO.Pipelines;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Client;
|
using Microsoft.AspNetCore.Http.Connections.Client;
|
||||||
|
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
|
|
@ -15,6 +16,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
public IDuplexPipe Application { get; private set; }
|
public IDuplexPipe Application { get; private set; }
|
||||||
public Task Receiving { get; private set; }
|
public Task Receiving { get; private set; }
|
||||||
|
|
||||||
|
private IDuplexPipe _transport;
|
||||||
|
|
||||||
|
public PipeReader Input => _transport.Input;
|
||||||
|
|
||||||
|
public PipeWriter Output => _transport.Output;
|
||||||
|
|
||||||
public TestTransport(Func<Task> onTransportStop = null, Func<Task> onTransportStart = null, TransferFormat transferFormat = TransferFormat.Text)
|
public TestTransport(Func<Task> onTransportStop = null, Func<Task> onTransportStart = null, TransferFormat transferFormat = TransferFormat.Text)
|
||||||
{
|
{
|
||||||
_stopHandler = onTransportStop ?? new Func<Task>(() => Task.CompletedTask);
|
_stopHandler = onTransportStop ?? new Func<Task>(() => Task.CompletedTask);
|
||||||
|
|
@ -22,13 +29,18 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
Format = transferFormat;
|
Format = transferFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(Uri url, IDuplexPipe application, TransferFormat transferFormat, IConnection connection)
|
public async Task StartAsync(Uri url, TransferFormat transferFormat)
|
||||||
{
|
{
|
||||||
if ((Format & transferFormat) == 0)
|
if ((Format & transferFormat) == 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"The '{transferFormat}' transfer format is not supported by this transport.");
|
throw new InvalidOperationException($"The '{transferFormat}' transfer format is not supported by this transport.");
|
||||||
}
|
}
|
||||||
Application = application;
|
|
||||||
|
var options = ClientPipeOptions.DefaultOptions;
|
||||||
|
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||||
|
|
||||||
|
_transport = pair.Transport;
|
||||||
|
Application = pair.Application;
|
||||||
await _startHandler();
|
await _startHandler();
|
||||||
|
|
||||||
// Start a loop to read from the pipe
|
// Start a loop to read from the pipe
|
||||||
|
|
@ -65,6 +77,9 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
public Task StopAsync()
|
public Task StopAsync()
|
||||||
{
|
{
|
||||||
|
_transport.Output.Complete();
|
||||||
|
_transport.Input.Complete();
|
||||||
|
|
||||||
Application.Input.CancelPendingRead();
|
Application.Input.CancelPendingRead();
|
||||||
return Receiving;
|
return Receiving;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Http.Connections;
|
using Microsoft.AspNetCore.Http.Connections;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Client;
|
using Microsoft.AspNetCore.Http.Connections.Client;
|
||||||
|
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
|
@ -471,8 +472,13 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
private int _tries;
|
private int _tries;
|
||||||
private string _prevConnectionId = null;
|
private string _prevConnectionId = null;
|
||||||
private IDuplexPipe _application;
|
private IDuplexPipe _application;
|
||||||
|
private IDuplexPipe _transport;
|
||||||
private int availableTransports = 3;
|
private int availableTransports = 3;
|
||||||
|
|
||||||
|
public PipeReader Input => _transport.Input;
|
||||||
|
|
||||||
|
public PipeWriter Output => _transport.Output;
|
||||||
|
|
||||||
public FakeTransport()
|
public FakeTransport()
|
||||||
{
|
{
|
||||||
if (!TestHelpers.IsWebSocketsSupported())
|
if (!TestHelpers.IsWebSocketsSupported())
|
||||||
|
|
@ -481,9 +487,13 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(Uri url, IDuplexPipe application, TransferFormat transferFormat, IConnection connection)
|
public Task StartAsync(Uri url, TransferFormat transferFormat)
|
||||||
{
|
{
|
||||||
_application = application;
|
var options = ClientPipeOptions.DefaultOptions;
|
||||||
|
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||||
|
|
||||||
|
_transport = pair.Transport;
|
||||||
|
_application = pair.Application;
|
||||||
_tries++;
|
_tries++;
|
||||||
Assert.True(QueryHelpers.ParseQuery(url.Query).TryGetValue("id", out var id));
|
Assert.True(QueryHelpers.ParseQuery(url.Query).TryGetValue("id", out var id));
|
||||||
if (_prevConnectionId == null)
|
if (_prevConnectionId == null)
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,9 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
using (StartLog(out var loggerFactory))
|
using (StartLog(out var loggerFactory))
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
||||||
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), pair.Application,
|
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"),
|
||||||
TransferFormat.Binary, connection: Mock.Of<IConnection>()).OrTimeout();
|
TransferFormat.Binary).OrTimeout();
|
||||||
await webSocketsTransport.StopAsync().OrTimeout();
|
await webSocketsTransport.StopAsync().OrTimeout();
|
||||||
await webSocketsTransport.Running.OrTimeout();
|
await webSocketsTransport.Running.OrTimeout();
|
||||||
}
|
}
|
||||||
|
|
@ -84,17 +83,16 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
using (StartLog(out var loggerFactory))
|
using (StartLog(out var loggerFactory))
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
||||||
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/httpheader"), pair.Application,
|
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/httpheader"),
|
||||||
TransferFormat.Binary, connection: Mock.Of<IConnection>()).OrTimeout();
|
TransferFormat.Binary).OrTimeout();
|
||||||
|
|
||||||
await pair.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("User-Agent"));
|
await webSocketsTransport.Output.WriteAsync(Encoding.UTF8.GetBytes("User-Agent"));
|
||||||
|
|
||||||
// The HTTP header endpoint closes the connection immediately after sending response which should stop the transport
|
// The HTTP header endpoint closes the connection immediately after sending response which should stop the transport
|
||||||
await webSocketsTransport.Running.OrTimeout();
|
await webSocketsTransport.Running.OrTimeout();
|
||||||
|
|
||||||
Assert.True(pair.Transport.Input.TryRead(out var result));
|
Assert.True(webSocketsTransport.Input.TryRead(out var result));
|
||||||
|
|
||||||
var userAgent = Encoding.UTF8.GetString(result.Buffer.ToArray());
|
var userAgent = Encoding.UTF8.GetString(result.Buffer.ToArray());
|
||||||
|
|
||||||
|
|
@ -113,17 +111,16 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
using (StartLog(out var loggerFactory))
|
using (StartLog(out var loggerFactory))
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
||||||
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/httpheader"), pair.Application,
|
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/httpheader"),
|
||||||
TransferFormat.Binary, connection: Mock.Of<IConnection>()).OrTimeout();
|
TransferFormat.Binary).OrTimeout();
|
||||||
|
|
||||||
await pair.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("X-Requested-With"));
|
await webSocketsTransport.Output.WriteAsync(Encoding.UTF8.GetBytes("X-Requested-With"));
|
||||||
|
|
||||||
// The HTTP header endpoint closes the connection immediately after sending response which should stop the transport
|
// The HTTP header endpoint closes the connection immediately after sending response which should stop the transport
|
||||||
await webSocketsTransport.Running.OrTimeout();
|
await webSocketsTransport.Running.OrTimeout();
|
||||||
|
|
||||||
Assert.True(pair.Transport.Input.TryRead(out var result));
|
Assert.True(webSocketsTransport.Input.TryRead(out var result));
|
||||||
|
|
||||||
var headerValue = Encoding.UTF8.GetString(result.Buffer.ToArray());
|
var headerValue = Encoding.UTF8.GetString(result.Buffer.ToArray());
|
||||||
|
|
||||||
|
|
@ -137,11 +134,10 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
using (StartLog(out var loggerFactory))
|
using (StartLog(out var loggerFactory))
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
||||||
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), pair.Application,
|
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"),
|
||||||
TransferFormat.Binary, connection: Mock.Of<IConnection>());
|
TransferFormat.Binary);
|
||||||
pair.Transport.Output.Complete();
|
webSocketsTransport.Output.Complete();
|
||||||
await webSocketsTransport.Running.OrTimeout(TimeSpan.FromSeconds(10));
|
await webSocketsTransport.Running.OrTimeout(TimeSpan.FromSeconds(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,18 +150,17 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
using (StartLog(out var loggerFactory))
|
using (StartLog(out var loggerFactory))
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
||||||
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echoAndClose"), pair.Application, transferFormat, connection: Mock.Of<IConnection>());
|
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echoAndClose"), transferFormat);
|
||||||
|
|
||||||
await pair.Transport.Output.WriteAsync(new byte[] { 0x42 });
|
await webSocketsTransport.Output.WriteAsync(new byte[] { 0x42 });
|
||||||
|
|
||||||
// The echoAndClose endpoint closes the connection immediately after sending response which should stop the transport
|
// The echoAndClose endpoint closes the connection immediately after sending response which should stop the transport
|
||||||
await webSocketsTransport.Running.OrTimeout();
|
await webSocketsTransport.Running.OrTimeout();
|
||||||
|
|
||||||
Assert.True(pair.Transport.Input.TryRead(out var result));
|
Assert.True(webSocketsTransport.Input.TryRead(out var result));
|
||||||
Assert.Equal(new byte[] { 0x42 }, result.Buffer.ToArray());
|
Assert.Equal(new byte[] { 0x42 }, result.Buffer.ToArray());
|
||||||
pair.Transport.Input.AdvanceTo(result.Buffer.End);
|
webSocketsTransport.Input.AdvanceTo(result.Buffer.End);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,11 +172,10 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
using (StartLog(out var loggerFactory))
|
using (StartLog(out var loggerFactory))
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
||||||
|
|
||||||
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), pair.Application,
|
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"),
|
||||||
transferFormat, connection: Mock.Of<IConnection>()).OrTimeout();
|
transferFormat).OrTimeout();
|
||||||
|
|
||||||
await webSocketsTransport.StopAsync().OrTimeout();
|
await webSocketsTransport.StopAsync().OrTimeout();
|
||||||
await webSocketsTransport.Running.OrTimeout();
|
await webSocketsTransport.Running.OrTimeout();
|
||||||
|
|
@ -196,10 +190,9 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
using (StartLog(out var loggerFactory))
|
using (StartLog(out var loggerFactory))
|
||||||
{
|
{
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
|
||||||
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
|
||||||
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||||
webSocketsTransport.StartAsync(new Uri("http://fakeuri.org"), pair.Application, transferFormat, connection: Mock.Of<IConnection>()));
|
webSocketsTransport.StartAsync(new Uri("http://fakeuri.org"), transferFormat));
|
||||||
|
|
||||||
Assert.Contains($"The '{transferFormat}' transfer format is not supported by this transport.", exception.Message);
|
Assert.Contains($"The '{transferFormat}' transfer format is not supported by this transport.", exception.Message);
|
||||||
Assert.Equal("transferFormat", exception.ParamName);
|
Assert.Equal("transferFormat", exception.ParamName);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue