Skip to content

Instantly share code, notes, and snippets.

@davidfowl
Last active June 2, 2023 09:16
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save davidfowl/b7c28c87dd523686450ef98b73c6171d to your computer and use it in GitHub Desktop.
Save davidfowl/b7c28c87dd523686450ef98b73c6171d to your computer and use it in GitHub Desktop.
using System.IO.Pipelines;
using System.Net;
using System.Net.Security;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
var builder = WebApplication.CreateBuilder(args);
// This is a hack but there are no public APIs for client side QUIC as yet
var quicConnectionFactory = typeof(WebHostBuilderQuicExtensions).Assembly.GetType("Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicConnectionFactory")!;
builder.Services.AddSingleton(typeof(IMultiplexedConnectionFactory), quicConnectionFactory);
builder.WebHost.ConfigureKestrel(o =>
{
o.Listen(IPAddress.Loopback, 7000, options =>
{
// We tell it we're HTTP/3 to get QUIC support in Kestrel
options.Protocols = HttpProtocols.Http3;
// QUIC requires TLS
options.UseHttps().UseCustomQuicProtocol();
});
});
var app = builder.Build();
app.Start();
var connectionFactory = app.Services.GetRequiredService<IMultiplexedConnectionFactory>();
var features = new FeatureCollection();
features.Set<SslClientAuthenticationOptions>(new()
{
ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 }
});
var quicConnection = await connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 7000), features);
var stream = await quicConnection.ConnectAsync();
// Write custom protocol on top of quic here
var read = Console.OpenStandardInput().CopyToAsync(stream.Transport.Output);
var write = stream.Transport.Input.CopyToAsync(Console.OpenStandardOutput());
app.WaitForShutdown();
try
{
await read;
await write;
}
catch (ConnectionResetException)
{
// Treat the reset like a shutdown (we hit ctrl+c)
}
public static class QuicProtocolExtensions
{
public static IMultiplexedConnectionBuilder UseCustomQuicProtocol(this IMultiplexedConnectionBuilder builder)
{
return builder.Use(next =>
{
return async context =>
{
// Graceful shutdown
var lifetimeFeature = context.Features.Get<IConnectionLifetimeNotificationFeature>()!; // Kestrel implements this
while (true)
{
// Accept a stream from this quic connection
var connection = await context.AcceptAsync();
if (connection is null) break;
try
{
// Write custom protocol on top of quic here
// Echo server
await connection.Transport.Input.CopyToAsync(connection.Transport.Output, lifetimeFeature.ConnectionClosedRequested);
}
catch (OperationCanceledException)
{
// Graceful shutdown (sorta, we are using that token to gracefully shutdown)
break;
}
}
};
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment