예제 #1
0
        private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connectionDelegate, HttpConnectionDispatcherOptions options, ConnectionLogScope logScope)
        {
            // set a tag to allow Application Performance Management tools to differentiate long running requests for reporting purposes
            context.Features.Get <IHttpActivityFeature>()?.Activity.AddTag("http.long_running", "true");

            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 ServerSentEventsServerTransport(connection.Application.Input, connection.ConnectionId, connection, _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 canceled
                connection.Cancellation = new CancellationTokenSource();

                var ws = new WebSocketsServerTransport(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;
                }

                if (!await connection.CancelPreviousPoll(context))
                {
                    // Connection closed. 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(TaskCreationOptions.RunContinuationsAsynchronously);

                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
                        // Ignore exceptions, it has been logged if there is one and the application has finished
                        // So there is no one to give the exception to
                        await connection.TransportTask !.NoThrow();

                        // 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 || resultTask.IsCanceled)
                    {
                        // 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();
                    }
                }
                finally
                {
                    // Artificial task queue
                    // This will cause incoming polls to wait until the previous poll has finished updating internal state info
                    currentRequestTcs.TrySetResult();
                }
            }
        }