Пример #1
0
        public async Task WebSocketsTransportStopsWhenConnectionClosedByTheServer(TransferFormat transferFormat)
        {
            using (StartVerifableLog(out var loggerFactory))
            {
                var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: loggerFactory);
                await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echoAndClose"), transferFormat);

                await webSocketsTransport.Output.WriteAsync(new byte[] { 0x42 });

                // The echoAndClose endpoint closes the connection immediately after sending response which should stop the transport
                await webSocketsTransport.Running.OrTimeout();

                Assert.True(webSocketsTransport.Input.TryRead(out var result));
                Assert.Equal(new byte[] { 0x42 }, result.Buffer.ToArray());
                webSocketsTransport.Input.AdvanceTo(result.Buffer.End);
            }
        }
Пример #2
0
        public async Task WebSocketsTransportStopsWhenConnectionClosedByTheServer(TransferFormat transferFormat)
        {
            await using (var server = await StartServer <Startup>())
            {
                var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null);
                await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echoAndClose"), transferFormat);

                await webSocketsTransport.Output.WriteAsync(new byte[] { 0x42 });

                // The echoAndClose endpoint closes the connection immediately after sending response which should stop the transport
                await webSocketsTransport.Running.DefaultTimeout();

                Assert.True(webSocketsTransport.Input.TryRead(out var result));
                Assert.Equal(new byte[] { 0x42 }, result.Buffer.ToArray());
                webSocketsTransport.Input.AdvanceTo(result.Buffer.End);
            }
        }
Пример #3
0
        public async Task ReceivedFramesAreWrittenToChannel(WebSocketMessageType webSocketMessageType)
        {
            using (StartLog(out var loggerFactory))
            {
                var transportToApplication = Channel.CreateUnbounded <byte[]>();
                var applicationToTransport = Channel.CreateUnbounded <byte[]>();

                using (var transportSide = ChannelConnection.Create <byte[]>(applicationToTransport, transportToApplication))
                    using (var applicationSide = ChannelConnection.Create <byte[]>(transportToApplication, applicationToTransport))
                        using (var feature = new TestWebSocketConnectionFeature())
                        {
                            var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                            var ws = new WebSocketsTransport(new WebSocketOptions(), transportSide, connectionContext, loggerFactory);

                            // Give the server socket to the transport and run it
                            var transport = ws.ProcessSocketAsync(await feature.AcceptAsync());

                            // Run the client socket
                            var client = feature.Client.ExecuteAndCaptureFramesAsync();

                            // Send a frame, then close
                            await feature.Client.SendAsync(
                                buffer : new ArraySegment <byte>(Encoding.UTF8.GetBytes("Hello")),
                                messageType : webSocketMessageType,
                                endOfMessage : true,
                                cancellationToken : CancellationToken.None);

                            await feature.Client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);

                            var buffer = await applicationSide.Reader.ReadAsync();

                            Assert.Equal("Hello", Encoding.UTF8.GetString(buffer));

                            Assert.True(applicationSide.Writer.TryComplete());

                            // The transport should finish now
                            await transport;

                            // The connection should close after this, which means the client will get a close frame.
                            var clientSummary = await client;

                            Assert.Equal(WebSocketCloseStatus.NormalClosure, clientSummary.CloseResult.CloseStatus);
                        }
            }
        }
Пример #4
0
        public async Task ReceivedFramesAreWrittenToChannel(WebSocketMessageType webSocketMessageType)
        {
            using (StartLog(out var loggerFactory, LogLevel.Debug))
            {
                var pair       = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
                var connection = new DefaultConnectionContext("foo", pair.Transport, pair.Application);

                using (var feature = new TestWebSocketConnectionFeature())
                {
                    var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                    var ws = new WebSocketsTransport(new WebSocketOptions(), connection.Application, connectionContext, loggerFactory);

                    // Give the server socket to the transport and run it
                    var transport = ws.ProcessSocketAsync(await feature.AcceptAsync());

                    // Run the client socket
                    var client = feature.Client.ExecuteAndCaptureFramesAsync();

                    // Send a frame, then close
                    await feature.Client.SendAsync(
                        buffer : new ArraySegment <byte>(Encoding.UTF8.GetBytes("Hello")),
                        messageType : webSocketMessageType,
                        endOfMessage : true,
                        cancellationToken : CancellationToken.None);

                    await feature.Client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);

                    var result = await connection.Transport.Input.ReadAsync();

                    var buffer = result.Buffer;
                    Assert.Equal("Hello", Encoding.UTF8.GetString(buffer.ToArray()));
                    connection.Transport.Input.AdvanceTo(buffer.End);

                    connection.Transport.Output.Complete();

                    // The transport should finish now
                    await transport;

                    // The connection should close after this, which means the client will get a close frame.
                    var clientSummary = await client;

                    Assert.Equal(WebSocketCloseStatus.NormalClosure, clientSummary.CloseResult.CloseStatus);
                }
            }
        }
Пример #5
0
        public async Task TransportFailsWhenClientDisconnectsAbnormally()
        {
            using (StartLog(out var loggerFactory))
            {
                var transportToApplication = Channel.CreateUnbounded <byte[]>();
                var applicationToTransport = Channel.CreateUnbounded <byte[]>();

                using (var transportSide = ChannelConnection.Create <byte[]>(applicationToTransport, transportToApplication))
                    using (var applicationSide = ChannelConnection.Create <byte[]>(transportToApplication, applicationToTransport))
                        using (var feature = new TestWebSocketConnectionFeature())
                        {
                            async Task CompleteApplicationAfterTransportCompletes()
                            {
                                // Wait until the transport completes so that we can end the application
                                await applicationSide.Reader.WaitToReadAsync();

                                // Complete the application so that the connection unwinds without aborting
                                applicationSide.Writer.TryComplete();
                            }

                            var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                            var ws = new WebSocketsTransport(new WebSocketOptions(), transportSide, connectionContext, loggerFactory);

                            // Give the server socket to the transport and run it
                            var transport = ws.ProcessSocketAsync(await feature.AcceptAsync());

                            // Run the client socket
                            var client = feature.Client.ExecuteAndCaptureFramesAsync();

                            // When the close frame is received, we complete the application so the send
                            // loop unwinds
                            _ = CompleteApplicationAfterTransportCompletes();

                            // Terminate the client to server channel with an exception
                            feature.Client.SendAbort();

                            // Wait for the transport
                            await Assert.ThrowsAsync <WebSocketException>(() => transport).OrTimeout();

                            var summary = await client.OrTimeout();

                            Assert.Equal(WebSocketCloseStatus.InternalServerError, summary.CloseResult.CloseStatus);
                        }
            }
        }
Пример #6
0
        public async Task WebSocketsTransportStopsWhenConnectionClosedByTheServer(TransferFormat transferFormat)
        {
            using (StartLog(out var loggerFactory))
            {
                var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
                var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
                await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echoAndClose"), pair.Application, transferFormat, connection : Mock.Of <IConnection>());

                await pair.Transport.Output.WriteAsync(new byte[] { 0x42 });

                // The echoAndClose endpoint closes the connection immediately after sending response which should stop the transport
                await webSocketsTransport.Running.OrTimeout();

                Assert.True(pair.Transport.Input.TryRead(out var result));
                Assert.Equal(new byte[] { 0x42 }, result.Buffer.ToArray());
                pair.Transport.Input.AdvanceTo(result.Buffer.End);
            }
        }
Пример #7
0
        public async Task WebSocketsTransportSetsTransferMode(TransferMode transferMode)
        {
            using (StartLog(out var loggerFactory))
            {
                var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
                var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);

                Assert.Null(webSocketsTransport.Mode);
                await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), pair.Application,
                                                     transferMode, connection : Mock.Of <IConnection>()).OrTimeout();

                Assert.Equal(transferMode, webSocketsTransport.Mode);

                await webSocketsTransport.StopAsync().OrTimeout();

                await webSocketsTransport.Running.OrTimeout();
            }
        }
Пример #8
0
        public async Task TransportFailsWhenClientDisconnectsAbnormally()
        {
            using (StartLog(out var loggerFactory, LogLevel.Debug))
            {
                var pair       = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
                var connection = new DefaultConnectionContext("foo", pair.Transport, pair.Application);

                using (var feature = new TestWebSocketConnectionFeature())
                {
                    async Task CompleteApplicationAfterTransportCompletes()
                    {
                        // Wait until the transport completes so that we can end the application
                        var result = await connection.Transport.Input.ReadAsync();

                        connection.Transport.Input.AdvanceTo(result.Buffer.End);

                        // Complete the application so that the connection unwinds without aborting
                        connection.Transport.Output.Complete();
                    }

                    var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                    var ws = new WebSocketsTransport(new WebSocketOptions(), connection.Application, connectionContext, loggerFactory);

                    // Give the server socket to the transport and run it
                    var transport = ws.ProcessSocketAsync(await feature.AcceptAsync());

                    // Run the client socket
                    var client = feature.Client.ExecuteAndCaptureFramesAsync();

                    // When the close frame is received, we complete the application so the send
                    // loop unwinds
                    _ = CompleteApplicationAfterTransportCompletes();

                    // Terminate the client to server channel with an exception
                    feature.Client.SendAbort();

                    // Wait for the transport
                    await Assert.ThrowsAsync <WebSocketException>(() => transport).OrTimeout();

                    await client.OrTimeout();
                }
            }
        }
Пример #9
0
        public async Task WebSocketsTransportSendsXRequestedWithHeader()
        {
            using (StartVerifiableLog(out var loggerFactory))
            {
                var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: loggerFactory, accessTokenProvider: null);
                await webSocketsTransport.StartAsync(new Uri(ServerFixture.WebSocketsUrl + "/httpheader"),
                                                     TransferFormat.Binary).OrTimeout();

                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
                await webSocketsTransport.Running.OrTimeout();

                Assert.True(webSocketsTransport.Input.TryRead(out var result));

                var headerValue = Encoding.UTF8.GetString(result.Buffer.ToArray());

                Assert.Equal("XMLHttpRequest", headerValue);
            }
        }
Пример #10
0
        public async Task WebSocketTransportSetsMessageTypeBasedOnTransferModeFeature(TransferMode transferMode, WebSocketMessageType expectedMessageType)
        {
            using (StartLog(out var loggerFactory))
            {
                var transportToApplication = Channel.CreateUnbounded <byte[]>();
                var applicationToTransport = Channel.CreateUnbounded <byte[]>();

                using (var transportSide = ChannelConnection.Create <byte[]>(applicationToTransport, transportToApplication))
                    using (var applicationSide = ChannelConnection.Create <byte[]>(transportToApplication, applicationToTransport))
                        using (var feature = new TestWebSocketConnectionFeature())
                        {
                            var connectionContext = new DefaultConnectionContext(string.Empty, null, null)
                            {
                                TransferMode = transferMode
                            };
                            var ws = new WebSocketsTransport(new WebSocketOptions(), transportSide, connectionContext, loggerFactory);

                            // Give the server socket to the transport and run it
                            var transport = ws.ProcessSocketAsync(await feature.AcceptAsync());

                            // Run the client socket
                            var client = feature.Client.ExecuteAndCaptureFramesAsync();

                            // Write to the output channel, and then complete it
                            await applicationSide.Writer.WriteAsync(Encoding.UTF8.GetBytes("Hello"));

                            Assert.True(applicationSide.Writer.TryComplete());

                            // The client should finish now, as should the server
                            var clientSummary = await client;
                            await feature.Client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);

                            await transport;

                            Assert.Equal(1, clientSummary.Received.Count);
                            Assert.True(clientSummary.Received[0].EndOfMessage);
                            Assert.Equal(expectedMessageType, clientSummary.Received[0].MessageType);
                            Assert.Equal("Hello", Encoding.UTF8.GetString(clientSummary.Received[0].Buffer));
                        }
            }
        }
Пример #11
0
        public async Task ServerGracefullyClosesWhenApplicationEndsThenClientSendsCloseFrame()
        {
            using (StartLog(out var loggerFactory))
            {
                var transportToApplication = Channel.CreateUnbounded <byte[]>();
                var applicationToTransport = Channel.CreateUnbounded <byte[]>();

                using (var transportSide = ChannelConnection.Create <byte[]>(applicationToTransport, transportToApplication))
                    using (var applicationSide = ChannelConnection.Create <byte[]>(transportToApplication, applicationToTransport))
                        using (var feature = new TestWebSocketConnectionFeature())
                        {
                            var options = new WebSocketOptions
                            {
                                // We want to verify behavior without timeout affecting it
                                CloseTimeout = TimeSpan.FromSeconds(20)
                            };

                            var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                            var ws = new WebSocketsTransport(options, transportSide, connectionContext, loggerFactory);

                            var serverSocket = await feature.AcceptAsync();

                            // Give the server socket to the transport and run it
                            var transport = ws.ProcessSocketAsync(serverSocket);

                            // Run the client socket
                            var client = feature.Client.ExecuteAndCaptureFramesAsync();

                            // close the client to server channel
                            applicationToTransport.Writer.TryComplete();

                            _ = await client.OrTimeout();

                            await feature.Client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None).OrTimeout();

                            await transport.OrTimeout();

                            Assert.Equal(WebSocketCloseStatus.NormalClosure, serverSocket.CloseStatus);
                        }
            }
        }
Пример #12
0
        public async Task WebSocketsTransportSetsTransferMode(TransferMode transferMode)
        {
            using (StartLog(out var loggerFactory))
            {
                var connectionToTransport = Channel.CreateUnbounded <SendMessage>();
                var transportToConnection = Channel.CreateUnbounded <byte[]>();
                var channelConnection     = new ChannelConnection <SendMessage, byte[]>(connectionToTransport, transportToConnection);

                var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);

                Assert.Null(webSocketsTransport.Mode);
                await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection,
                                                     transferMode, connectionId : string.Empty, connection : Mock.Of <IConnection>()).OrTimeout();

                Assert.Equal(transferMode, webSocketsTransport.Mode);

                await webSocketsTransport.StopAsync().OrTimeout();

                await webSocketsTransport.Running.OrTimeout();
            }
        }
Пример #13
0
        public async Task WebSocketsTransportSendsXRequestedWithHeader()
        {
            using (StartLog(out var loggerFactory))
            {
                var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
                var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
                await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/httpheader"), pair.Application,
                                                     TransferFormat.Binary, connection : Mock.Of <IConnection>()).OrTimeout();

                await pair.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("X-Requested-With"));

                // The HTTP header endpoint closes the connection immediately after sending response which should stop the transport
                await webSocketsTransport.Running.OrTimeout();

                Assert.True(pair.Transport.Input.TryRead(out var result));

                var headerValue = Encoding.UTF8.GetString(result.Buffer.ToArray());

                Assert.Equal("XMLHttpRequest", headerValue);
            }
        }
Пример #14
0
        private static async Task UpgradeToWebSockets(RavenDBOptions options, IOwinContext context, Func <Task> next)
        {
            var accept = context.Get <Action <IDictionary <string, object>, Func <IDictionary <string, object>, Task> > >("websocket.Accept");

            if (accept == null)
            {
                // Not a websocket request
                await next();

                return;
            }

            WebSocketsTransport webSocketsTrasport = WebSocketTransportFactory.CreateWebSocketTransport(options, context);

            if (webSocketsTrasport != null)
            {
                if (await webSocketsTrasport.TrySetupRequest())
                {
                    accept(null, webSocketsTrasport.Run);
                }
            }
        }
Пример #15
0
        public async Task ServerGracefullyClosesWhenApplicationEndsThenClientSendsCloseFrame()
        {
            using (StartLog(out var loggerFactory, LogLevel.Debug))
            {
                var pair       = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
                var connection = new DefaultConnectionContext("foo", pair.Transport, pair.Application);

                using (var feature = new TestWebSocketConnectionFeature())
                {
                    var options = new WebSocketOptions
                    {
                        // We want to verify behavior without timeout affecting it
                        CloseTimeout = TimeSpan.FromSeconds(20)
                    };

                    var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                    var ws = new WebSocketsTransport(options, connection.Application, connectionContext, loggerFactory);

                    var serverSocket = await feature.AcceptAsync();

                    // Give the server socket to the transport and run it
                    var transport = ws.ProcessSocketAsync(serverSocket);

                    // Run the client socket
                    var client = feature.Client.ExecuteAndCaptureFramesAsync();

                    // close the client to server channel
                    connection.Transport.Output.Complete();

                    _ = await client.OrTimeout();

                    await feature.Client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None).OrTimeout();

                    await transport.OrTimeout();

                    Assert.Equal(WebSocketCloseStatus.NormalClosure, serverSocket.CloseStatus);
                }
            }
        }
Пример #16
0
        public async Task WebSocketTransportSetsMessageTypeBasedOnTransferFormatFeature(TransferFormat transferFormat, WebSocketMessageType expectedMessageType)
        {
            using (StartLog(out var loggerFactory, LogLevel.Debug))
            {
                var pair       = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
                var connection = new DefaultConnectionContext("foo", pair.Transport, pair.Application);

                using (var feature = new TestWebSocketConnectionFeature())
                {
                    var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                    connectionContext.ActiveFormat = transferFormat;
                    var ws = new WebSocketsTransport(new WebSocketOptions(), connection.Application, connectionContext, loggerFactory);

                    // Give the server socket to the transport and run it
                    var transport = ws.ProcessSocketAsync(await feature.AcceptAsync());

                    // Run the client socket
                    var client = feature.Client.ExecuteAndCaptureFramesAsync();

                    // Write to the output channel, and then complete it
                    await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello"));

                    connection.Transport.Output.Complete();

                    // The client should finish now, as should the server
                    var clientSummary = await client;
                    await feature.Client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);

                    await transport;

                    Assert.Equal(1, clientSummary.Received.Count);
                    Assert.True(clientSummary.Received[0].EndOfMessage);
                    Assert.Equal(expectedMessageType, clientSummary.Received[0].MessageType);
                    Assert.Equal("Hello", Encoding.UTF8.GetString(clientSummary.Received[0].Buffer));
                }
            }
        }
Пример #17
0
        public async Task TransportClosesOnCloseTimeoutIfClientDoesNotSendCloseFrame()
        {
            using (StartLog(out var loggerFactory))
            {
                var transportToApplication = Channel.CreateUnbounded <byte[]>();
                var applicationToTransport = Channel.CreateUnbounded <byte[]>();

                using (var transportSide = ChannelConnection.Create <byte[]>(applicationToTransport, transportToApplication))
                    using (var applicationSide = ChannelConnection.Create <byte[]>(transportToApplication, applicationToTransport))
                        using (var feature = new TestWebSocketConnectionFeature())
                        {
                            var options = new WebSocketOptions()
                            {
                                CloseTimeout = TimeSpan.FromSeconds(1)
                            };

                            var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                            var ws = new WebSocketsTransport(options, transportSide, connectionContext, loggerFactory);

                            var serverSocket = await feature.AcceptAsync();

                            // Give the server socket to the transport and run it
                            var transport = ws.ProcessSocketAsync(serverSocket);

                            // End the app
                            applicationSide.Dispose();

                            await transport.OrTimeout(TimeSpan.FromSeconds(10));

                            // Now we're closed
                            Assert.Equal(WebSocketState.Aborted, serverSocket.State);

                            serverSocket.Dispose();
                        }
            }
        }
Пример #18
0
        public async Task TransportFailsOnTimeoutWithErrorWhenApplicationFailsAndClientDoesNotSendCloseFrame()
        {
            using (StartLog(out var loggerFactory))
            {
                var transportToApplication = Channel.CreateUnbounded <byte[]>();
                var applicationToTransport = Channel.CreateUnbounded <byte[]>();

                using (var transportSide = ChannelConnection.Create <byte[]>(applicationToTransport, transportToApplication))
                    using (var applicationSide = ChannelConnection.Create <byte[]>(transportToApplication, applicationToTransport))
                        using (var feature = new TestWebSocketConnectionFeature())
                        {
                            var options = new WebSocketOptions
                            {
                                CloseTimeout = TimeSpan.FromSeconds(1)
                            };

                            var connectionContext = new DefaultConnectionContext(string.Empty, null, null);
                            var ws = new WebSocketsTransport(options, transportSide, connectionContext, loggerFactory);

                            var serverSocket = await feature.AcceptAsync();

                            // Give the server socket to the transport and run it
                            var transport = ws.ProcessSocketAsync(serverSocket);

                            // Run the client socket
                            var client = feature.Client.ExecuteAndCaptureFramesAsync();

                            // fail the client to server channel
                            applicationToTransport.Writer.TryComplete(new Exception());

                            await Assert.ThrowsAsync <Exception>(() => transport).OrTimeout();

                            Assert.Equal(WebSocketState.Aborted, serverSocket.State);
                        }
            }
        }
Пример #19
0
        private async Task ExecuteEndpointAsync(HttpContext context, ConnectionDelegate ConnectionDelegate, HttpSocketOptions options, ConnectionLogScope logScope)
        {
            var supportedTransports = options.Transports;

            // Server sent events transport
            // GET /{path}
            // Accept: text/event-stream
            var headers = context.Request.GetTypedHeaders();

            if (headers.Accept?.Contains(new Net.Http.Headers.MediaTypeHeaderValue("text/event-stream")) == true)
            {
                // Connection must already exist
                var connection = await GetConnectionAsync(context);

                if (connection == null)
                {
                    // No such connection, GetConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, TransportType.ServerSentEvents, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                Log.EstablishedConnection(_logger);

                // ServerSentEvents is a text protocol only
                connection.SupportedFormats = TransferFormat.Text;

                // We only need to provide the Input channel since writing to the application is handled through /send.
                var sse = new ServerSentEventsTransport(connection.Application.Input, connection.ConnectionId, _loggerFactory);

                await DoPersistentConnection(ConnectionDelegate, sse, context, connection);
            }
            else if (context.WebSockets.IsWebSocketRequest)
            {
                // Connection can be established lazily
                var connection = await GetOrCreateConnectionAsync(context, options);

                if (connection == null)
                {
                    // No such connection, GetOrCreateConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, TransportType.WebSockets, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                Log.EstablishedConnection(_logger);

                var ws = new WebSocketsTransport(options.WebSockets, connection.Application, connection, _loggerFactory);

                await DoPersistentConnection(ConnectionDelegate, ws, context, connection);
            }
            else
            {
                // GET /{path} maps to long polling

                // Connection must already exist
                var connection = await GetConnectionAsync(context);

                if (connection == null)
                {
                    // No such connection, GetConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, TransportType.LongPolling, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                try
                {
                    await connection.Lock.WaitAsync();

                    if (connection.Status == DefaultConnectionContext.ConnectionStatus.Disposed)
                    {
                        Log.ConnectionDisposed(_logger, connection.ConnectionId);

                        // The connection was disposed
                        context.Response.StatusCode  = StatusCodes.Status404NotFound;
                        context.Response.ContentType = "text/plain";
                        return;
                    }

                    if (connection.Status == DefaultConnectionContext.ConnectionStatus.Active)
                    {
                        var existing = connection.GetHttpContext();
                        Log.ConnectionAlreadyActive(_logger, connection.ConnectionId, existing.TraceIdentifier);

                        using (connection.Cancellation)
                        {
                            // Cancel the previous request
                            connection.Cancellation.Cancel();

                            // Wait for the previous request to drain
                            await connection.TransportTask;

                            Log.PollCanceled(_logger, connection.ConnectionId, existing.TraceIdentifier);
                        }
                    }

                    // Mark the connection as active
                    connection.Status = DefaultConnectionContext.ConnectionStatus.Active;

                    // Raise OnConnected for new connections only since polls happen all the time
                    if (connection.ApplicationTask == null)
                    {
                        Log.EstablishedConnection(_logger);

                        connection.Items[ConnectionMetadataNames.Transport] = TransportType.LongPolling;

                        connection.ApplicationTask = ExecuteApplication(ConnectionDelegate, connection);
                    }
                    else
                    {
                        Log.ResumingConnection(_logger);
                    }

                    // REVIEW: Performance of this isn't great as this does a bunch of per request allocations
                    connection.Cancellation = new CancellationTokenSource();

                    var timeoutSource = new CancellationTokenSource();
                    var tokenSource   = CancellationTokenSource.CreateLinkedTokenSource(connection.Cancellation.Token, context.RequestAborted, timeoutSource.Token);

                    // Dispose these tokens when the request is over
                    context.Response.RegisterForDispose(timeoutSource);
                    context.Response.RegisterForDispose(tokenSource);

                    var longPolling = new LongPollingTransport(timeoutSource.Token, connection.Application.Input, connection.ConnectionId, _loggerFactory);

                    // Start the transport
                    connection.TransportTask = longPolling.ProcessRequestAsync(context, tokenSource.Token);

                    // Start the timeout after we return from creating the transport task
                    timeoutSource.CancelAfter(options.LongPolling.PollTimeout);
                }
                finally
                {
                    connection.Lock.Release();
                }

                var resultTask = await Task.WhenAny(connection.ApplicationTask, connection.TransportTask);

                var pollAgain = true;

                // If the application ended before the transport task then we potentially need to end the connection
                if (resultTask == connection.ApplicationTask)
                {
                    // Complete the transport (notifying it of the application error if there is one)
                    connection.Transport.Output.Complete(connection.ApplicationTask.Exception);

                    // Wait for the transport to run
                    await connection.TransportTask;

                    // If the status code is a 204 it means the connection is done
                    if (context.Response.StatusCode == StatusCodes.Status204NoContent)
                    {
                        // We should be able to safely dispose because there's no more data being written
                        await _manager.DisposeAndRemoveAsync(connection);

                        // Don't poll again if we've removed the connection completely
                        pollAgain = false;
                    }
                }
                else if (context.Response.StatusCode == StatusCodes.Status204NoContent)
                {
                    // Don't poll if the transport task was cancelled
                    pollAgain = false;
                }

                if (pollAgain)
                {
                    // Otherwise, we update the state to inactive again and wait for the next poll
                    try
                    {
                        await connection.Lock.WaitAsync();

                        if (connection.Status == DefaultConnectionContext.ConnectionStatus.Active)
                        {
                            // Mark the connection as inactive
                            connection.LastSeenUtc = DateTime.UtcNow;

                            connection.Status = DefaultConnectionContext.ConnectionStatus.Inactive;

                            connection.SetHttpContext(null);

                            // Dispose the cancellation token
                            connection.Cancellation.Dispose();

                            connection.Cancellation = null;
                        }
                    }
                    finally
                    {
                        connection.Lock.Release();
                    }
                }
            }
        }
        private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connectionDelegate, HttpConnectionDispatcherOptions options, ConnectionLogScope logScope)
        {
            var supportedTransports = options.Transports;

            // Server sent events transport
            // GET /{path}
            // Accept: text/event-stream
            var headers = context.Request.GetTypedHeaders();

            if (headers.Accept?.Contains(new Net.Http.Headers.MediaTypeHeaderValue("text/event-stream")) == true)
            {
                // Connection must already exist
                var connection = await GetConnectionAsync(context);

                if (connection == null)
                {
                    // No such connection, GetConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, HttpTransportType.ServerSentEvents, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                Log.EstablishedConnection(_logger);

                // ServerSentEvents is a text protocol only
                connection.SupportedFormats = TransferFormat.Text;

                // We only need to provide the Input channel since writing to the application is handled through /send.
                var sse = new ServerSentEventsTransport(connection.Application.Input, connection.ConnectionId, _loggerFactory);

                await DoPersistentConnection(connectionDelegate, sse, context, connection);
            }
            else if (context.WebSockets.IsWebSocketRequest)
            {
                // Connection can be established lazily
                var connection = await GetOrCreateConnectionAsync(context, options);

                if (connection == null)
                {
                    // No such connection, GetOrCreateConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, HttpTransportType.WebSockets, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                Log.EstablishedConnection(_logger);

                // Allow the reads to be cancelled
                connection.Cancellation = new CancellationTokenSource();

                var ws = new WebSocketsTransport(options.WebSockets, connection.Application, connection, _loggerFactory);

                await DoPersistentConnection(connectionDelegate, ws, context, connection);
            }
            else
            {
                // GET /{path} maps to long polling

                // Connection must already exist
                var connection = await GetConnectionAsync(context);

                if (connection == null)
                {
                    // No such connection, GetConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, HttpTransportType.LongPolling, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                // Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
                var currentRequestTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);

                using (connection.Cancellation)
                {
                    // Cancel the previous request
                    connection.Cancellation?.Cancel();

                    try
                    {
                        // Wait for the previous request to drain
                        await connection.PreviousPollTask;
                    }
                    catch (OperationCanceledException)
                    {
                        // Previous poll canceled due to connection closing, close this poll too
                        context.Response.ContentType = "text/plain";
                        context.Response.StatusCode  = StatusCodes.Status204NoContent;
                        return;
                    }
                }

                if (!connection.TryActivateLongPollingConnection(
                        connectionDelegate, context, options.LongPolling.PollTimeout,
                        currentRequestTcs.Task, _loggerFactory, _logger))
                {
                    return;
                }

                var resultTask = await Task.WhenAny(connection.ApplicationTask, connection.TransportTask);

                try
                {
                    // If the application ended before the transport task then we potentially need to end the connection
                    if (resultTask == connection.ApplicationTask)
                    {
                        // Complete the transport (notifying it of the application error if there is one)
                        connection.Transport.Output.Complete(connection.ApplicationTask.Exception);

                        // Wait for the transport to run
                        await connection.TransportTask;

                        // If the status code is a 204 it means the connection is done
                        if (context.Response.StatusCode == StatusCodes.Status204NoContent)
                        {
                            // Cancel current request to release any waiting poll and let dispose acquire the lock
                            currentRequestTcs.TrySetCanceled();

                            // We should be able to safely dispose because there's no more data being written
                            // We don't need to wait for close here since we've already waited for both sides
                            await _manager.DisposeAndRemoveAsync(connection, closeGracefully : false);
                        }
                        else
                        {
                            // Only allow repoll if we aren't removing the connection.
                            connection.MarkInactive();
                        }
                    }
                    else if (resultTask.IsFaulted)
                    {
                        // Cancel current request to release any waiting poll and let dispose acquire the lock
                        currentRequestTcs.TrySetCanceled();

                        // transport task was faulted, we should remove the connection
                        await _manager.DisposeAndRemoveAsync(connection, closeGracefully : false);
                    }
                    else
                    {
                        // Only allow repoll if we aren't removing the connection.
                        connection.MarkInactive();
                    }
                }
                finally
                {
                    // Artificial task queue
                    // This will cause incoming polls to wait until the previous poll has finished updating internal state info
                    currentRequestTcs.TrySetResult(null);
                }
            }
        }
 public WebSocketConnectionContext(WebSocketConnectionOptions httpConnectionOptions, ILoggerFactory loggerFactory, Func <Task <string> > accessTokenProvider)
 {
     Transport    = _websocketTransport = new WebSocketsTransport(httpConnectionOptions, loggerFactory, accessTokenProvider);
     ConnectionId = "sc_" + Guid.NewGuid();
 }
Пример #22
0
        private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connectionDelegate, HttpConnectionDispatcherOptions options, ConnectionLogScope logScope)
        {
            var supportedTransports = options.Transports;

            // Server sent events transport
            // GET /{path}
            // Accept: text/event-stream
            var headers = context.Request.GetTypedHeaders();

            if (headers.Accept?.Contains(new Net.Http.Headers.MediaTypeHeaderValue("text/event-stream")) == true)
            {
                // Connection must already exist
                var connection = await GetConnectionAsync(context);

                if (connection == null)
                {
                    // No such connection, GetConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, HttpTransportType.ServerSentEvents, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                Log.EstablishedConnection(_logger);

                // ServerSentEvents is a text protocol only
                connection.SupportedFormats = TransferFormat.Text;

                // We only need to provide the Input channel since writing to the application is handled through /send.
                var sse = new ServerSentEventsTransport(connection.Application.Input, connection.ConnectionId, _loggerFactory);

                await DoPersistentConnection(connectionDelegate, sse, context, connection);
            }
            else if (context.WebSockets.IsWebSocketRequest)
            {
                // Connection can be established lazily
                var connection = await GetOrCreateConnectionAsync(context, options);

                if (connection == null)
                {
                    // No such connection, GetOrCreateConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, HttpTransportType.WebSockets, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                Log.EstablishedConnection(_logger);

                // Allow the reads to be cancelled
                connection.Cancellation = new CancellationTokenSource();

                var ws = new WebSocketsTransport(options.WebSockets, connection.Application, connection, _loggerFactory);

                await DoPersistentConnection(connectionDelegate, ws, context, connection);
            }
            else
            {
                // GET /{path} maps to long polling

                // Connection must already exist
                var connection = await GetConnectionAsync(context);

                if (connection == null)
                {
                    // No such connection, GetConnection already set the response status code
                    return;
                }

                if (!await EnsureConnectionStateAsync(connection, context, HttpTransportType.LongPolling, supportedTransports, logScope, options))
                {
                    // Bad connection state. It's already set the response status code.
                    return;
                }

                // Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
                var currentRequestTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);

                await connection.StateLock.WaitAsync();

                try
                {
                    if (connection.Status == HttpConnectionStatus.Disposed)
                    {
                        Log.ConnectionDisposed(_logger, connection.ConnectionId);

                        // The connection was disposed
                        context.Response.StatusCode  = StatusCodes.Status404NotFound;
                        context.Response.ContentType = "text/plain";
                        return;
                    }

                    if (connection.Status == HttpConnectionStatus.Active)
                    {
                        var existing = connection.GetHttpContext();
                        Log.ConnectionAlreadyActive(_logger, connection.ConnectionId, existing.TraceIdentifier);
                    }

                    using (connection.Cancellation)
                    {
                        // Cancel the previous request
                        connection.Cancellation?.Cancel();

                        try
                        {
                            // Wait for the previous request to drain
                            await connection.PreviousPollTask;
                        }
                        catch (OperationCanceledException)
                        {
                            // Previous poll canceled due to connection closing, close this poll too
                            context.Response.ContentType = "text/plain";
                            context.Response.StatusCode  = StatusCodes.Status204NoContent;
                            return;
                        }

                        connection.PreviousPollTask = currentRequestTcs.Task;
                    }

                    // Mark the connection as active
                    connection.Status = HttpConnectionStatus.Active;

                    // Raise OnConnected for new connections only since polls happen all the time
                    if (connection.ApplicationTask == null)
                    {
                        Log.EstablishedConnection(_logger);

                        connection.ApplicationTask = ExecuteApplication(connectionDelegate, connection);

                        context.Response.ContentType = "application/octet-stream";

                        // This request has no content
                        context.Response.ContentLength = 0;

                        // On the first poll, we flush the response immediately to mark the poll as "initialized" so future
                        // requests can be made safely
                        connection.TransportTask = context.Response.Body.FlushAsync();
                    }
                    else
                    {
                        Log.ResumingConnection(_logger);

                        // REVIEW: Performance of this isn't great as this does a bunch of per request allocations
                        connection.Cancellation = new CancellationTokenSource();

                        var timeoutSource = new CancellationTokenSource();
                        var tokenSource   = CancellationTokenSource.CreateLinkedTokenSource(connection.Cancellation.Token, context.RequestAborted, timeoutSource.Token);

                        // Dispose these tokens when the request is over
                        context.Response.RegisterForDispose(timeoutSource);
                        context.Response.RegisterForDispose(tokenSource);

                        var longPolling = new LongPollingTransport(timeoutSource.Token, connection.Application.Input, _loggerFactory);

                        // Start the transport
                        connection.TransportTask = longPolling.ProcessRequestAsync(context, tokenSource.Token);

                        // Start the timeout after we return from creating the transport task
                        timeoutSource.CancelAfter(options.LongPolling.PollTimeout);
                    }
                }
                finally
                {
                    connection.StateLock.Release();
                }

                var resultTask = await Task.WhenAny(connection.ApplicationTask, connection.TransportTask);

                try
                {
                    var pollAgain = true;

                    // If the application ended before the transport task then we potentially need to end the connection
                    if (resultTask == connection.ApplicationTask)
                    {
                        // Complete the transport (notifying it of the application error if there is one)
                        connection.Transport.Output.Complete(connection.ApplicationTask.Exception);

                        // Wait for the transport to run
                        await connection.TransportTask;

                        // If the status code is a 204 it means the connection is done
                        if (context.Response.StatusCode == StatusCodes.Status204NoContent)
                        {
                            // Cancel current request to release any waiting poll and let dispose acquire the lock
                            currentRequestTcs.TrySetCanceled();

                            // We should be able to safely dispose because there's no more data being written
                            // We don't need to wait for close here since we've already waited for both sides
                            await _manager.DisposeAndRemoveAsync(connection, closeGracefully : false);

                            // Don't poll again if we've removed the connection completely
                            pollAgain = false;
                        }
                    }
                    else if (resultTask.IsFaulted)
                    {
                        // Cancel current request to release any waiting poll and let dispose acquire the lock
                        currentRequestTcs.TrySetCanceled();

                        // transport task was faulted, we should remove the connection
                        await _manager.DisposeAndRemoveAsync(connection, closeGracefully : false);

                        pollAgain = false;
                    }
                    else if (context.Response.StatusCode == StatusCodes.Status204NoContent)
                    {
                        // Don't poll if the transport task was canceled
                        pollAgain = false;
                    }

                    if (pollAgain)
                    {
                        // Mark the connection as inactive
                        connection.LastSeenUtc = DateTime.UtcNow;

                        connection.Status = HttpConnectionStatus.Inactive;
                    }
                }
                finally
                {
                    // Artificial task queue
                    // This will cause incoming polls to wait until the previous poll has finished updating internal state info
                    currentRequestTcs.TrySetResult(null);
                }
            }
        }