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); }
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"); } } }
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(); } }
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."); } }