Last active
June 2, 2023 09:16
-
-
Save davidfowl/b7c28c87dd523686450ef98b73c6171d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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