diff --git a/samples/ServerApplication/Program.cs b/samples/ServerApplication/Program.cs index 359c2bed..37cb557b 100644 --- a/samples/ServerApplication/Program.cs +++ b/samples/ServerApplication/Program.cs @@ -1,12 +1,15 @@ using System; using System.Net; using System.Security.Cryptography.X509Certificates; +using System.Text; using System.Threading.Tasks; using Bedrock.Framework; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; namespace ServerApplication { @@ -24,26 +27,36 @@ public static async Task Main(string[] args) services.AddSignalR(); + services.TryAddSingleton(); + services.TryAddSingleton>(serviceProvider => + { + var objectPoolProvider = serviceProvider.GetRequiredService(); + var policy = new StringBuilderPooledObjectPolicy(); + return objectPoolProvider.Create(policy); + }); + var serviceProvider = services.BuildServiceProvider(); var server = new ServerBuilder(serviceProvider) .UseSockets(sockets => { + var stringBuilderPool = serviceProvider.GetRequiredService>(); + // Echo server sockets.ListenLocalhost(5000, - builder => builder.UseConnectionLogging().UseConnectionHandler()); + builder => builder.UseConnectionLogging(stringBuilderPool: stringBuilderPool).UseConnectionHandler()); // HTTP/1.1 server sockets.Listen(IPAddress.Loopback, 5001, - builder => builder.UseConnectionLogging().UseConnectionHandler()); + builder => builder.UseConnectionLogging(stringBuilderPool: stringBuilderPool).UseConnectionHandler()); // SignalR Hub sockets.Listen(IPAddress.Loopback, 5002, - builder => builder.UseConnectionLogging().UseHub()); + builder => builder.UseConnectionLogging(stringBuilderPool: stringBuilderPool).UseHub()); // MQTT application sockets.Listen(IPAddress.Loopback, 5003, - builder => builder.UseConnectionLogging().UseConnectionHandler()); + builder => builder.UseConnectionLogging(stringBuilderPool: stringBuilderPool).UseConnectionHandler()); // Echo Server with TLS sockets.Listen(IPAddress.Loopback, 5004, @@ -54,7 +67,7 @@ public static async Task Main(string[] args) // NOTE: Do not do this in a production environment options.AllowAnyRemoteCertificate(); }) - .UseConnectionLogging().UseConnectionHandler()); + .UseConnectionLogging(stringBuilderPool: stringBuilderPool).UseConnectionHandler()); sockets.Listen(IPAddress.Loopback, 5005, builder => builder.UseConnectionLogging().UseConnectionHandler()); diff --git a/src/Bedrock.Framework/Middleware/ConnectionBuilderExtensions.cs b/src/Bedrock.Framework/Middleware/ConnectionBuilderExtensions.cs index e9b95a9e..27eb3444 100644 --- a/src/Bedrock.Framework/Middleware/ConnectionBuilderExtensions.cs +++ b/src/Bedrock.Framework/Middleware/ConnectionBuilderExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.ObjectPool; namespace Bedrock.Framework { @@ -17,11 +18,16 @@ public static class ConnectionBuilderExtensions /// /// Emits verbose logs for bytes read from and written to the connection. /// - public static TBuilder UseConnectionLogging(this TBuilder builder, string loggerName = null, ILoggerFactory loggerFactory = null, LoggingFormatter loggingFormatter = null) where TBuilder : IConnectionBuilder + public static TBuilder UseConnectionLogging(this TBuilder builder, string loggerName = null, ILoggerFactory loggerFactory = null, ObjectPool stringBuilderPool = null, LoggingFormatter loggingFormatter = null) where TBuilder : IConnectionBuilder { loggerFactory ??= builder.ApplicationServices.GetRequiredService(); var logger = loggerName == null ? loggerFactory.CreateLogger() : loggerFactory.CreateLogger(loggerName); - builder.Use(next => new LoggingConnectionMiddleware(next, logger, loggingFormatter).OnConnectionAsync); + if (stringBuilderPool == null) + { + var objectPoolProvider = new DefaultObjectPoolProvider(); + stringBuilderPool = objectPoolProvider.CreateStringBuilderPool(); + } + builder.Use(next => new LoggingConnectionMiddleware(next, logger, stringBuilderPool, loggingFormatter).OnConnectionAsync); return builder; } diff --git a/src/Bedrock.Framework/Middleware/Internal/LoggingStream.cs b/src/Bedrock.Framework/Middleware/Internal/LoggingStream.cs index c750eca9..107f3842 100644 --- a/src/Bedrock.Framework/Middleware/Internal/LoggingStream.cs +++ b/src/Bedrock.Framework/Middleware/Internal/LoggingStream.cs @@ -7,76 +7,42 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; namespace Bedrock.Framework.Infrastructure { - internal sealed class LoggingStream : Stream + public sealed class LoggingStream : Stream { private readonly Stream _inner; private readonly ILogger _logger; + private readonly ObjectPool _stringBuilderPool; private readonly LoggingFormatter _logFormatter; - public LoggingStream(Stream inner, ILogger logger, LoggingFormatter logFormatter = null) + public LoggingStream(Stream inner, ILogger logger, ObjectPool stringBuilderPool, LoggingFormatter logFormatter = null) { _inner = inner; _logger = logger; + _stringBuilderPool = stringBuilderPool; _logFormatter = logFormatter; } - public override bool CanRead - { - get - { - return _inner.CanRead; - } - } + public override bool CanRead => _inner.CanRead; - public override bool CanSeek - { - get - { - return _inner.CanSeek; - } - } + public override bool CanSeek => _inner.CanSeek; - public override bool CanWrite - { - get - { - return _inner.CanWrite; - } - } + public override bool CanWrite => _inner.CanWrite; - public override long Length - { - get - { - return _inner.Length; - } - } + public override long Length => _inner.Length; public override long Position { - get - { - return _inner.Position; - } - - set - { - _inner.Position = value; - } + get => _inner.Position; + set => _inner.Position = value; } - public override void Flush() - { - _inner.Flush(); - } + public override void Flush() => _inner.Flush(); - public override Task FlushAsync(CancellationToken cancellationToken) - { - return _inner.FlushAsync(cancellationToken); - } + public override Task FlushAsync(CancellationToken cancellationToken) => _inner.FlushAsync(cancellationToken); public override int Read(byte[] buffer, int offset, int count) { @@ -92,7 +58,7 @@ public override int Read(Span destination) return read; } - public async override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { int read = await _inner.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); Log("ReadAsync", new ReadOnlySpan(buffer, offset, read)); @@ -140,7 +106,7 @@ public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationTo return _inner.WriteAsync(source, cancellationToken); } - private void Log(string method, ReadOnlySpan buffer) + private void Log(string method, in ReadOnlySpan buffer) { if (_logFormatter != null) { @@ -153,20 +119,20 @@ private void Log(string method, ReadOnlySpan buffer) return; } - var builder = new StringBuilder(); + var builder = _stringBuilderPool.Get(); builder.AppendLine($"{method}[{buffer.Length}]"); - var charBuilder = new StringBuilder(); + var charBuilder = _stringBuilderPool.Get(); // Write the hex for (int i = 0; i < buffer.Length; i++) { builder.Append(buffer[i].ToString("X2")); - builder.Append(" "); + builder.Append(' '); var bufferChar = (char)buffer[i]; if (char.IsControl(bufferChar)) { - charBuilder.Append("."); + charBuilder.Append('.'); } else { @@ -175,15 +141,15 @@ private void Log(string method, ReadOnlySpan buffer) if ((i + 1) % 16 == 0) { - builder.Append(" "); - builder.Append(charBuilder.ToString()); + builder.Append(' ', 2); + builder.Append(charBuilder); builder.AppendLine(); charBuilder.Clear(); } else if ((i + 1) % 8 == 0) { - builder.Append(" "); - charBuilder.Append(" "); + builder.Append(' '); + charBuilder.Append(' '); } } @@ -193,32 +159,25 @@ private void Log(string method, ReadOnlySpan buffer) builder.Append(string.Empty.PadRight(2 + (3 * (16 - charBuilder.Length)))); // extra for space after 8th byte if (charBuilder.Length < 8) - builder.Append(" "); - builder.Append(charBuilder.ToString()); + { + builder.Append(' '); + } + builder.Append(charBuilder); } _logger.LogDebug(builder.ToString()); + + _stringBuilderPool.Return(builder); + _stringBuilderPool.Return(charBuilder); } // The below APM methods call the underlying Read/WriteAsync methods which will still be logged. - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); - } + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); - public override int EndRead(IAsyncResult asyncResult) - { - return TaskToApm.End(asyncResult); - } + public override int EndRead(IAsyncResult asyncResult) => TaskToApm.End(asyncResult); - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); - } + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); - public override void EndWrite(IAsyncResult asyncResult) - { - TaskToApm.End(asyncResult); - } + public override void EndWrite(IAsyncResult asyncResult) => TaskToApm.End(asyncResult); } } diff --git a/src/Bedrock.Framework/Middleware/LoggingConnectionMiddleware.cs b/src/Bedrock.Framework/Middleware/LoggingConnectionMiddleware.cs index 00c4108a..3ef1c351 100644 --- a/src/Bedrock.Framework/Middleware/LoggingConnectionMiddleware.cs +++ b/src/Bedrock.Framework/Middleware/LoggingConnectionMiddleware.cs @@ -3,10 +3,12 @@ using System; using System.IO.Pipelines; +using System.Text; using System.Threading.Tasks; using Bedrock.Framework.Infrastructure; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; namespace Bedrock.Framework { @@ -15,11 +17,13 @@ internal class LoggingConnectionMiddleware private readonly ConnectionDelegate _next; private readonly ILogger _logger; private readonly LoggingFormatter _loggingFormatter; + private readonly ObjectPool _stringBuilderPool; - public LoggingConnectionMiddleware(ConnectionDelegate next, ILogger logger, LoggingFormatter loggingFormatter = null) + public LoggingConnectionMiddleware(ConnectionDelegate next, ILogger logger, ObjectPool stringBuilderPool, LoggingFormatter loggingFormatter = null) { _next = next ?? throw new ArgumentNullException(nameof(next)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); _loggingFormatter = loggingFormatter; } @@ -29,7 +33,7 @@ public async Task OnConnectionAsync(ConnectionContext context) try { - await using (var loggingDuplexPipe = new LoggingDuplexPipe(context.Transport, _logger, _loggingFormatter)) + await using (var loggingDuplexPipe = new LoggingDuplexPipe(context.Transport, _logger, _stringBuilderPool, _loggingFormatter)) { context.Transport = loggingDuplexPipe; @@ -44,8 +48,8 @@ public async Task OnConnectionAsync(ConnectionContext context) private class LoggingDuplexPipe : DuplexPipeStreamAdapter { - public LoggingDuplexPipe(IDuplexPipe transport, ILogger logger, LoggingFormatter loggingFormatter) : - base(transport, stream => new LoggingStream(stream, logger, loggingFormatter)) + public LoggingDuplexPipe(IDuplexPipe transport, ILogger logger, ObjectPool stringBuilderPool, LoggingFormatter loggingFormatter) : + base(transport, stream => new LoggingStream(stream, logger, stringBuilderPool, loggingFormatter)) { } }