原文 | 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. sipp3.6分支压测方案

    概述 SIP压测工具sipp,免费,开源,功能足够强大,配置灵活,优点多. 本文档介绍sipp工具的常用参数和测试脚本. 环境 centos7.9 sipp v3.6.2_rc1 常用参数 -sf 加 ...

  2. 自动化测试复习巩固第一天,requests的用法

    如何快速发送post请求 因为我用的python语言,所以大家需要在本地安装python语言和pycharm,如何安装请自行查找教程,这里不做过多赘述 这里需要提前下载安装好需要的第三方库reques ...

  3. zzuli 1907: 小火山的宝藏收益

    ***题意:中文的 做法:邻接表+DFS,就相当于搜一棵树,比较一下当前结点得到的宝藏多还是子树下面得到的宝藏多,仔细想想就是水题*** #include<iostream> #inclu ...

  4. 机器学习-线性分类-SVM支持向量机算法-12

    目录 1. 铺垫 感知器算法模型 2. SVM 算法思想 3. 硬分割SVM总结 支持向量机(Support Vector Machine, SVM)本身是一个二元分类算法,是对感知器算法模型的一种扩 ...

  5. windows mysql安装及常用命令

    安装windows版本mysql只是为本地代码调试,不建议用于生产.觉得步骤麻烦也可以直接下载集成环境(如xampp),一键安装即可用.之前本地测试都用一键安装,今天换个方法玩玩,安装步骤如下: my ...

  6. 如何让golang的web服务热重载

    有很多方法可以热重载 golang Web 应用程序或 golang 程序. 我选择gin(不是web gin框架)来进行热重载. 首先在 GOPATH/bin下安装gin,命令如下所示: go ge ...

  7. Shell-基本

  8. Mygin实现简单的路由

    本文是Mygin第二篇 目的: 实现路由映射 提供了用户注册静态路由方法(GET.POST方法) 基于上一篇 Mygin 实现简单Http 且参照Gin 我使用了map数组实现简单路由的映射关系 不同 ...

  9. 使用 golang 开发 PHP 扩展

    使用 golang 开发 PHP 扩展 环境 golang go1.19.9 darwin/arm64 Macos/Linux PHP8.1.11 编译安装 实战 PHP脚手架生成 进入PHP源码,使 ...

  10. [转帖]两种Nginx日志切分方案,狼厂主要在用第1种

    两种Nginx日志切分方案,狼厂主要在用第1种 nginx的日志切分问题一直是运维nginx时需要重点关注的.本文将简单说明下nginx支持的两种日志切分方式. 一.定时任务切分 所谓的定时任务切分, ...