.NET 6中的HTTP/3支持使用代码演示

302 阅读7分钟

.NET 6包括对HTTP/3的预览支持:

  • 在Kestrel、HTTP.Sys和IIS for ASP.NET的服务器方案中
  • 在HttpClient中提出外向请求
  • 对于gRPC

什么是HTTP/3,为什么支持是重要的?

通过1.1版本的HTTP是一个相对简单的协议,打开一个TCP连接,通过明文发送一组头信息,然后接收响应。请求可以通过同一连接进行流水线处理,但每个请求必须按顺序处理。TLS增加了一些额外的复杂性,并且需要进行几次往返以启动连接,但一旦建立,HTTP就会以同样的方式在安全通道上使用。

由于许多网站已经转向需要TLS加密,而在HTTP/1.1中,每次连接只能提供一个请求,因此通常需要下载多种资源(脚本、图像、CSS、字体等)的网页的性能受到限制,因为需要多个连接,而每个连接的设置成本很高。

HTTP/2通过改变成为一个使用框架概念的二进制协议来解决这个问题,使多个请求可以在同一个连接上同时处理。TLS的设置成本可以支付一次,然后所有的请求都可以通过这个单一的连接交错进行。

这一切都很好,只是我们都已经进入了移动时代,现在大部分的访问都来自于使用Wi-Fi和手机连接的手机和平板电脑,这些连接可能是不可靠的。尽管HTTP/2支持多个数据流,但它们都通过一个TLS加密的连接,所以如果一个TCP数据包丢失,所有的数据流都会被阻断,直到数据被恢复。这就是所谓的线头阻塞问题。

HTTP/3通过使用一个新的底层连接协议QUIC来解决这些问题。QUIC使用UDP并内置了TLS,所以它建立连接的速度更快,因为TLS握手作为连接的一部分发生。每一帧数据都是独立加密的,因此在丢包的情况下,它不再有线路阻塞的问题。与TCP不同,QUIC连接是独立于IP地址的,因此移动客户可以在WIFI和蜂窝网络之间漫游,保持相同的逻辑连接,继续长时间下载等。

在网站和服务使用的指标中,使用P90、P95或P99跟踪最差连接的延迟是很常见的。事实证明,HTTP/3已经对这些数字产生了积极的影响,改善了连接最差的用户的体验,例如FacebookSnapchat谷歌云

.NET中的QUIC支持

QUIC被设计为HTTP/3的基础层,但它也可以被其他协议使用。它被设计成能够很好地处理网络变化,并在发生丢包的情况下有很好的恢复能力,适用于移动。

.NET使用MSQuic库来实现其QUIC。这是一个开源的、来自Windows网络团队的跨平台库。由于包装的原因,它被包括在Windows的.NET 6中,并作为一个单独的软件包用于Linux。

QUIC的一个关键区别是,TLS加密是内置的,因此连接建立包括TLS握手。这意味着所使用的TLS库需要提供API来实现这种握手方式。对于Windows,这些API包含在SChannel / Bcrypt.dll中。对于Linux来说,这就有点复杂了--.NET和Linux上大多数其他软件使用的OpenSSL还不包括这些API。OpenSSL团队一直在低头开发OpenSSL 3.0,它有一个提交FIPS 140-2认证的硬性截止日期,所以无法在OpenSSL 3.0中直接添加这种支持。

这给我们以及其他许多从事QUIC和HTTP/3工作的人带来了问题。为了在OpenSSL加入对QUIC握手的API支持之前提供一个权宜之计,微软已经与Akamai合作创建了一个OpenSSL的分叉--QuicTLS,提供API以实现QUIC握手。与其他随着时间推移与OpenSSL发生分歧的分叉不同,QuicTLS提供了与主线OpenSSL相比最小的差异,并与上游保持同步。

用于Linux的MSQuic软件包与QuicTLS静态链接,因此不需要单独下载和管理OpenSSL库的多个变体。这也意味着,当主线OpenSSL包括QUIC APIs时,该包将被更新以使用这些API。

在.NET 6中,我们没有公开.NET QUIC APIs,目标是在.NET 7中公开它们。QUIC可以像TCP套接字一样使用,并不是专门针对HTTP/3的,所以我们希望其他协议能够随着时间的推移建立在QUIC上,例如SMB over QUIC

.NET 6中的HTTP/3支持

在发表这篇文章的时候,HTTP/3的RFC还没有最终确定,所以仍然可以改变。我们在.NET 6中包含了HTTP/3,以便客户可以开始尝试使用它,但它是.NET 6的一个预览功能--这是因为它不符合.NET 6其他部分的质量标准。可能有粗糙的边缘,需要与其他服务器和客户端进行更广泛的测试,以确保兼容性,特别是在边缘情况下。

前提条件

要使用HTTP/3,需要安装MSQuic和其TLS依赖的先决条件版本。

窗口

MsQuic是作为.NET 6的一部分安装的,但是它需要一个更新的Schannel SSP版本,它提供了TLS API,这是由最近的操作系统版本提供的。

  • Windows 11 Build 22000或更高版本,或Server 2022 RTM

Windows 11构建版目前只对Windows内部人员开放。

Linux系统

在Linux上,libmsquic是通过微软官方Linux软件包库packages.microsoft.com发布的。为了使用它,必须手动添加。参见微软产品的Linux软件仓库。在配置了软件包馈送后,可以通过你的发行版的软件包管理器来安装它,例如在Ubuntu上:

sudo apt install libmsquic

Kestrel服务器

服务器支持包含在Kestrel中。预览功能需要使用以下项目属性来启用:

<PropertyGroup>
  <EnablePreviewFeatures>True</EnablePreviewFeatures>
</PropertyGroup>

然后在监听器选项中设置,例如:

public static async Task Main(string[] args)
{
  var builder = WebApplication.CreateBuilder(args);
  builder.WebHost.ConfigureKestrel((context, options) =>
  {
    options.Listen(IPAddress.Any, 5001, listenOptions =>
    {
      // Use HTTP/3
      listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
      listenOptions.UseHttps();
    });
  });
}

更多细节,请参见使用HTTP/3与ASP.NET Core Kestrel网络服务器

HTTP/3客户端

HttpClient已经更新,包括对HTTP/3的支持,但它需要用运行时标志启用。在项目文件中包括以下内容,以便用HttpClient启用HTTP/3:

<ItemGroup>
  <RuntimeHostConfigurationOption Include="System.Net.SocketsHttpHandler.Http3Support" Value="true" />
</ItemGroup>

HTTP/3需要被指定为请求的版本:

// See https://aka.ms/new-console-template for more information
using System.Net;

var client = new HttpClient();
client.DefaultRequestVersion = HttpVersion.Version30;
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;

var resp = await client.GetAsync("https://localhost:5001/");
var body = await resp.Content.ReadAsStringAsync();

Console.WriteLine($"status: {resp.StatusCode}, version: {resp.Version}, body: {body.Substring(0, Math.Min(100, body.Length))}");

通过HTTP.sys和IIS的HTTP/3

在Windows Server 2022上,Http.sys支持HTTP/3,当它用注册表键启用时,必须启用TLS 1.3(默认)。这与ASP.NET对HTTP/3的支持无关,因为在这种配置下,HTTP协议是由HTTP.sys处理的--所以它不仅适用于ASP.NET,也适用于由HTTP.sys提供的任何内容或服务。关于更多的细节,请看Windows网络团队的这篇博文

gRPC与HTTP/3

gRPC是一种使用protobuf序列化格式的RPC机制。gRPC通常使用HTTP/2作为其传输。HTTP/3使用相同的语义,所以几乎不需要改变就可以工作。HTTP/3的gRPC还不是一个标准,是由.NET团队提出的

下面的代码是基于greeter样本,使用hello world proto

客户端和服务器项目需要在其项目中启用与上述样本相同的各自预览功能。

ASP.NET服务器

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddGrpc();
builder.WebHost.ConfigureKestrel((context, options) =>
{
  options.Listen(IPAddress.Any, 5001, listenOptions =>
  {
    listenOptions.Protocols = HttpProtocols.Http3;
    listenOptions.UseHttps();
  });
});
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.MapGrpcService<GreeterService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run();

客户端

using Grpc.Net.Client;
using GrpcService1;
using System.Net;

var httpClient = new HttpClient();
httpClient.DefaultRequestVersion = HttpVersion.Version30;
httpClient.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions() { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);

var response = await client.SayHelloAsync(
new HelloRequest { Name = "World" });

Console.WriteLine(response.Message);

MacOS支持

.NET 6不包括对macOS上的HTTP/3的支持,这主要是因为缺乏一个QUIC兼容的TLS API。.NET在macOS上使用SecureTransport来实现其TLS,它还不包括支持QUIC握手的TLS API。虽然我们可以使用OpenSSL,但我们认为最好不要引入一个没有与操作系统的证书管理集成的额外依赖。

展望未来

我们将在.NET 7中进一步投资于QUIC和HTTP/3,所以希望能在预览版中看到更新的功能。