原文 | Máňa,Natalia Kondratyeva

翻译 | 郑子铭

随着新的 .NET 版本的发布,发布有关网络空间中新的有趣变化的博客文章已成为一种传统。今年,我们希望引入 HTTP 空间的变化、新添加的指标、新的 HttpClientFactory API 等。

HTTP协议

指标

.NET 8 使用 .NET 6 中引入的 System.Diagnostics.Metrics API 将内置 HTTP 指标添加到 ASP.NET Core 和 HttpClient。指标 API 和新内置指标的语义都是在与 OpenTelemetry 密切合作,确保新指标符合标准,并与 PrometheusGrafana 等流行工具良好配合。

System.Diagnostics.Metrics API 引入了许多 EventCounters 所缺少的新功能。新的内置指标广泛利用了这些功能,从而通过更简单、更优雅的仪器集实现了更广泛的功能。举几个例子:

  • 直方图使我们能够报告持续时间,例如。请求持续时间 (http.client.request.duration) 或连接持续时间 (http.client.connection.duration)。这些是没有 EventCounter 对应项的新指标。
  • 多维性允许我们将标签(又名属性或标签)附加到测量值上,这意味着我们可以报告诸如 server.address (标识 URI 来源)或 error.type (描述请求失败时的错误原因)之类的信息测量值。多维还可以实现简化:报告打开的 HTTP 连接数 SocketsHttpHandler 使用 3 个 EventCounters:http11-connections-current-total、http20-connections-current-total 和 http30-connections-current-total,而 Metrics 相当于这些counters 是一个单一的工具,http.client.open_connections,其中 HTTP 版本是使用 network.protocol.version 标签报告的。
  • 为了帮助内置标签不足以对传出 HTTP 请求进行分类的用例,http.client.request.duration 指标支持注入用户定义的标签。这称为浓缩
  • IMeterFactory 集成可以隔离用于发出 HTTP 指标的 Meter 实例,从而更轻松地编写针对内置测量运行验证的测试,并实现此类测试的并行执行。
  • 虽然这并不是特定于内置网络指标,但值得一提的是 System.Diagnostics.Metrics 中的集合 API 也更高级:它们是强类型的且性能更高,并允许多个同时侦听器和侦听器访问未聚合的测量结果。

这些优势共同带来了更好、更丰富的指标,这些指标可以通过 Prometheus 等第三方工具更有效地收集。得益于 PromQL(Prometheus 查询语言)的灵活性,它允许针对从 .NET 网络堆栈收集的多维指标创建复杂的查询,用户现在可以深入了解 HttpClient 和 SocketsHttpHandler 实例的状态和运行状况,其级别如下:以前是不可能的。

不利的一面是,我们应该提到,.NET 8 中只有 System.Net.Http 和 System.Net.NameResolution 组件使用 System.Diagnostics.Metrics 进行检测,这意味着您仍然需要使用 EventCounters 从较低层提取计数器堆栈的级别,例如 System.Net.Sockets。虽然仍然支持以前版本中存在的所有内置 EventCounters,但 .NET 团队预计不会对 EventCounters 进行大量新投资,并且在未来版本中将使用 System.Diagnostics.Metrics 添加新的内置检测。

有关使用内置 HTTP 指标的更多信息,请阅读我们有关 .NET 中的网络指标的教程。它包括有关使用 Prometheus 和 Grafana 进行收集和报告的示例,还演示了如何丰富和测试内置 HTTP 指标。有关内置工具的完整列表,请参阅 System.Net 指标的文档。如果您对服务器端更感兴趣,请阅读有关 ASP.NET Core 指标的文档。

扩展遥测

除了新指标之外,.NET 5 中引入的现有基于 EventSource 的遥测事件还增加了有关 HTTP 连接的更多信息 (dotnet/runtime#88853):

- ConnectionEstablished(byte versionMajor, byte versionMinor)
+ ConnectionEstablished(byte versionMajor, byte versionMinor, long connectionId, string scheme, string host, int port, string? remoteAddress) - ConnectionClosed(byte versionMajor, byte versionMinor)
+ ConnectionClosed(byte versionMajor, byte versionMinor, long connectionId) - RequestHeadersStart()
+ RequestHeadersStart(long connectionId)

现在,当建立新连接时,该事件会记录其连接 ID 及其方案、端口和对等 IP 地址。这使得可以通过 RequestHeadersStart 事件将请求和响应与连接关联起来(当请求关联到池连接并开始处理时发生),该事件还记录关联的 ConnectionId。这在用户想要查看为其 HTTP 请求提供服务的服务器的 IP 地址的诊断场景中尤其有价值,这是添加项背后的主要动机 (dotnet/runtime#63159)。

事件可以通过多种方式使用,请参阅.NET 中的网络遥测 – 事件。但为了进程内增强日志记录,可以使用自定义 EventListener 将请求/响应对与连接数据相关联:

using IPLoggingListener ipLoggingListener = new();
using HttpClient client = new(); // Send requests in parallel.
await Parallel.ForAsync(0, 1000, async (i, ct) =>
{
// Initialize the async local so that it can be populated by "RequestHeadersStart" event handler.
RequestInfo info = RequestInfo.Current;
using var response = await client.GetAsync("https://testserver");
Console.WriteLine($"Response {response.StatusCode} handled by connection {info.ConnectionId}. Remote IP: {info.RemoteAddress}"); // Process response...
}); internal sealed class RequestInfo
{
private static readonly AsyncLocal<RequestInfo> _asyncLocal = new();
public static RequestInfo Current => _asyncLocal.Value ??= new(); public string? RemoteAddress;
public long ConnectionId;
} internal sealed class IPLoggingListener : EventListener
{
private static readonly ConcurrentDictionary<long, string> s_connection2Endpoint = new ConcurrentDictionary<long, string>(); // EventId corresponds to [Event(eventId)] attribute argument and the payload indices correspond to the event method argument order. // See: https://github.com/dotnet/runtime/blob/a6e4834d53ac591a4b3d4a213a8928ad685f7ad8/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs#L100-L101
private const int ConnectionEstablished_EventId = 4;
private const int ConnectionEstablished_ConnectionIdIndex = 2;
private const int ConnectionEstablished_RemoteAddressIndex = 6; // See: https://github.com/dotnet/runtime/blob/a6e4834d53ac591a4b3d4a213a8928ad685f7ad8/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs#L106-L107
private const int ConnectionClosed_EventId = 5;
private const int ConnectionClosed_ConnectionIdIndex = 2; // See: https://github.com/dotnet/runtime/blob/a6e4834d53ac591a4b3d4a213a8928ad685f7ad8/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs#L118-L119
private const int RequestHeadersStart_EventId = 7;
private const int RequestHeadersStart_ConnectionIdIndex = 0; protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name == "System.Net.Http")
{
EnableEvents(eventSource, EventLevel.LogAlways);
}
} protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
ReadOnlyCollection<object?>? payload = eventData.Payload;
if (payload == null) return; switch (eventData.EventId)
{
case ConnectionEstablished_EventId:
// Remember the connection data.
long connectionId = (long)payload[ConnectionEstablished_ConnectionIdIndex]!;
string? remoteAddress = (string?)payload[ConnectionEstablished_RemoteAddressIndex];
if (remoteAddress != null)
{
Console.WriteLine($"Connection {connectionId} established to {remoteAddress}");
s_connection2Endpoint.TryAdd(connectionId, remoteAddress);
}
break;
case ConnectionClosed_EventId:
connectionId = (long)payload[ConnectionClosed_ConnectionIdIndex]!;
s_connection2Endpoint.TryRemove(connectionId, out _);
break;
case RequestHeadersStart_EventId:
// Populate the async local RequestInfo with data from "ConnectionEstablished" event.
connectionId = (long)payload[RequestHeadersStart_ConnectionIdIndex]!;
if (s_connection2Endpoint.TryGetValue(connectionId, out remoteAddress))
{
RequestInfo.Current.RemoteAddress = remoteAddress;
RequestInfo.Current.ConnectionId = connectionId;
}
break;
}
}
}

此外,重定向事件已扩展为包含重定向 URI:

-void Redirect();
+void Redirect(string redirectUri);

HTTP 错误代码

HttpClient 的诊断问题之一是,在发生异常时,不容易以编程方式区分错误的确切根本原因。区分其中许多的唯一方法是解析来自 HttpRequestException 的异常消息。此外,其他 HTTP 实现(例如带有 ERROR_WINHTTP_* 错误代码的 WinHTTP)以数字代码或枚举的形式提供此类功能。因此.NET 8引入了类似的枚举,并在HTTP处理抛出的异常中提供它,它们是:

dotnet/runtime#76644 API 提案中描述了 HttpRequestError 枚举的设计以及如何将其插入 HTTP 异常。

现在,HttpClient 方法的使用者可以更轻松、更可靠地处理特定的内部错误:

using HttpClient httpClient = new();

// Handling problems with the server:
try
{
using HttpResponseMessage response = await httpClient.GetAsync("https://testserver", HttpCompletionOption.ResponseHeadersRead);
using Stream responseStream = await response.Content.ReadAsStreamAsync();
// Process responseStream ...
}
catch (HttpRequestException e) when (e.HttpRequestError == HttpRequestError.NameResolutionError)
{
Console.WriteLine($"Unknown host: {e}");
// --> Try different hostname.
}
catch (HttpRequestException e) when (e.HttpRequestError == HttpRequestError.ConnectionError)
{
Console.WriteLine($"Server unreachable: {e}");
// --> Try different server.
}
catch (HttpIOException e) when (e.HttpRequestError == HttpRequestError.InvalidResponse)
{
Console.WriteLine($"Mangled responses: {e}");
// --> Block list server.
} // Handling problems with HTTP version selection:
try
{
using HttpResponseMessage response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://testserver")
{
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionExact
}, HttpCompletionOption.ResponseHeadersRead);
using Stream responseStream = await response.Content.ReadAsStreamAsync();
// Process responseStream ...
}
catch (HttpRequestException e) when (e.HttpRequestError == HttpRequestError.VersionNegotiationError)
{
Console.WriteLine($"HTTP version is not supported: {e}");
// Try with different HTTP version.
}

HTTPS 代理支持

此版本实现的最受欢迎的功能之一是支持 HTTPS 代理 (dotnet/runtime#31113)。现在可以使用代理通过 HTTPS 处理请求,这意味着与代理的连接是安全的。这并没有说明来自代理的请求本身,它仍然可以是 HTTP 或 HTTPS。如果是纯文本 HTTP 请求,与 HTTPS 代理的连接是安全的(通过 HTTPS),然后是从代理到目标的纯文本请求。如果是 HTTPS 请求(代理隧道),打开隧道的初始 CONNECT 请求将通过安全通道 (HTTPS) 发送到代理,然后通过隧道将 HTTPS 请求从代理发送到目的地。

要利用该功能,只需在设置代理时使用 HTTPS 方案即可:

using HttpClient client = new HttpClient(new SocketsHttpHandler()
{
Proxy = new WebProxy("https://proxy.address:12345")
}); using HttpResponseMessage response = await client.GetAsync("https://httpbin.org/");

HttpClientFactory

.NET 8 扩展了配置 HttpClientFactory 的方式,包括客户端默认设置、自定义日志记录和简化的 SocketsHttpHandler 配置。这些 API 在 Microsoft.Extensions.Http 包中实现,该包可在 NuGet 上获取,并包含对 .NET Standard 2.0 的支持。因此,此功能不仅适用于 .NET 8 上的客户,而且适用于所有版本的 .NET,包括 .NET Framework(唯一的例外是仅适用于 .NET 5+ 的 SocketsHttpHandler 相关 API)。

为所有客户端设置默认值

.NET 8 添加了设置默认配置的功能,该配置将用于 HttpClientFactory (dotnet/runtime#87914) 创建的所有 HttpClient。当所有或大多数注册客户端包含相同的配置子集时,这非常有用。

考虑一个定义了两个命名客户端的示例,并且它们的消息处理程序链中都需要 MyAuthHandler。

services.AddHttpClient("consoto", c => c.BaseAddress = new Uri("https://consoto.com/"))
.AddHttpMessageHandler<MyAuthHandler>(); services.AddHttpClient("github", c => c.BaseAddress = new Uri("https://github.com/"))
.AddHttpMessageHandler<MyAuthHandler>();

要提取公共部分,您现在可以使用ConfigureHttpClientDefaults方法:

services.ConfigureHttpClientDefaults(b => b.AddHttpMessageHandler<MyAuthHandler>());

// both clients will have MyAuthHandler added by default
services.AddHttpClient("consoto", c => c.BaseAddress = new Uri("https://consoto.com/"));
services.AddHttpClient("github", c => c.BaseAddress = new Uri("https://github.com/"));

与 AddHttpClient 一起使用的所有 IHttpClientBuilder 扩展方法也可以在 ConfigureHttpClientDefaults 中使用。

默认配置 (ConfigureHttpClientDefaults) 在客户端特定 (AddHttpClient) 配置之前应用于所有客户端;他们在注册中的相对位置并不重要。配置HttpClientDefaults可以注册多次,在这种情况下,配置将按照注册的顺序一一应用。配置的任何部分都可以在特定于客户端的配置中覆盖或修改,例如,您可以为 HttpClient 对象或主处理程序设置其他设置,删除以前添加的其他处理程序等。

请注意,从 8.0 开始,ConfigureHttpMessageHandlerBuilder 方法已被弃用。您应该改用[ConfigurePrimaryHttpMessageHandler](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.httpclientbuilderextensions.configureprimaryhttpmessagehandler?view=dotnet-plat-ext-8.0#microsoft-extensions-dependencyinjection-httpclientbuilderextensions-configureprimaryhttpmessagehandler(microsoft-extensions-dependencyinjection-ihttpclientbuilder-system-action((system-net-http-httpmessagehandler-system-iserviceprovider))(Action<HttpMessageHandler,IServiceProvider>) 或ConfigureAdditionalHttpMessageHandlers 方法来分别修改先前配置的主处理程序或附加处理程序列表。

// by default, adds User-Agent header, uses HttpClientHandler with UseCookies=false
// as a primary handler, and adds MyAuthHandler to all clients
services.ConfigureHttpClientDefaults(b =>
b.ConfigureHttpClient(c => c.DefaultRequestHeaders.UserAgent.ParseAdd("HttpClient/8.0"))
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { UseCookies = false })
.AddHttpMessageHandler<MyAuthHandler>()); // HttpClient will have both User-Agent (from defaults) and BaseAddress set
// + client will have UseCookies=false and MyAuthHandler from defaults
services.AddHttpClient("modify-http-client", c => c.BaseAddress = new Uri("https://httpbin.org/")) // primary handler will have both UseCookies=false (from defaults) and MaxConnectionsPerServer set
// + client will have User-Agent and MyAuthHandler from defaults
services.AddHttpClient("modify-primary-handler")
.ConfigurePrimaryHandler((h, _) => ((HttpClientHandler)h).MaxConnectionsPerServer = 1); // MyWrappingHandler will be inserted at the top of the handlers chain
// + client will have User-Agent, UseCookies=false and MyAuthHandler from defaults
services.AddHttpClient("insert-handler-into-chain"))
.ConfigureAdditionalHttpMessageHandlers((handlers, _) =>
handlers.Insert(0, new MyWrappingHandler()); // MyAuthHandler (initially from defaults) will be removed from the handler chain
// + client will still have User-Agent and UseCookies=false from defaults
services.AddHttpClient("remove-handler-from-chain"))
.ConfigureAdditionalHttpMessageHandlers((handlers, _) =>
handlers.Remove(handlers.Single(h => h is MyAuthHandler)));

原文链接

.NET 8 Networking Improvements

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com)

【译】.NET 8 网络改进(一)的更多相关文章

  1. 【译】.NET 6 网络改进

    原文 | Máňa Píchová 翻译 | 郑子铭 对于 .NET 的每个新版本,我们都希望发布一篇博客文章,重点介绍网络的一些变化和改进.在这篇文章中,我很高兴谈论 .NET 6 中的变化. 这篇 ...

  2. [译] 理解 LSTM 网络

    原文链接:http://colah.github.io/posts/2015-08-Understanding-LSTMs/ 吴恩达版:http://www.ai-start.com/dl2017/h ...

  3. Kubernetes 网络改进的三项实践分享

    自研CNI IPAM插件 解决K8s功能问题 首先,在功能方面,Kubernetes 网络模型由于IP不固定,无法对IP资源进行精细管控,无法使用基于IP的监控和基于IP的安全策略,此外,一些IP发现 ...

  4. (译)理解 LSTM 网络 (Understanding LSTM Networks by colah)

    @翻译:huangyongye 原文链接: Understanding LSTM Networks 前言:其实之前就已经用过 LSTM 了,是在深度学习框架 keras 上直接用的,但是到现在对LST ...

  5. 学习笔记TF051:生成式对抗网络

    生成式对抗网络(gennerative adversarial network,GAN),谷歌2014年提出网络模型.灵感自二人博弈的零和博弈,目前最火的非监督深度学习.GAN之父,Ian J.Goo ...

  6. 【RL-TCPnet网络教程】第18章 BSD Sockets基础知识

    第18章      BSD Sockets基础知识 本章节为大家讲解BSD Sockets,需要大家对BSD Sockets有个基础的认识,方便后面章节Socket实战操作. (本章的知识点主要整理自 ...

  7. 第三节,目标检测---R-CNN网络系列

    1.目标检测 检测图片中所有物体的 类别标签 位置(最小外接矩形/Bounding box) 区域卷积神经网络R-CNN 模块进化史 2.区域卷积神经网络R-CNN Region proposals+ ...

  8. TOP100summit【分享实录-网易】构建云直播分发网络

    本篇文章内容来自2016年TOP100summit网易视频云.网易杭州研究院服务端技术专家邵峰的案例分享.编辑:Cynthia 邵峰:网易视频云.网易杭州研究院服务端技术专家浙江大学计算机专业博士毕业 ...

  9. [转] 理解 LSTM 网络

    [译] 理解 LSTM 网络 http://www.jianshu.com/p/9dc9f41f0b29 Recurrent Neural Networks 人类并不是每时每刻都从一片空白的大脑开始他 ...

  10. 【超分辨率】—(ESRGAN)增强型超分辨率生成对抗网络-解读与实现

    一.文献解读 我们知道GAN 在图像修复时更容易得到符合视觉上效果更好的图像,今天要介绍的这篇文章——ESRGAN: Enhanced Super-Resolution Generative Adve ...

随机推荐

  1. apache-jmeter-5.6.3版本报错:errorlevel=1的解决办法

    一.背景: 今天遇到了apache-jmeter-5.6.3版本,下载解决后,打开bin下的:jmeter.bat报错 二.解决方法:  尝试解决了jmeter.bat的内存占用还是没有解决 最终发现 ...

  2. xshell配置隧道转移规则

    钢铁知识库,一个学习python爬虫.数据分析的知识库.人生苦短,快用python. xshell是什么 通俗点说就是一款强大ssh远程软件,可以方便运维人员对服务器进行管理操作,功能很多朋友们自行探 ...

  3. AvaloniaUI 取消标题栏,无边框无最大最小化,

    AvaloniaUI 取消标题栏,无边框无最大最小化, 创建一个Window控件 并且在Window中添加以下代码 ExtendClientAreaToDecorationsHint="Tr ...

  4. 函数指针、std::function、std::bind

    函数指针.std::function.std::bind 函数指针: C++语法中可以直接将函数名作为指针, void fun(int a, int b); 在这个函数声明中,函数指针即为fun,传入 ...

  5. 第二章 VB.NET 绘图基础

    GDI+( Graphics Device Interface Plus)是 Windows操作系统用来执行绘画及其他相关图形操作的一套子系统,是由. Net Framework中的System.Dr ...

  6. Java中有哪些方式能实现锁某个变量

    有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 系列文章地址 在Java中,有几种方式可以实现对某个变量的锁定 ...

  7. [转帖]3--二进制安装k8s

    https://www.cnblogs.com/caodan01/p/15104491.html 目录 一.节点规划 二.插件规划 三.系统优化(所有master节点) 1.关闭swap分区 2.关闭 ...

  8. 鲲鹏920上面 Docker 部署 clickhouse 的方式方法

    鲲鹏920上面 Docker 部署 clickhouse 的方式方法 背景 最近有一套鲲鹏920的测试环境, 研发同事想纯Dcoker部署一套环境. 其中就包括了 Clickhouse 之前发现Cli ...

  9. Python处理Oracle数据库的学习过程

    Python处理Oracle数据库的学习过程 背景 产品数据存在一些大小写敏感的数据迁移到不敏感的数据库时出现报错的情况. 基于此, 我这边跟帅男同学学习了下Python的使用. 因为这一块一直比较菜 ...

  10. ESXi 虚拟机性能情况简单验证

    1.虚拟化的CPU超售问题. 经过查找资料, 发现 ESXi 5.5 的版本 一个 物理CPU的Core 可以虚拟出 25个vCPU, 升级到ESXi6.0 之后可以虚拟化32个vCPU. 所以虚拟化 ...