Skip to content

Instantly share code, notes, and snippets.

@HurricanKai
Last active May 27, 2021 01:48
Show Gist options
  • Save HurricanKai/1709f09e3a63c8ba0abf2c48d6a0bf32 to your computer and use it in GitHub Desktop.
Save HurricanKai/1709f09e3a63c8ba0abf2c48d6a0bf32 to your computer and use it in GitHub Desktop.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Pipelines" Version="4.7.1" />
</ItemGroup>
</Project>
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Text.Unicode;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
// note that the SDK of this project is the web SDK, as specified in .csproj
// therefore lots of things that ar normally NuGet are pre-installed
namespace Bedrock
{
class Program
{
static Task Main(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging((builder => builder.SetMinimumLevel(LogLevel.Debug)))
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>()
.UseKestrel(options =>
{
options.ListenAnyIP(10000, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.None;
#if DEBUG
listenOptions.UseConnectionLogging();
#endif
listenOptions.UseConnectionHandler<SimpleConnectionHandler>();
});
});
})
.UseConsoleLifetime()
.RunConsoleAsync();
}
public class Startup
{
public void Configure(IApplicationBuilder app)
{
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISomeService, ServiceImpl>();
}
}
public interface ISomeService
{
string GetResponse(string input);
}
public class ServiceImpl : ISomeService
{
public string GetResponse(string input) => input + "!";
}
public class SimpleConnectionHandler : ConnectionHandler
{
private readonly ISomeService _dep;
private readonly ILogger _logger;
public SimpleConnectionHandler(ISomeService dep, ILogger<SimpleConnectionHandler> logger)
{
_dep = dep;
_logger = logger;
}
public override async Task OnConnectedAsync(ConnectionContext connection)
{
var reader = connection.Transport.Input;
var writer = connection.Transport.Output;
await Process(reader, writer);
}
// SEE https://devblogs.microsoft.com/dotnet/system-io-pipelines-high-performance-io-in-net/
private async Task Process(PipeReader reader, PipeWriter writer)
{
while (true)
{
ReadResult result = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;
buffer = ProcessBuffer(writer, buffer);
// Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.Start, buffer.End);
// Stop reading if there's no more data coming
if (result.IsCompleted)
{
break;
}
}
}
private ReadOnlySequence<byte> ProcessBuffer(PipeWriter writer, ReadOnlySequence<byte> buffer)
{
SequencePosition? position = null;
do
{
// Look for a EOL in the buffer
position = buffer.PositionOf((byte) '\0');
if (position != null)
{
// Process the line
var response = ProcessLine(buffer.Slice(0, position.Value));
// Skip the line + the \0 character (basically position)
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
// Use writer.GetSpan & writer.Advance to write the response.
}
} while (position != null);
return buffer;
}
private ReadOnlySpan<byte> ProcessLine(ReadOnlySequence<byte> slice)
{
var arr = slice.Slice(0, slice.Length - 1).ToArray();
var dest = new char[arr.Length];
Utf8.ToUtf16(arr, dest, out _, out _);
var str = new string(dest);
_logger.LogInformation($"Received: {str}");
var response = _dep.GetResponse(str);
var dest2 = new byte[response.Length];
Utf8.FromUtf16(response.AsSpan(), dest2, out _, out _);
return dest2;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment