Using HTTP/3 (QUIC) in .NET

  • Gérald Barré

#What's HTTP/3?

HTTP/3 is a new version of HTTP. HTTP/3 protocol is supported by most modern browsers and servers. This update should bring performance benefits mostly for mobile users or unreliable connections. The main idea is to replace TCP by a new protocol, QUIC, which removes some issues of TCP for HTTP. So, QUIC has the following advantages (non-exhaustive list):

  • Faster connection set-up by combining TCP and TLS handshakes
  • Less head of line blocking by better handling packet loss recovery logic
  • Connection migration, so you don't need to reconnect (handshake) when moving between networks (e.g. WIFI to cellular)

If you want to understand in details what is HTTP/3, you can read the following articles:

.NET 6 supports HTTP/3 for clients (HttpClient, including gRPC) and servers (Kestrel). This implementation is based on MsQuic, is a Microsoft implementation of the IETF QUIC protocol. Note that this is still in preview in .NET 6, so you need to explicitly enable it in the csproj or the code. At the moment, .NET supports HTTP/3 on:

  • Windows 11 and Windows Server 2022
  • Linux (you may need to install msquic using apt install libmsquic)

While msquic supports macOS using OpenSSL, the .NET implementation doesn't support it at the moment. Indeed, the .NET team prefers relying on the OS security APIs instead of adding a new dependency, so SecureTransport on macOS. It avoids integration issues, such as certificate management. However, SecureTransport doesn't expose methods to implement QUIC.

#Server (Kestrel)

You first need to enable preview features in the csproj:

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
  </PropertyGroup>
</Project>

Then, you can configure Kestrel to listen on HTTP/1, HTTP/2 and HTTP/3. It's important to support older protocols as not all clients support newer protocols. Also, HTTP/3 requires secure connections, so you must use UseHttps.

C#
using Microsoft.AspNetCore.Server.Kestrel.Core;

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, options) =>
{
    options.ListenAnyIP(5001, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
        listenOptions.UseHttps();
    });
});
var app = builder.Build();

app.MapGet("/", () => "hello world");
app.Run();

Most browsers don't allow HTTP/3 for localhost addresses. However, you can validate it works by looking at the response header. The response should contain the the alt-svc header with the value h3:

You can also check the server uses HTTP/3 by enabling more detailed logging. You can change the configuration in appsettings.json or appsettings.Development.json:

JSON
{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.Hosting.Diagnostics":  "Information"
    }
  }
}

Then, you should see the following in the logs:

You can also use W3C logging and check the protocol version used by the client:

C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddW3CLogging(logging =>
{
    logging.LoggingFields = W3CLoggingFields.All;
    logging.LogDirectory = @"C:\logs";
    logging.FlushInterval = TimeSpan.FromSeconds(2);
});

builder.WebHost.ConfigureKestrel((context, options) =>
{
    ...
});

var app = builder.Build();
app.UseW3CLogging();
app.MapGet("/", () => "hello world");
app.Run();

Alternatively, you can use the client from the next section to validate the server works correctly.

#Client (HttpClient)

HTTP/3 has preview support in .NET 6. So, you need to manually enable the feature before using HTTP/3 in HttpClient:

  • Option 1: Edit the csproj to add a runtime option

    csproj (MSBuild project file)
    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    
      <ItemGroup>
        <RuntimeHostConfigurationOption Include="System.Net.SocketsHttpHandler.Http3Support"
                                        Value="true" />
      </ItemGroup>
    </Project>
  • Option 2: Set the following switch before creating the first HttpClient

    C#
    System.AppContext.SetSwitch("System.Net.SocketsHttpHandler.Http3Support", true);

Then, you can use the HttpClient to make requests to HTTP/3 servers:

C#
using var client = new HttpClient();
client.DefaultRequestVersion = HttpVersion.Version30;

// The client falls back to HTTP2 or HTTP1 if HTTP3 is not supported
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;

// Will use HTTP3 if the server supports it
var data = await client.GetStringAsync("https://localhost:5001/");

You can also enable HTTP3 for specific requests:

C#
using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:5001/");
request.Version = HttpVersion.Version30;
request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;

using var response = await client.SendAsync(request);
var data = await response.Content.ReadAsStringAsync();

#Additional resources

This post is available in Spanish at https://www.campusmvp.es/recursos/post/como-utilizar-http-3-quic-en-net.aspx.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub