关于.net:必看NET-7-在网络领域的四大更新

108次阅读

共计 13979 个字符,预计需要花费 35 分钟才能阅读完成。

最新的 .NET 7 现已公布,咱们想介绍一下其在网络畛域所做的一些乏味的更改和增加。这篇文章咱们将探讨 .NET 7 在 HTTP 空间、新 QUIC API、网络安全和 WebSockets 方面的变动。

HTTP

改良了对连贯尝试失败的解决

在 .NET 6 之前的版本中,如果连接池中没有立刻可用的连贯,(处理程序上的设置容许的状况下,例如 HTTP /1.1 中的 MaxConnectionsPerServer,或 HTTP/2 中的 EnableMultipleHttp2Connections)新的 HTTP 申请始终会发动新的连贯尝试并期待响应。这样做的毛病是,建设该连贯须要一段时间,而在这段时间里如果另一个连贯曾经可用,该申请仍将持续期待它生成的连贯,从而影响提早。在 .NET 6.0 中咱们扭转了这一过程,无论是新建设的连贯还是与此同时筹备好解决申请的另一个连贯,第一个可用的连贯会解决申请。这样仍会一个新连贯被建设(受限制),如果发动的申请未应用这一连贯,则它会被合并以供后续申请应用。

可怜的是,.NET 6.0 中的这一性能对某些用户来说是有问题的:失败的连贯尝试也会使位于申请队列顶部的申请失败,这可能会在某些状况下导致意外的申请失败。此外,如果因为某些起因(例如因为服务器行为不当或网络问题)池中有一个永远未被应用的连贯,与之关联的新传入的申请也将提早并可能超时。

在 .NET 7.0 中,咱们施行了以下更改来解决这些问题:

  • 失败的连贯尝试只能使其相干的发动申请失败,而不会导致无关的申请失败。如果在连贯失败时原始申请已失去解决,则连贯失败将被疏忽 (dotnet/runtime#62935)。
  • 如果一个申请发动了一个新连贯,但随后被池中的另一个连贯解决,则新的待应用的连贯尝试将不论 ConnectTimeout,在短时间后主动超时。通过此更改,提早的连贯将不会提早不相干的申请 (dotnet/runtime#71785)。请留神,不被应用的连贯尝试主动超时失败的这一过程只会在后盾本人运行,用户不会看到此过程。察看它们的惟一办法是启用 telemetry。

HttpHeaders 读取线程平安

这些 HttpHeaders 汇合素来都不是线程平安的。拜访 header 可能会强制提早解析它的值,从而导致对底层数据结构的批改。

在 .NET 6 之前,同时读取汇合在大多数状况下恰好是线程平安的。

从 .NET 6 开始,因为外部不再须要锁定,针对 header 解析执行的锁定较少。这一变动导致许多用户谬误地同时拜访 header,例如,在 gRPC (dotnet/runtime#55898)、NewRelic (newrelic/newrelic-dotnet-agent#803)甚至 HttpClient 自身(dotnet/runtime #65379)。违反 .NET 6 中的线程平安可能会导致 header 值反复 / 格局谬误或在枚举(enumeration)/header 访问期间产生各种异样。

.NET 7 使 header 行为更加直观。该 HttpHeaders 汇合当初合乎 Dictionary 线程平安保障:

汇合能够同时反对多个读者,只有它不被批改。极少数状况下,枚举(enumeration)与书写拜访权限争用,则该汇合必须在整个枚举期间被锁定。要容许多个线程拜访汇合以同时进行读写,您必须实现本人的同步。

这是通过以下更改实现的:

  • 有效值的“验证读取”不会删除有效值 – dotnet/runtime#67833(感激 @heathbm)。
  • 同时读取是线程平安的——dotnet/runtime#68115。

检测 HTTP/2 和 HTTP/3 协定谬误

HTTP/2 和 HTTP/3 协定在 RFC 7540 第 7 节和 RFC 9114 第 8.1 节中定义了协定级别的错误代码,例如,HTTP/2 中的 REFUSED_STREAM (0x7) 或 HTTP/3 中的 H3_EXCESSIVE_LOAD (0x0107)。与 HTTP 状态代码不同,这是对大多数 HttpClient 用户来说不重要的低级错误信息,但它在高级 HTTP/2 或 HTTP/3 场景中有帮忙,特地是 grpc-dotnet,其中辨别协定谬误对于实现客户端重试至关重要。

咱们定义了一个新的异样 HttpProtocolException 来在其 ErrorCode 属性中保留协定级错误代码。

HttpClient 间接调用时,HttpProtocolException 能够是外部异样 HttpRequestException:

try
{using var response = await httpClient.GetStringAsync(url);
}
catch (HttpRequestException ex) when (ex.InnerException is HttpProtocolException pex)
{Console.WriteLine("HTTP error code:" + pex.ErrorCode)
}

应用 HttpContent 的响应流时,它被间接抛出:

using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var responseStream = await response.Content.ReadAsStreamAsync();
try
{await responseStream.ReadAsync(buffer);
}
catch (HttpProtocolException pex)
{Console.WriteLine("HTTP error code:" + pex.ErrorCode)
}

HTTP/3

在 HttpClient 之前的 .NET 版本曾经实现了对 HTTP/3 的反对,所以咱们次要集中精力在这个畛域的 System.Net.Quic 底层。尽管如此,咱们的确在 .NET 7 中引入了一些修复和更改。

最重要的变动是当初默认启用 HTTP/3 (dotnet/runtime#73153)。这并不意味着从当初开始所有 HTTP 申请都将首选 HTTP/3,但在某些状况下它们可能会降级到 HTTP/3。为此,申请必须通过将 HttpRequestMessage.VersionPolicy 设置为 RequestVersionOrHigher,从而可能版本升级。而后,如果服务器在 Alt-Svc header 中有 HTTP/3 受权,HttpClient 将应用它进行进一步的申请,请参阅 RFC 9114 第 3.1.1 节。

QUIC

QUIC 是一种新的传输层协定。它最近已在 RFC 9000 中标准化。它应用 UDP 作为底层协定,并且它实质上是平安的,因为它要求应用 TLS 1.3。与家喻户晓的传输协定(如 TCP 和 UDP)的另一个乏味区别是它在传输层上内置了流多路复用。这使其可能领有多个并发的独立数据流,且这些数据流不会相互影响。

QUIC 自身没有为替换的数据定义任何语义,因为它是一种传输协定。它更实用于应用层协定,例如 HTTP/3 或 SMB over QUIC。它还能够用于任何自定义协定。

与 TLS 的 TCP 相比,该协定具备许多劣势。例如,它不须要像顶部带有 TLS 的 TCP 那样多的往返行程,所以可能更快地建设连贯。它可能防止队头阻塞问题,一个失落的数据包不会阻塞所有其余流的数据。另一方面,应用 QUIC 也有毛病。因为它是一个新协定,它的采纳仍在增长并且是无限的。除此之外,QUIC 流量甚至可能被某些网络组件阻止。

.NET 中的 QUIC

咱们在 System.Net.Quic 库中介绍了 .NET 5 中的 QUIC 实现。然而,到目前为止,这个库是仅限外部的,并且只为本人的 HTTP/3 实现服务。随着 .NET 7 的公布,咱们公开了该库并公开了它的 API。因为咱们只有 HttpClient 和 Kestrel 作为此版本 API 的使用者,因而咱们决定将它们保留为预览性能。它使咱们可能在确定最终模式之前在下一个版本中调整 API。

从施行的角度来看,System.Net.Quic 取决于 QUIC 协定的原生实现 MsQuic。因而,System.Net.Quic 平台反对和依赖项继承自 MsQuic,并记录在 HTTP/3 平台依赖项文档中。简而言之,MsQuic 库作为 .NET for Windows 的一部分提供。对于 Linux,libmsquic 必须通过适当的包管理器手动装置。对于其余平台,依然能够手动构建 MsQuic,无论是针对 SChannel 还是 OpenSSL,并将其与 System.Net.Quic 一起应用。

API 概述

System.Net.Quic 携带了可能应用 QUIC 协定的三个次要类:

  • QuicListener – 服务器端类,用于承受传入连贯。
  • QuicConnection – QUIC 连贯,对应 RFC 9000 Section 5。
  • QuicStream – QUIC 流,对应 RFC 9000 Section 2。

然而在应用这些类之前,用户代码应该查看以后零碎是否反对 QUIC,因为零碎可能缺失 libmsquic 或者不反对 TLS 1.3。为此, QuicListener 和 QuicConnection 都公开了一个动态属性 IsSupported:

if (QuicListener.IsSupported)
{QuicListenerOptions
// Use QuicListener
}
else
{// Fallback/Error}

if (QuicConnection.IsSupported)
{// Use QuicConnection}
else
{// Fallback/Error}

请留神,目前这两个属性是同步的并将显示雷同的值,但未来可能会扭转。所以咱们倡议检查一下反对服务器场景的 QuicListener.IsSupported 和用于客户端的 QuicListener.IsSupported。

QuicListener

QuicListener 属于承受客户端的传入连贯的服务器端类。该侦听设施是通过静态方法 QuicListener.ListenAsync 结构和启动的。该办法承受 QuicListenerOptions 类的一个实例,其中蕴含启动侦听设施和承受传入连贯所需的所有设置。之后,侦听设施着手通过 AcceptConnectionAsync 散发连贯。此办法返回的连贯始终是齐全连贯的,这意味着 TLS 交互曾经实现,连贯能够应用了。最初,要敞开侦听设施并开释所有资源,必须调用 DisposeAsync 办法。

更多对于这个类设计的细节能够在 QuicListener API Proposal (dotnet/runtime#67560) 中找到。

QuicConnection

是用于服务器端和客户端 QUIC 连贯的类。服务器端连贯由侦听设施外部创立,并通过 QuicListener.AcceptConnectionAsync 散发连贯。客户端连贯必须被关上并连贯到服务器。静态方法 QuicConnection 和侦听设施一起,建设并实例连贯。它承受 QuicClientConnectionOptions 的实例,这是一个相似于 QuicServerConnectionOptions 的类。首次之前,此链接的工作形式与客户端和服务器之间没有区别。它能够关上向外和向内的流。它还提供与连贯信息无关的属性,如 LocalEndPoint、RemoteEndPoint 或 RemoteCertificate。

当连贯的工作实现后,须要敞开和处理侦听设施。QUIC 协定要求应用应用层代码立刻敞开侦听设施,参见 RFC 9000 Section 10.2。为此,能够调用带有应用层代码的 CloseAsync,如果没有,DisposeAsync 将应用 QuicConnectionOptions.DefaultCloseErrorCode 中提供的代码。无论是哪种形式,都必须在连贯工作完结时调用 DisposeAsync,涌起齐全开释所有相干资源。

更多对于这个类设计的细节能够在 QuicConnection API Proposal (dotnet/runtime#68902) 中找到。

QuicStream

QuicStream 是 QUIC 协定中用于发送和接收数据的理论类型。它起源于一般的流 Stream。能够和一般的流一样应用,但它也提供了一些特定于 QUIC 协定的个性。首先,QUIC 流能够是单向的,也能够是双向的,参见 RFC 9000 Section 2.1。双向流可能在两端发送和接收数据,而单向流只能从发动端输出数据,从承受端读取。每端都能够限度每种类型的并发流的数量,参见 QuicConnectionOptions.MaxInboundBidirectionalStreams 与 QuicConnectionOptions.MaxInboundUnidirectionalStreams。

QUIC 流的另一个特点是可能在流的工作过程中显式地敞开写入端,参见 CompleteWrites or WriteAsync?ocid=AID3052907)-system-boolean-system-threading-cancellationtoken)) 重载 CompleteWrites 参数。敞开写入端能够让承受端明确不会再有更多的数据输出了,但另一端依然能够持续发送数据流(在双向流的状况下)。这在 HTTP 申请 / 响应替换等场景中十分有用,客户端发送申请并敞开写入端,服务器就晓得这是申请发送的所有内容了。在此之后,服务器依然可能发送响应,但客户端却不会发送更多的数据流了。而对于谬误的状况,流的写入或读取端都能够进行停止,请参阅 Abort。下表总结了每种流类型的每种办法的体现(留神客户端和服务器都能够凋谢和承受流):

在以上这些办法中,quickstream 提供了两个专门的属性,用于在流的读写端敞开时取得告诉: 它们是 readclosed 和 WritesClosed。两者都返回一个工作,该工作实现后相应的边被敞开。无论工作是胜利还是停止,其中都将蕴含相应的字段。当用户代码须要晓得流端的敞开状况而不须要调用 ReadAsync 或 WriteAsync 时,这些属性十分就派上作用了。

最初,当流的工作实现时,它须要应用 DisposeAsync 办法。这一办法将确保读取 / 写入端 (取决于流类型) 都敞开。如果流直到完结还没有被正确读取,dispose 将收回一个等效的 Abort(QuicAbortDirection.Read) 命令。然而,如果流写入端还没有敞开,写入端会像应用 CompleteWrites 办法一样被敞开。之所以存在这种差别,是为了确保一般流的应用可能依照预期的形式进行,并进入相应的门路。

更多对于这个类设计的细节能够在 the QuicStream API Proposal (dotnet/runtime#69675) 中找到。

将来

因为 System.Net.Quic 是新公开的,并且用法无限,因而咱们将不胜感激无关 API 形态的任何错误报告或见解。因为 API 处于预览模式,咱们依然有机会依据收到的反馈针对 .NET 8 调整它们。能够在 dotnet/runtime 存储库中提交问题。

平安

Negotiate API

Windows Authentication 是一个蕴含多种技术的术语,用于企业中针对地方机构(通常是域控制器)对用户和应用程序进行身份验证。它反对诸如单点登录电子邮件服务或 Intranet 应用程序之类的场景。用于身份验证的根底技术是 Kerberos、NTLM 和蕴含的 Negotiate 协定,其中为特定身份验证计划抉择最合适的技术。

在 .NET 7 之前,Windows 身份验证在高级 API 中公开。例如 HttpClient (Negotiate 和 NTLM 身份验证计划),SmtpClient (GSSAPI 和 NTLM 身份验证计划),NegotiateStream、ASP.NET Core 和 SQL Server 客户端库。尽管这涵盖了最终用户的大多数场景,但它对库作者来说是无限的。其余库,如 Npgsql PostgreSQL client、MailKit、Apache Kudu client 须要诉诸各种技巧,为并非基于 HTTP 或其余可用的高级构建块构建的低级协定施行雷同的身份验证计划。

.NET 7 引入了新的 API,提供低级构建块来执行上述协定的身份验证替换,请参阅 dotnet/runtime#69920。与 .NET 中的所有其余 API  一样,它在构建时思考了跨平台互操作性。在 Linux、macOS、iOS 和其余相似平台上,它应用 GSSAPI 零碎库。在 Windows 上,它依赖于 SSPI 库。对于零碎实现不可用的平台,例如 Android 和 tvOS,存在无限的仅客户端实现。

如何应用 API

为了了解身份验证 API 的工作原理,让咱们从一个示例开始,理解身份验证会话在 SMTP 等高级协定中的外观。该示例取自 Microsoft protocol documentation,该文档对其进行了更具体的解释。

S: 220 server.contoso.com Authenticated Receive Connector
C: EHLO client.contoso.com
S: 250-server-contoso.com Hello [203.0.113.1]
S: 250-AUTH GSSAPI NTLM
S: 250 OK
C: AUTH GSSAPI <token1>
S: 334 <token2>
C: <token3>
S: 235 2.7.0 Authentication successful

身份验证从客户端生成质询令牌开始。而后服务器产生响应。客户端解决响应,并向服务器发送新的质询。这种质询 / 响应替换能够产生屡次。当任何一方回绝认证或单方都承受认证时,它完结。令牌的格局由 Windows 身份验证协定定义,而封装是高级协定标准的一部分。在此示例中,SMTP 协定事后增加 334 代码以告知客户端服务器产生了身份验证响应,该 235 代码示意身份验证胜利。

大部分新 API 都以新 NegotiateAuthentication 类为核心。它用于实例化客户端或服务器端身份验证的上下文。有多种选项能够指定建设通过身份验证的会话的要求,例如要求加密或确定要应用的特定协定 (Negotiate, Kerberos 或 NTLM) 指定参数后,身份验证将通过在客户端和服务器之间替换身份验证质询 / 响应来进行。GetOutgoingBlob 办法用于此目标。它能够解决字节跨度或 base64 编码的字符串。

以下代码将同时执行客户端和服务器,对同一台机器上的以后用户进行局部身份验证:

using System.Net;
using System.Net.Security;

var serverAuthentication = new NegotiateAuthentication(new NegotiateAuthenticationServerOptions {});
var clientAuthentication = new NegotiateAuthentication(
    new NegotiateAuthenticationClientOptions
    {
        Package = "Negotiate",
        Credential = CredentialCache.DefaultNetworkCredentials,
        TargetName = "HTTP/localhost",
        RequiredProtectionLevel = ProtectionLevel.Sign
    });

string? serverBlob = null;
while (!clientAuthentication.IsAuthenticated)
{
    // Client produces the authentication challenge, or response to server's challenge
    string? clientBlob = clientAuthentication.GetOutgoingBlob(serverBlob, out var clientStatusCode);
    if (clientStatusCode == NegotiateAuthenticationStatusCode.ContinueNeeded)
    {
        // Send the client blob to the server; this would normally happen over a network
        Console.WriteLine($"C: {clientBlob}");
        serverBlob = serverAuthentication.GetOutgoingBlob(clientBlob, out var serverStatusCode);
        if (serverStatusCode != NegotiateAuthenticationStatusCode.Completed &&
            serverStatusCode != NegotiateAuthenticationStatusCode.ContinueNeeded)
        {Console.WriteLine($"Server authentication failed with status code {serverStatusCode}");
            break;
        }
        Console.WriteLine($"S: {serverBlob}");
    }
    else
    {
        Console.WriteLine(
            clientStatusCode == NegotiateAuthenticationStatusCode.Completed ?
            "Successfully authenticated" :
            $"Authentication failed with status code {clientStatusCode}");
        break;
    }
}

一旦建设了通过身份验证的会话,NegotiateAuthentication 实例就能够用于对传出音讯进行签名 / 加密并验证 / 解密传入音讯。这是通过 Wrap 和 Unwrap 办法实现的。

证书验证选项

当客户端收到服务器的证书时,反之亦然,如果申请客户端证书,证书将通过 X509Chain。验证始终进行,即便 RemoteCertificateValidationCallback 提供了验证,并且在验证期间可能会下载其余证书。因为无法控制此行为,因而提出了几个问题。其中包含齐全阻止证书下载、设置超时或提供自定义存储以从中获取证书的要求。为了缓解这整组问题,咱们决定在 SslClientAuthenticationOptions 和 SslServerAuthenticationOptions 上引入一个新属性 CertificateChainPolicy。此属性的指标是在 AuthenticateAsClientAsync / AuthenticateAsServerAsync 操作期间生成链时笼罩 SslStream 的默认行为。在失常状况下,X509ChainPolicy 在后盾主动构建。然而,如果指定了这个新属性,它将优先并被应用,从而使用户可能齐全管制证书验证过程。

更多信息能够在 API 提案 (dotnet/runtime#71191) 中找到。

TLS 简介

建设新的 TLS 连贯是相当低廉的操作,因为它须要多个步骤和屡次往返。在常常从新创立与同一服务器的连贯的状况下,握手所耗费的工夫将加起来。TLS 提供的被称为会话复原的性能能够缓解这种状况,请参阅 RFC 5246 Section 7.3 和 RFC 8446 Section 2.2。简而言之,在握手期间,客户端能够发送先前建设的 TLS 会话的标识,如果服务器批准,则依据先前连贯的缓存数据从新建设平安上下文。只管不同 TLS 版本的机制不同,但最终目标是雷同的,即在从新建设与先前连贯的服务器的连贯时节俭往返工夫和一些 CPU 工夫。此性能由 Windows 上的 SChannel 主动提供,但在 Linux 上应用 OpenSSL 须要进行几处更改能力启用此性能:

  • 服务器端(无状态)dotnet/runtime#57079 和 dotnet/runtime#63030
  • 客户端 – dotnet/runtime#64369
  • 缓存大小管制 – dotnet/runtime#69065

如果不须要缓存 TLS 上下文,能够应用环境变量“DOTNET_SYSTEM_NET_SECURITY_DISABLETLSRESUME”或通过 AppContext.SetSwitch“System.Net.Security.TlsCacheSize”在过程范畴内敞开它。

OCSP Stapling

Online Certificate Status Protocol (OCSP) Stapling 是服务器提供已签名和工夫戳证实(OCSP 响应)的机制,证实发送的证书尚未被撤消,参见 RFC 6961。因而,客户端无需分割 OCSP 服务器自身,从而缩小了建设连贯所需的申请数量以及施加在 OCSP 服务器上的负载。因为 OCSP 响应须要由证书颁发机构(CA)签名,因而提供证书的服务器无奈伪造它。在此版本之前,咱们没有利用此 TLS 性能,无关详细信息请参阅 dotnet/runtime#33377。

跨平台的一致性

咱们晓得,.NET 提供的某些性能仅在某些平台上可用。然而每个版本咱们都试图进一步缩小差距。在 .NET 7 中,咱们对网络安全空间进行了一些更改,以改善差别:

  • 在实用于 TLS 1.3 的 Linux 上反对握手后认证 – dotnet/runtime#64268
  • 近程证书现已在 Windows 上设置 SslClientAuthenticationOptions.LocalCertificateSelectionCallback–dotnet/runtime#65134
  • 反对在 OSX 和 Linux 上的 TLS 握手中发送受信赖 CA 的名称 – dotnet/runtime#65195

WebSockets

WebSocket 握手响应详细信息

在 .NET 7 之前,WebSocket 的收场握手(对降级申请的 HTTP 响应)的服务器响应局部暗藏在 ClientWebSocket 实现中,并且所有握手谬误都将显示为 WebSocketException,在异样信息旁边没有太多详细信息。然而,无关 HTTP 响应标头和状态代码的信息在失败和胜利计划中都可能很重要。如果产生故障,HTTP 状态代码能够帮忙辨别可重试和不可重试的谬误(例如,服务器基本不反对 WebSockets,或者只是暂时性网络谬误)。标头还可能蕴含无关如何解决这种状况的其余信息。即便在 WebSocket 握手胜利的状况下,标头也很有用,例如,它们能够蕴含与会话绑定的令牌、与子协定版本相干的信息,或者服务器可能很快敞开的信息。

.NET 7 将 CollectHttpResponseDetails 设置增加到  ClientWebSocketOptions 中,该设置容许在 ConnectAsync 调用期间收集 ClientWebSocket 实例中的降级响应详细信息。你稍后能够应用 ClientWebSocket 实例的 HttpStatusCode 和 HttpResponseHeaders 属性拜访数据,即便在 ConnectAsync 引发异样的状况下也是如此。请留神,在非凡状况下,信息可能不可用,即如果服务器从未响应申请。

另请留神,如果连贯胜利,并且在应用 HttpResponseHeaders 数据后,能够通过将 ClientWebSocket.HttpResponseHeaders 属性设置为 null 来缩小 ClientWebSocket 的内存占用量。

var ws = new ClientWebSocket();
ws.Options.CollectHttpResponseDetails = true;
try
{await ws.ConnectAsync(uri, cancellationToken);
    // success scenario
    ProcessSuccess(ws.HttpResponseHeaders);
    ws.HttpResponseHeaders = null; // clean up (if needed)
}
catch (WebSocketException)
{
    // failure scenario
    if (ws.HttpStatusCode != 0)
    {ProcessFailure(ws.HttpStatusCode, ws.HttpResponseHeaders);
    }
}

提供内部 HTTP 客户端

在默认状况下,ClientWebSocket 应用缓存的动态 HttpMessageInvoker 实例来执行 HTTP 降级申请。然而,有一些 ClientWebSocketOptions 会阻止缓存调用程序,例如 Proxy、ClientCertificates 或 Cookie。具备这些参数的 HttpMessageInvoker 实例不平安,每次调用 ConnectAsync 时都须要创立。这会导致许多不必要的调配,并使 HttpMessageInvoker 连接池无奈重用。

.NET 7 容许您应用 ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) 重载将现有的 HttpMessageInvoker (例如 HttpClient) 实例传递给 ConnectAsync 调用。在这种状况下,将应用提供的实例执行 HTTP 降级申请。

var httpClient = new HttpClient();

var ws = new ClientWebSocket();
await ws.ConnectAsync(uri, httpClient, cancellationToken);

请留神,如果传递了自定义 HTTP 调用程序,则不得设置以下任何 ClientWebSocketOptions,而应在 HTTP 调用程序上设置:

  • ClientCertificates
  • Cookies
  • Credentials
  • Proxy
  • RemoteCertificateValidationCallback
  • UseDefaultCredentials

以下是在 HttpMessageInvoker 实例上设置所有这些选项的办法:

var handler = new HttpClientHandler();
handler.CookieContainer = cookies;
handler.UseCookies = cookies != null;
handler.ServerCertificateCustomValidationCallback = remoteCertificateValidationCallback;
handler.Credentials = useDefaultCredentials ?
    CredentialCache.DefaultCredentials :
    credentials;
if (proxy == null)
{handler.UseProxy = false;}
else
{handler.Proxy = proxy;}
if (clientCertificates?.Count > 0)
{handler.ClientCertificates.AddRange(clientCertificates);
}
var invoker = new HttpMessageInvoker(handler);

var ws = new ClientWebSocket();
await ws.ConnectAsync(uri, invoker, cancellationToken);

WebSockets over HTTP/2

.NET 7 还减少了通过 HTTP/2 应用 WebSocket 协定的性能,如 RFC 8441 中所述。这样,WebSocket 连贯是通过 HTTP / 2 连贯上的单个流建设的。这容许同时在多个 WebSocket 连贯和 HTTP 申请之间共享单个 TCP 连贯,从而更无效地应用网络。

要通过 HTTP/2 启用 WebSockets,您能够将 ClientWebSocketOptions.HttpVersion 选项设置为 HttpVersion.Version20。您还能够通过设置  ClientWebSocketOptions.HttpVersionPolicy 属性来启用所应用的 HTTP 版本的降级 / 降级。这些选项的行为形式与 HttpRequestMessage.Version 和 HttpRequestMessage.VersionPolicy 的行为形式雷同。

例如,以下代码将探测 HTTP/2 WebSocket,如果无奈建设 WebSocket 连贯,它将回退到 HTTP/1.1:

var ws = new ClientWebSocket();
ws.Options.HttpVersion = HttpVersion.Version20;
ws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
await ws.ConnectAsync(uri, httpClient, cancellationToken);

HttpVersion.Version11 和 HttpVersionPolicy.RequestVersionOrHigher 的组合将导致与上述雷同的行为,而 HttpVersionPolicy.RequestVersionExact 将不容许降级 / 降级 HTTP 版本。

默认状况下,设置了 HttpVersion = HttpVersion.Version11 和 HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrLower,这意味着只会应用 HTTP/1.1。

通过单个 HTTP/2 连贯多路复用 WebSocket 连贯和 HTTP 申请的能力是此性能的要害局部。为了使它按预期工作,您须要在调用 ConnectAsync 时从代码中传递并重用雷同的 HttpMessageInvoker 实例(例如 HttpClient),即应用 ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) 重载。这将重用 HttpMessageInvoker 实例中的连接池进行多路复用。

最初,感谢您成为 .NET 一员,如果您遇到问题或有任何反馈,都能够在文章上面留言,咱们将极力为您解决!

点我理解更多 .NET 7 性能改良~

正文完
 0