private async Task<NegotiationResponse> Negotiate(Uri url, HttpClient httpClient, ILogger logger) { try { // Get a connection ID from the server logger.EstablishingConnection(url); var urlBuilder = new UriBuilder(url); if (!urlBuilder.Path.EndsWith("/")) { urlBuilder.Path += "/"; } urlBuilder.Path += "negotiate"; using (var request = new HttpRequestMessage(HttpMethod.Post, urlBuilder.Uri)) { SendUtils.PrepareHttpRequest(request, _httpOptions); using (var response = await httpClient.SendAsync(request)) { response.EnsureSuccessStatusCode(); return await ParseNegotiateResponse(response, logger); } } } catch (Exception ex) { logger.ErrorWithNegotiation(url, ex); throw; } }
public Task StartAsync(Uri url, IDuplexPipe application, TransferFormat transferFormat, IConnection connection) { if (transferFormat != TransferFormat.Text) { throw new ArgumentException($"The '{transferFormat}' transfer format is not supported by this transport.", nameof(transferFormat)); } _application = application; Log.StartTransport(_logger, transferFormat); var startTcs = new TaskCompletionSource <object>(TaskContinuationOptions.RunContinuationsAsynchronously); var sendTask = SendUtils.SendMessages(url, _application, _httpClient, _httpOptions, _transportCts, _logger); var receiveTask = OpenConnection(_application, url, startTcs, _transportCts.Token); Running = Task.WhenAll(sendTask, receiveTask).ContinueWith(t => { Log.TransportStopped(_logger, t.Exception?.InnerException); _application.Output.Complete(t.Exception?.InnerException); _application.Input.Complete(); return(t); }).Unwrap(); return(startTcs.Task); }
public Task StartAsync(Uri url, IDuplexPipe application, TransferMode requestedTransferMode, IConnection connection) { if (requestedTransferMode != TransferMode.Binary && requestedTransferMode != TransferMode.Text) { throw new ArgumentException("Invalid transfer mode.", nameof(requestedTransferMode)); } connection.Features.Set <IConnectionInherentKeepAliveFeature>(new ConnectionInherentKeepAliveFeature(_httpClient.Timeout)); _application = application; Mode = requestedTransferMode; Log.StartTransport(_logger, Mode.Value); // Start sending and polling (ask for binary if the server supports it) _poller = Poll(url, _transportCts.Token); _sender = SendUtils.SendMessages(url, _application, _httpClient, _httpOptions, _transportCts, _logger); Running = Task.WhenAll(_sender, _poller).ContinueWith(t => { Log.TransportStopped(_logger, t.Exception?.InnerException); _application.Output.Complete(t.Exception?.InnerException); _application.Input.Complete(); return(t); }).Unwrap(); return(Task.CompletedTask); }
public Task StartAsync(Uri url, Channel <byte[], SendMessage> application, TransferMode requestedTransferMode, string connectionId) { if (requestedTransferMode != TransferMode.Binary && requestedTransferMode != TransferMode.Text) { throw new ArgumentException("Invalid transfer mode.", nameof(requestedTransferMode)); } _application = application; Mode = requestedTransferMode; _connectionId = connectionId; _logger.StartTransport(_connectionId, Mode.Value); // Start sending and polling (ask for binary if the server supports it) _poller = Poll(url, _transportCts.Token); _sender = SendUtils.SendMessages(url, _application, _httpClient, _transportCts, _logger, _connectionId); Running = Task.WhenAll(_sender, _poller).ContinueWith(t => { _logger.TransportStopped(_connectionId, t.Exception?.InnerException); _application.Out.TryComplete(t.IsFaulted ? t.Exception.InnerException : null); return(t); }).Unwrap(); return(Task.CompletedTask); }
private async Task <NegotiationResponse> Negotiate(Uri url, HttpClient httpClient, ILogger logger) { try { // Get a connection ID from the server Log.EstablishingConnection(logger, url); var urlBuilder = new UriBuilder(url); if (!urlBuilder.Path.EndsWith("/")) { urlBuilder.Path += "/"; } urlBuilder.Path += "negotiate"; using (var request = new HttpRequestMessage(HttpMethod.Post, urlBuilder.Uri)) { // Corefx changed the default version and High Sierra curlhandler tries to upgrade request request.Version = new Version(1, 1); SendUtils.PrepareHttpRequest(request, _httpOptions); using (var response = await httpClient.SendAsync(request)) { response.EnsureSuccessStatusCode(); return(await ParseNegotiateResponse(response, logger)); } } } catch (Exception ex) { Log.ErrorWithNegotiation(logger, url, ex); throw; } }
public Task StartAsync(Uri url, Channel <byte[], SendMessage> application, TransferMode requestedTransferMode, IConnection connection) { if (requestedTransferMode != TransferMode.Binary && requestedTransferMode != TransferMode.Text) { throw new ArgumentException("Invalid transfer mode.", nameof(requestedTransferMode)); } _application = application; Mode = TransferMode.Text; // Server Sent Events is a text only transport _logger.StartTransport(Mode.Value); var sendTask = SendUtils.SendMessages(url, _application, _httpClient, _httpOptions, _transportCts, _logger); var receiveTask = OpenConnection(_application, url, _transportCts.Token); Running = Task.WhenAll(sendTask, receiveTask).ContinueWith(t => { _logger.TransportStopped(t.Exception?.InnerException); _application.Writer.TryComplete(t.IsFaulted ? t.Exception.InnerException : null); return(t); }).Unwrap(); return(Task.CompletedTask); }
private async Task OpenConnection(IDuplexPipe application, Uri url, TaskCompletionSource <object> startTcs, CancellationToken cancellationToken) { Log.StartReceive(_logger); var request = new HttpRequestMessage(HttpMethod.Get, url); SendUtils.PrepareHttpRequest(request, _httpOptions); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); HttpResponseMessage response; try { response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); startTcs.TrySetResult(null); } catch (Exception ex) { Log.TransportStopping(_logger); startTcs.TrySetException(ex); return; } using (var stream = await response.Content.ReadAsStreamAsync()) { var pipeOptions = new PipeOptions(pauseWriterThreshold: 0, resumeWriterThreshold: 0); var pipelineReader = StreamPipeConnection.CreateReader(pipeOptions, stream); var readCancellationRegistration = cancellationToken.Register( reader => ((PipeReader)reader).CancelPendingRead(), pipelineReader); try { while (true) { var result = await pipelineReader.ReadAsync(); var input = result.Buffer; if (result.IsCanceled || (input.IsEmpty && result.IsCompleted)) { Log.EventStreamEnded(_logger); break; } var consumed = input.Start; var examined = input.End; try { Log.ParsingSSE(_logger, input.Length); var parseResult = _parser.ParseMessage(input, out consumed, out examined, out var buffer); switch (parseResult) { case ServerSentEventsMessageParser.ParseResult.Completed: Log.MessageToApp(_logger, buffer.Length); await _application.Output.WriteAsync(buffer); _parser.Reset(); break; case ServerSentEventsMessageParser.ParseResult.Incomplete: if (result.IsCompleted) { throw new FormatException("Incomplete message."); } break; } } finally { pipelineReader.AdvanceTo(consumed, examined); } } } catch (OperationCanceledException) { Log.ReceiveCanceled(_logger); } finally { readCancellationRegistration.Dispose(); _transportCts.Cancel(); Log.ReceiveStopped(_logger); } } }
private async Task Poll(Uri pollUrl, CancellationToken cancellationToken) { Log.StartReceive(_logger); try { while (!cancellationToken.IsCancellationRequested) { var request = new HttpRequestMessage(HttpMethod.Get, pollUrl); SendUtils.PrepareHttpRequest(request, _httpOptions); HttpResponseMessage response; try { response = await _httpClient.SendAsync(request, cancellationToken); } catch (OperationCanceledException) { // SendAsync will throw the OperationCanceledException if the passed cancellationToken is canceled // or if the http request times out due to HttpClient.Timeout expiring. In the latter case we // just want to start a new poll. continue; } response.EnsureSuccessStatusCode(); if (response.StatusCode == HttpStatusCode.NoContent || cancellationToken.IsCancellationRequested) { Log.ClosingConnection(_logger); // Transport closed or polling stopped, we're done break; } else { Log.ReceivedMessages(_logger); var stream = new PipeWriterStream(_application.Output); await response.Content.CopyToAsync(stream); await _application.Output.FlushAsync(); } } } catch (OperationCanceledException) { // transport is being closed Log.ReceiveCanceled(_logger); } catch (Exception ex) { Log.ErrorPolling(_logger, pollUrl, ex); throw; } finally { // Make sure the send loop is terminated _transportCts.Cancel(); Log.ReceiveStopped(_logger); } }
private async Task OpenConnection(Channel <byte[], SendMessage> application, Uri url, CancellationToken cancellationToken) { _logger.StartReceive(); var request = new HttpRequestMessage(HttpMethod.Get, url); SendUtils.PrepareHttpRequest(request, _httpOptions); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); var stream = await response.Content.ReadAsStreamAsync(); var pipelineReader = StreamPipeConnection.CreateReader(new PipeOptions(_memoryPool), stream); var readCancellationRegistration = cancellationToken.Register( reader => ((IPipeReader)reader).CancelPendingRead(), pipelineReader); try { while (true) { var result = await pipelineReader.ReadAsync(); var input = result.Buffer; if (result.IsCancelled || (input.IsEmpty && result.IsCompleted)) { _logger.EventStreamEnded(); break; } var consumed = input.Start; var examined = input.End; try { var parseResult = _parser.ParseMessage(input, out consumed, out examined, out var buffer); switch (parseResult) { case ServerSentEventsMessageParser.ParseResult.Completed: _application.Writer.TryWrite(buffer); _parser.Reset(); break; case ServerSentEventsMessageParser.ParseResult.Incomplete: if (result.IsCompleted) { throw new FormatException("Incomplete message."); } break; } } finally { pipelineReader.Advance(consumed, examined); } } } catch (OperationCanceledException) { _logger.ReceiveCanceled(); } finally { readCancellationRegistration.Dispose(); _transportCts.Cancel(); stream.Dispose(); _logger.ReceiveStopped(); } }
private async Task Poll(Uri pollUrl, CancellationToken cancellationToken) { _logger.StartReceive(_connectionId); try { while (!cancellationToken.IsCancellationRequested) { var request = new HttpRequestMessage(HttpMethod.Get, pollUrl); SendUtils.PrepareHttpRequest(request, _httpOptions); HttpResponseMessage response; try { response = await _httpClient.SendAsync(request, cancellationToken); } catch (OperationCanceledException) { // SendAsync will throw the OperationCanceledException if the passed cancellationToken is canceled // or if the http request times out due to HttpClient.Timeout expiring. In the latter case we // just want to start a new poll. continue; } response.EnsureSuccessStatusCode(); if (response.StatusCode == HttpStatusCode.NoContent || cancellationToken.IsCancellationRequested) { _logger.ClosingConnection(_connectionId); // Transport closed or polling stopped, we're done break; } else { _logger.ReceivedMessages(_connectionId); // Until Pipeline starts natively supporting BytesReader, this is the easiest way to do this. var payload = await response.Content.ReadAsByteArrayAsync(); if (payload.Length > 0) { while (!_application.Writer.TryWrite(payload)) { if (cancellationToken.IsCancellationRequested || !await _application.Writer.WaitToWriteAsync(cancellationToken)) { return; } } } } } } catch (OperationCanceledException) { // transport is being closed _logger.ReceiveCanceled(_connectionId); } catch (Exception ex) { _logger.ErrorPolling(_connectionId, pollUrl, ex); throw; } finally { // Make sure the send loop is terminated _transportCts.Cancel(); _logger.ReceiveStopped(_connectionId); } }