예제 #1
0
 public static void TransportDoesNotSupportTransferFormat(ILogger logger, HttpTransportType transport, TransferFormat transferFormat)
 {
     if (logger.IsEnabled(LogLevel.Debug))
     {
         _transportDoesNotSupportTransferFormat(logger, transport.ToString(), transferFormat.ToString(), null);
     }
 }
            public static void TransportDoesNotSupportTransferFormat(ILogger?logger, HttpTransportType transport,
                                                                     TransferFormat transferFormat)
            {
                if (logger is null)
                {
                    return;
                }

                if (!logger.IsEnabled(LogLevel.Debug))
                {
                    return;
                }

                TransportDoesNotSupportTransferFormatMessage(logger, transport.ToString(), transferFormat.ToString(),
                                                             null);
            }
예제 #3
0
        public async Task ConnectionCanSendAndReceiveMessages(HttpTransportType transportType, TransferFormat requestedTransferFormat)
        {
            using (StartVerifiableLog(out var loggerFactory, minLogLevel: LogLevel.Trace, testName: $"ConnectionCanSendAndReceiveMessages_{transportType.ToString()}_{requestedTransferFormat.ToString()}"))
            {
                var logger = loggerFactory.CreateLogger <EndToEndTests>();

                const string message = "Major Key";

                var url        = ServerFixture.Url + "/echo";
                var connection = new HttpConnection(new Uri(url), transportType, loggerFactory);
                try
                {
                    logger.LogInformation("Starting connection to {url}", url);
                    await connection.StartAsync(requestedTransferFormat).OrTimeout();

                    logger.LogInformation("Started connection to {url}", url);

                    var bytes = Encoding.UTF8.GetBytes(message);

                    logger.LogInformation("Sending {length} byte message", bytes.Length);
                    try
                    {
                        await connection.Transport.Output.WriteAsync(bytes).OrTimeout();
                    }
                    catch (OperationCanceledException)
                    {
                        // Because the server and client are run in the same process there is a race where websocket.SendAsync
                        // can send a message but before returning be suspended allowing the server to run the EchoConnectionHandler and
                        // send a close frame which triggers a cancellation token on the client and cancels the websocket.SendAsync.
                        // Our solution to this is to just catch OperationCanceledException from the sent message if the race happens
                        // because we know the send went through, and its safe to check the response.
                    }

                    logger.LogInformation("Sent message");

                    logger.LogInformation("Receiving message");
                    Assert.Equal(message, Encoding.UTF8.GetString(await connection.Transport.Input.ReadAsync(bytes.Length).OrTimeout()));
                    logger.LogInformation("Completed receive");
                }
                catch (Exception ex)
                {
                    logger.LogInformation(ex, "Test threw exception");
                    throw;
                }
                finally
                {
                    logger.LogInformation("Disposing Connection");
                    await connection.DisposeAsync().OrTimeout();

                    logger.LogInformation("Disposed Connection");
                }
            }
        }
예제 #4
0
        private async Task StartAsyncInternal(TransferFormat transferFormat)
        {
            Log.HttpConnectionStarting(_logger);

            try
            {
                var connectUrl = Url;
                if (_requestedTransportType == TransportType.WebSockets)
                {
                    Log.StartingTransport(_logger, _requestedTransportType, connectUrl);
                    await StartTransport(connectUrl, _requestedTransportType, transferFormat);
                }
                else
                {
                    var negotiationResponse = await GetNegotiationResponse();

                    // Connection is being disposed while start was in progress
                    if (_connectionState == ConnectionState.Disposed)
                    {
                        Log.HttpConnectionClosed(_logger);
                        return;
                    }

                    // This should only need to happen once
                    connectUrl = CreateConnectUrl(Url, negotiationResponse.ConnectionId);

                    // We're going to search for the transfer format as a string because we don't want to parse
                    // all the transfer formats in the negotiation response, and we want to allow transfer formats
                    // we don't understand in the negotiate response.
                    var transferFormatString = transferFormat.ToString();

                    foreach (var transport in negotiationResponse.AvailableTransports)
                    {
                        if (!Enum.TryParse <TransportType>(transport.Transport, out var transportType))
                        {
                            Log.TransportNotSupported(_logger, transport.Transport);
                            continue;
                        }

                        try
                        {
                            if ((transportType & _requestedTransportType) == 0)
                            {
                                Log.TransportDisabledByClient(_logger, transportType);
                            }
                            else if (!transport.TransferFormats.Contains(transferFormatString, StringComparer.Ordinal))
                            {
                                Log.TransportDoesNotSupportTransferFormat(_logger, transportType, transferFormat);
                            }
                            else
                            {
                                // The negotiation response gets cleared in the fallback scenario.
                                if (negotiationResponse == null)
                                {
                                    negotiationResponse = await GetNegotiationResponse();

                                    connectUrl = CreateConnectUrl(Url, negotiationResponse.ConnectionId);
                                }

                                Log.StartingTransport(_logger, transportType, connectUrl);
                                await StartTransport(connectUrl, transportType, transferFormat);

                                break;
                            }
                        }
                        catch (Exception ex)
                        {
                            Log.TransportFailed(_logger, transportType, ex);
                            // Try the next transport
                            // Clear the negotiation response so we know to re-negotiate.
                            negotiationResponse = null;
                        }
                    }
                }

                if (_transport == null)
                {
                    throw new InvalidOperationException("Unable to connect to the server with any of the available transports.");
                }
            }

            catch
            {
                // The connection can now be either in the Connecting or Disposed state - only change the state to
                // Disconnected if the connection was in the Connecting state to not resurrect a Disposed connection
                ChangeState(from: ConnectionState.Connecting, to: ConnectionState.Disconnected);
                throw;
            }

            // if the connection is not in the Connecting state here it means the user called DisposeAsync while
            // the connection was starting
            if (ChangeState(from: ConnectionState.Connecting, to: ConnectionState.Connected) == ConnectionState.Connecting)
            {
                _closeTcs = new TaskCompletionSource <object>();

                Input.OnWriterCompleted(async(exception, state) =>
                {
                    // Grab the exception and then clear it.
                    // See comment at AbortAsync for more discussion on the thread-safety
                    // StartAsync can't be called until the ChangeState below, so we're OK.
                    var abortException = _abortException;
                    _abortException    = null;

                    // There is an inherent race between receive and close. Removing the last message from the channel
                    // makes Input.Completion task completed and runs this continuation. We need to await _receiveLoopTask
                    // to make sure that the message removed from the channel is processed before we drain the queue.
                    // There is a short window between we start the channel and assign the _receiveLoopTask a value.
                    // To make sure that _receiveLoopTask can be awaited (i.e. is not null) we need to await _startTask.
                    Log.ProcessRemainingMessages(_logger);

                    await _startTcs.Task;
                    await _receiveLoopTask;

                    Log.DrainEvents(_logger);

                    await Task.WhenAny(_eventQueue.Drain().NoThrow(), Task.Delay(_eventQueueDrainTimeout));

                    Log.CompleteClosed(_logger);
                    _logScope.ConnectionId = null;

                    // At this point the connection can be either in the Connected or Disposed state. The state should be changed
                    // to the Disconnected state only if it was in the Connected state.
                    // From this point on, StartAsync can be called at any time.
                    ChangeState(from: ConnectionState.Connected, to: ConnectionState.Disconnected);

                    _closeTcs.SetResult(null);

                    try
                    {
                        if (exception != null)
                        {
                            Closed?.Invoke(exception);
                        }
                        else
                        {
                            // Call the closed event. If there was an abort exception, it will be flowed forward
                            // However, if there wasn't, this will just be null and we're good
                            Closed?.Invoke(abortException);
                        }
                    }
                    catch (Exception ex)
                    {
                        // Suppress (but log) the exception, this is user code
                        Log.ErrorDuringClosedEvent(_logger, ex);
                    }
                }, null);

                _receiveLoopTask = ReceiveAsync();
            }
        }
예제 #5
0
        private async Task SelectAndStartTransport(TransferFormat transferFormat)
        {
            if (_requestedTransportType == TransportType.WebSockets)
            {
                Log.StartingTransport(_logger, _requestedTransportType, Url);
                await StartTransport(Url, _requestedTransportType, transferFormat);
            }
            else
            {
                var negotiationResponse = await GetNegotiationResponse();

                // This should only need to happen once
                var connectUrl = CreateConnectUrl(Url, negotiationResponse.ConnectionId);

                // We're going to search for the transfer format as a string because we don't want to parse
                // all the transfer formats in the negotiation response, and we want to allow transfer formats
                // we don't understand in the negotiate response.
                var transferFormatString = transferFormat.ToString();

                foreach (var transport in negotiationResponse.AvailableTransports)
                {
                    if (!Enum.TryParse <TransportType>(transport.Transport, out var transportType))
                    {
                        Log.TransportNotSupported(_logger, transport.Transport);
                        continue;
                    }

                    if (transportType == TransportType.WebSockets && !IsWebSocketsSupported())
                    {
                        Log.WebSocketsNotSupportedByOperatingSystem(_logger);
                        continue;
                    }

                    try
                    {
                        if ((transportType & _requestedTransportType) == 0)
                        {
                            Log.TransportDisabledByClient(_logger, transportType);
                        }
                        else if (!transport.TransferFormats.Contains(transferFormatString, StringComparer.Ordinal))
                        {
                            Log.TransportDoesNotSupportTransferFormat(_logger, transportType, transferFormat);
                        }
                        else
                        {
                            // The negotiation response gets cleared in the fallback scenario.
                            if (negotiationResponse == null)
                            {
                                negotiationResponse = await GetNegotiationResponse();

                                connectUrl = CreateConnectUrl(Url, negotiationResponse.ConnectionId);
                            }

                            Log.StartingTransport(_logger, transportType, connectUrl);
                            await StartTransport(connectUrl, transportType, transferFormat);

                            break;
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.TransportFailed(_logger, transportType, ex);
                        // Try the next transport
                        // Clear the negotiation response so we know to re-negotiate.
                        negotiationResponse = null;
                    }
                }
            }

            if (_transport == null)
            {
                throw new InvalidOperationException("Unable to connect to the server with any of the available transports.");
            }
        }