// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Buffers; using System.IO; using System.IO.Pipelines; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { internal static class SendUtils { public static async Task SendMessages(Uri sendUrl, IDuplexPipe application, HttpClient httpClient, ILogger logger) { Log.SendStarted(logger); try { while (true) { var result = await application.Input.ReadAsync(); var buffer = result.Buffer; try { if (result.IsCanceled) { Log.SendCanceled(logger); break; } if (!buffer.IsEmpty) { Log.SendingMessages(logger, buffer.Length, sendUrl); // Send them in a single post var request = new HttpRequestMessage(HttpMethod.Post, sendUrl); // Corefx changed the default version and High Sierra curlhandler tries to upgrade request request.Version = new Version(1, 1); request.Content = new ReadOnlySequenceContent(buffer); // ResponseHeadersRead instructs SendAsync to return once headers are read // rather than buffer the entire response. This gives a small perf boost. // Note that it is important to dispose of the response when doing this to // avoid leaving the connection open. using (var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); } Log.SentSuccessfully(logger); } else if (result.IsCompleted) { break; } else { Log.NoMessages(logger); } } finally { application.Input.AdvanceTo(buffer.End); } } } catch (OperationCanceledException) { Log.SendCanceled(logger); } catch (Exception ex) { Log.ErrorSending(logger, sendUrl, ex); throw; } finally { application.Input.Complete(); } Log.SendStopped(logger); } private class ReadOnlySequenceContent : HttpContent { private readonly ReadOnlySequence _buffer; public ReadOnlySequenceContent(in ReadOnlySequence buffer) { _buffer = buffer; } protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) { return stream.WriteAsync(_buffer).AsTask(); } protected override bool TryComputeLength(out long length) { length = _buffer.Length; return true; } } private static class Log { private static readonly Action _sendStarted = LoggerMessage.Define(LogLevel.Debug, new EventId(100, "SendStarted"), "Starting the send loop."); private static readonly Action _sendStopped = LoggerMessage.Define(LogLevel.Debug, new EventId(101, "SendStopped"), "Send loop stopped."); private static readonly Action _sendCanceled = LoggerMessage.Define(LogLevel.Debug, new EventId(102, "SendCanceled"), "Send loop canceled."); private static readonly Action _sendingMessages = LoggerMessage.Define(LogLevel.Debug, new EventId(103, "SendingMessages"), "Sending {Count} bytes to the server using url: {Url}."); private static readonly Action _sentSuccessfully = LoggerMessage.Define(LogLevel.Debug, new EventId(104, "SentSuccessfully"), "Message(s) sent successfully."); private static readonly Action _noMessages = LoggerMessage.Define(LogLevel.Debug, new EventId(105, "NoMessages"), "No messages in batch to send."); private static readonly Action _errorSending = LoggerMessage.Define(LogLevel.Error, new EventId(106, "ErrorSending"), "Error while sending to '{Url}'."); // When adding a new log message make sure to check with LongPollingTransport and ServerSentEventsTransport that share these logs to not have conflicting EventIds // We start the IDs at 100 to make it easy to avoid conflicting IDs public static void SendStarted(ILogger logger) { _sendStarted(logger, null); } public static void SendCanceled(ILogger logger) { _sendCanceled(logger, null); } public static void SendStopped(ILogger logger) { _sendStopped(logger, null); } public static void SendingMessages(ILogger logger, long count, Uri url) { _sendingMessages(logger, count, url, null); } public static void SentSuccessfully(ILogger logger) { _sentSuccessfully(logger, null); } public static void NoMessages(ILogger logger) { _noMessages(logger, null); } public static void ErrorSending(ILogger logger, Uri url, Exception exception) { _errorSending(logger, url, exception); } } } }