aspnetcore/src/Microsoft.AspNetCore.Http.C.../Internal/SendUtils.cs

175 lines
6.5 KiB
C#

// 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<byte> _buffer;
public ReadOnlySequenceContent(in ReadOnlySequence<byte> 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<ILogger, Exception> _sendStarted =
LoggerMessage.Define(LogLevel.Debug, new EventId(100, "SendStarted"), "Starting the send loop.");
private static readonly Action<ILogger, Exception> _sendStopped =
LoggerMessage.Define(LogLevel.Debug, new EventId(101, "SendStopped"), "Send loop stopped.");
private static readonly Action<ILogger, Exception> _sendCanceled =
LoggerMessage.Define(LogLevel.Debug, new EventId(102, "SendCanceled"), "Send loop canceled.");
private static readonly Action<ILogger, long, Uri, Exception> _sendingMessages =
LoggerMessage.Define<long, Uri>(LogLevel.Debug, new EventId(103, "SendingMessages"), "Sending {Count} bytes to the server using url: {Url}.");
private static readonly Action<ILogger, Exception> _sentSuccessfully =
LoggerMessage.Define(LogLevel.Debug, new EventId(104, "SentSuccessfully"), "Message(s) sent successfully.");
private static readonly Action<ILogger, Exception> _noMessages =
LoggerMessage.Define(LogLevel.Debug, new EventId(105, "NoMessages"), "No messages in batch to send.");
private static readonly Action<ILogger, Uri, Exception> _errorSending =
LoggerMessage.Define<Uri>(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);
}
}
}
}