《ASP.NET Core 6框架揭秘》实例演示[17]:利用IHttpClientFactory工厂来创建HttpClient

在一个采用依赖注入框架的应用中,我们一般不太推荐利用手工创建的HttpClient对象来进行HTTP调用,使用的HttpClient对象最好利用注入的IHttpClientFactory工厂来创建。前者引起的问题,以及后者带来的好处,将通过如下这几个演示程序展现出来。IHttpClientFactory类型由“Microsoft.Extensions.Http”这个NuGet包提供,“Microsoft.NET.Sdk.Web”SDK具有该包的默认引用。如果采用“Microsoft.NET.Sdk”这个SDK,需要添加该包的引用。[本文节选《ASP.NET Core 6框架揭秘》第12章]

[S1201]频繁创建HttpClient对象调用API(源代码)
[S1202]以单例方式使用HttpClient(源代码)
[S1203]利用IHttpClientFactory工厂创建HttpClient对象(源代码)
[S1204]直接注入HttpClient对象(源代码)
[S1205]定制HttpClient对象(源代码)
[S1206]强类型客户端(源代码)
[S1207]基于Polly的失败重试(源代码)

[S1201]频繁创建HttpClient对象调用API

HttpClient类型实现了IDisposable接口,如果采用在每次调用时创建新的对象,那么按照我们理解的编程规范,调用结束之后就应该主动调用Dispose方法及时地将其释放。如下的演示程序就采用了这种编程方式,我们启动了一个ASP.NET应用,它提供了一个返回“Hello World”的终结点。

using System.Diagnostics;var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
await app.StartAsync();while (true)
{using (var httpClient = new HttpClient()){try{var reply = await httpClient.GetStringAsync("http://localhost:5000");Debug.Assert(reply == "Hello World!");}catch (Exception ex){Console.WriteLine(ex.Message);}}
}

ASP.NET应用启动之后,我们在一个无限循环中对它发起调用。每次迭代的创建的HttpClient对象会在完成调用之后被释放。当我们的程序运行之后,初始阶段都没有问题。当调用次数累积到一定规模之后,程序会大量地抛出HttpRequestExcetion异常,并提示“Only one usage of each socket address (protocol/network address/port) is normally permitted”。

a60d6bf51308d6b0708e579a2b66b2ee.png
图1 频繁创建HttpClient导致的异常

[S1202]以单例方式使用HttpClient

这个演示实例表明频繁创建HttpClient对象是不可取的。如果我们需要自行创建HttpClient对象并频繁地使用它们,应该尽可能地复用这个对象。如果将演示程序改写成如下的形式使用单例的HttpClient对象就不会抛出上面这个异常,但是这又会带来一些额外的问题。HttpRequestExcetion异常在前面的实例中为何会出现,后面的实例究竟又有哪些问题,我们将在后面回答这个问题。

using System.Diagnostics;
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
await app.StartAsync();var httpClient = new HttpClient();
while (true)
{try{var reply = await httpClient.GetStringAsync("http://localhost:5000");Debug.Assert(reply == "Hello World!");}catch (Exception ex){Console.WriteLine(ex.Message);}
}

[S1203]利用IHttpClientFactory工厂创建HttpClient对象

引入IHttpClientFactory工厂将会使一切变得简单,我们只需要在需要进行HTTP调用的时候利用这个工厂创建出对应的HttpClient对象就可以了。虽然HttpClient类型实现了IDisposable接口,我们在完成了调用之后根本不需要去调用它的Dispose方法。在下面的演示程序中,我们调用ServiceCollection对象的AddHttpClient扩展方法对IHttpClientFactory工厂进行了注册,并利用构建出来的IServiceProvider对象得到了这个对象。在每次进行HTTP调用的时候,我们利用这个IHttpClientFactory工厂实时地将HttpClient对象创建出来。

using System.Diagnostics;var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
await app.StartAsync();var httpClientFactory = new ServiceCollection().AddHttpClient().BuildServiceProvider().GetRequiredService<IHttpClientFactory>();while (true)
{try{var reply = await httpClientFactory.CreateClient().GetStringAsync("http://localhost:5000");Debug.Assert(reply == "Hello World!");}catch (Exception ex){Console.WriteLine(ex.Message);}
}

[S1204]直接注入HttpClient对象

上面介绍的CreateClient扩展方法还注册加了针对HttpClient类型的服务,所以HttpClient对象可以直接作为注入的服务来使用。在如下所示的演示程序中,我们直接利用IServiceProvider对象来创提供HttpClient对象,它与上面演示的程序是等效的(S1204)。

using System.Diagnostics;var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
await app.StartAsync();var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
while (true)
{try{var reply = await serviceProvider.GetRequiredService<HttpClient>().GetStringAsync("http://localhost:5000");Debug.Assert(reply == "Hello World!");}catch (Exception ex){Console.WriteLine(ex.Message);}
}

[S1205]定制HttpClient对象

调用IServiceCollection接口的AddHttpClient扩展方法进行服务注册的时候可以对HttpClient作相应的定制,比如可以设置超时时间、默认请求报头和网络代理等。如果应用会涉及针对众多不同类型API的调用,调用不同的API可能需要采用不同的设置,比如局域网内部调用就比外部调用需要更小的超时设置。为了解决这个问题,我们对提供的设置赋予一个唯一的名称,在使用的时候针对这个标识提取对应的设置来创建HttpClient对象,为了方便描述,我们将这个唯一标识HttpClient设置的名称就称为HttpClient的名称。在接下来演示的实例中,我们将设置两个HttpClient来调用指向“www.foo.com”和“www.bar.com”这两个域名的API。为此我们需要在host文件中添加了如下的映射关系

127.0.0.1 www.foo.com
127.0.0.1 www.bar.com

在如下所示的演示实例中,我们为ASP.NET应用注册的终结点会返回包含请求的域名和路径。我们调用IServiceCollection接口的AddHttpClient方法注册了两个名称分别为“foo”和“bar”的HttpClient,并对它们的基础地址进行针对性的设置(S1205)。

using System.Diagnostics;var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:80");
app.MapGet("/{path}" , (HttpRequest resquest, HttpResponse response)=>response.WriteAsync($"{resquest.Host}{resquest.Path}"));
await app.StartAsync();var services = new ServiceCollection();
services.AddHttpClient("foo", httpClient => httpClient.BaseAddress = new Uri("http://www.foo.com"));
services.AddHttpClient("bar", httpClient => httpClient.BaseAddress = new Uri("http://www.bar.com"));
var httpClientFactory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();var reply = await httpClientFactory.CreateClient("foo").GetStringAsync("abc");
Debug.Assert(reply == "www.foo.com/abc");
reply = await httpClientFactory.CreateClient("bar").GetStringAsync("xyz");
Debug.Assert(reply == "www.bar.com/xyz");

我们将HttpClient的注册名称作为参数调用IHttpClientFactory工厂的Create方法得到对应的HttpClient对象。由于基础地址已经设置好了,所以在进行HTTP调用时只需要指定相对地址(“abc”和“xyz”)就可以了。

[S1206]强类型客户端

所谓“强类型客户端”指的针对具体场景自定义的用于调用指定API的类型,强类型客户端直接使用注入的HttpClient进行HTTP调用。对于上一个实例的应用场景,我们就可以定义如下两个客户端类型FooClient和BarClient,并使用它们分别调用指向不同域名的API。如代码片段所示,我们直接在其构造函数中注入了HttpClient对象,并在GetStringAsync方法中使用它来完成最终的HTTP调用。

public class FooClient
{private readonly HttpClient _httpClient;public FooClient(HttpClient httpClient) => _httpClient = httpClient;public Task<string> GetStringAsync(string path) => _httpClient.GetStringAsync(path);
}public class BarClient
{private readonly HttpClient _httpClient;public BarClient(HttpClient httpClient) => _httpClient = httpClient;public Task<string> GetStringAsync(string path) => _httpClient.GetStringAsync(path);
}

由于FooClient和BarClient对使用的HttpClient具有不同的要求,所以我们采用如下的方式调用IServiceCollection接口的AddHttpClient<TClient>针对客户端类型对HttpClient进行针对设置,具体设置的依然是基础地址。由于AddHttpClient<TClient>扩展方法会将作为泛型参数的TClient类型注册为服务,所以我们可以直接利用IServiceProvider对象提取对应的客户端实例。

using App;
using System.Diagnostics;var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:80");
app.MapGet("/{path}", (HttpRequest resquest, HttpResponse response)=> response.WriteAsync($"{resquest.Host}{resquest.Path}"));
await app.StartAsync();var services = new ServiceCollection();
services.AddHttpClient<FooClient>("foo", httpClient=> httpClient.BaseAddress = new Uri("http://www.foo.com"));
services.AddHttpClient<BarClient>("bar", httpClient=> httpClient.BaseAddress = new Uri("http://www.bar.com"));
var serviceProvider = services.BuildServiceProvider();
var foo = serviceProvider.GetRequiredService<FooClient>();
var bar = serviceProvider.GetRequiredService<BarClient>();var reply = await foo.GetStringAsync("abc");
Debug.Assert(reply == "www.foo.com/abc");
reply = await bar.GetStringAsync("xyz");
Debug.Assert(reply == "www.bar.com/xyz");

[S1207]基于Polly的失败重试

在任何环境下都不可能确保次HTTP调用都能成功,所以在失败重试是很有必要的。失败重试是要讲究策略的,返回何种响应状态才需要重试?重试多少次?时间间隔多长?一提到策略化自动重试,大多数人会想到Polly这个开源框架,“Microsoft.Extensions.Http.Polly”这个NuGet包提供了IHttpClientFactory工厂和Polly的整合。在添加了这个包引用之后,我们将演示程序做了如下的修改。如代码片段所示,我们注册的终结点接收到的每三个请求只有一个会返回状态码为200的响应,其余两个响应码均为500。如果客户端能够确保失败后至少进行两次重试,那么就能保证客户端调用100%成功。

using Polly;
using Polly.Extensions.Http;
using System.Diagnostics;var app = WebApplication.Create(args);
var counter = 0;
app.MapGet("/", (HttpResponse response) => response.StatusCode = counter++ % 3 == 0 ? 200 : 500);
await app.StartAsync();var services = new ServiceCollection();
services.AddHttpClient(string.Empty).AddPolicyHandler(HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(2, _ => TimeSpan.FromSeconds(1)));
var httpClientFactory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();while (true)
{var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000");var response = await httpClientFactory.CreateClient().SendAsync(request);Debug.Assert(response.IsSuccessStatusCode);
}

如上面的代码片段所示,调用AddHttpClient扩展方法注册了一个默认匿名HttpClient(名称采用空字符串)之后,我们接着调用返回的IHttpClientBuilder对象的AddPolicyHandler扩展方法设置了失败重试策略。AddPolicyHandler方法的参数类型为IAsyncPolicy<HttpResponseMessage>的参数,我们利用HttpPolicyExtensions类型的HandleTransientHttpError静态方法创建一个用来处理偶发错误(比如HttpRequestException异常和5XX/408响应)的PolicyBuilder<HttpResponseMessage>对象。我们最终调用该对象的WaitAndRetryAsync方法返回所需的IAsyncPolicy<HttpResponseMessage>对象,并通过参数设置了重试次数(两次)和每次重试时间间隔(1秒)。

在利用代表依赖注入容器的IServiceProvider对象得到IHttpClientFactory之后,我们在一个无限循环中利用它创建的HttpClient对本地承载的API发起调用,虽然服务端每三次调用只有一次是成功的,但是2次重试足以确保最终的调用是成功的,我们提供的调试断言证实了这一点。

《ASP.NET Core 6框架揭秘》实例演示[01]:编程初体验
《ASP.NET Core 6框架揭秘》实例演示[02]:各种形式的API开发
《ASP.NET Core 6框架揭秘》实例演示[03]:Dapr初体验
《ASP.NET Core 6框架揭秘》实例演示[04]:自定义依赖注入框架
《ASP.NET Core 6框架揭秘》实例演示[05]:依赖注入基本编程模式
《ASP.NET Core 6框架揭秘》实例演示[06]:依赖注入框架设计细节
《ASP.NET Core 6框架揭秘》实例演示[07]:文件系统
《ASP.NET Core 6框架揭秘》实例演示[08]:配置的基本编程模式
《ASP.NET Core 6框架揭秘》实例演示[09]:将配置绑定为对象
《ASP.NET Core 6框架揭秘》实例演示[10]:Options基本编程模式
《ASP.NET Core 6框架揭秘》实例演示[11]:诊断跟踪的几种基本编程方式 
《ASP.NET Core 6框架揭秘》实例演示[13]:日志的基本编程模式
《ASP.NET Core 6框架揭秘》实例演示[14]:日志的进阶用法
《ASP.NET Core 6框架揭秘》实例演示[15]:针对控制台的日志输出
《ASP.NET Core 6框架揭秘》实例演示[16]:内存缓存与分布式缓存的使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283180.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hadoop部署方式-高可用集群部署(High Availability)

Hadoop部署方式-高可用集群部署(High Availability) 作者&#xff1a;尹正杰 版权声明&#xff1a;原创作品&#xff0c;谢绝转载&#xff01;否则将追究法律责任。 本篇博客的高可用集群是建立在完全分布式基础之上的&#xff0c;详情请参考&#xff1a;https://www.cnblogs.c…

sybase 连接mysql_如何连接到Sybase SQL Anywhere数据库

Sybase SQL Anywhere数据库具有许多非常有用的功能&#xff0c;与竞争对手相比&#xff0c;它具有很高的竞争力。首先&#xff0c;它允许您处理大量数据。其次&#xff0c;它具有很高的生产率&#xff0c;也就是说&#xff0c;可以快速提供大量数据。第三&#xff0c;它需要最少…

【汇编语言】DEBUG的使用

在masm for windows中&#xff0c;需要先生存exe文件&#xff0c;然后再点调试按钮。 常用的命令有&#xff1a; R命令&#xff1a;查看、改变CPU寄存器的内容&#xff1b;如果要修改某个寄存器的内容&#xff0c;可以在r的后面接上空格和寄存器名。如&#xff1a;-r ax&#x…

Leetcode 动态规划 Trapping Rain Water

本文为senlie原创。转载请保留此地址&#xff1a;http://blog.csdn.net/zhengsenlie Trapping Rain Water Total Accepted: 14568 Total Submissions: 50810My SubmissionsGiven n non-negative integers representing an elevation map where the width of each bar is 1, co…

白盒测试用例设计方法

一、白盒测试 根据软件产品的内部工作过程&#xff0c;在计算机上进行测试&#xff0c;以证实每种内部操作是否符合设计规格要求&#xff0c;所有内部成分是否已经过检查。这种测试方法就是白盒测试。白盒测试把测试对象看做一个打开的盒子&#xff0c;允许测试人员利用程序内部…

k8s 读书笔记 - 详解 Pod 调度(Ⅰ卷)

上一篇 《深入掌握 Pod》 文章我们介绍了 Pod 的知识点&#xff0c;接下来我们来继续学习 Pod 在 k8s 中的调度原理。在 k8s 平台上&#xff0c;通常情况下很少直接创建一个 Pod&#xff0c;大多情况下都是通过 Pod 的资源管理对象 来创建&#xff0c;例如&#xff1a;RC/RS、…

.NET MAUI实战 Navigation

1.概要用过WPF的小伙伴一般都用过Prism&#xff0c;Prism里面的导航概念在MAUI中也有类似的概念&#xff0c;在MAUI中是直接集成在框架中我们不需要安装任何其他的nuget包。直接使用Navigation对象即可,通常在移动平台中使用的更多&#xff0c;桌面程序中。我们先来看看微软官方…

python报错 scrolled: false_python 元组tuple - python基础入门(14)

文章首发微信公众号&#xff0c;微信搜索&#xff1a;猿说python在上一篇文章中我们讲解了关于python列表List的相关内容&#xff0c;今天给大家解释一下列表List的兄弟 – 元组&#xff0c;俗称: tuple.元组tuple和列表List类似&#xff0c;元组有如下特点&#xff1a;1.由一个…

开放原子开源峰会 - SmartIDE正式开源并发布v1.0版本丨IDCF

在上周刚刚结束的【2022开放原子全球开源峰会】上 SmartIDE作为正在进行开放原子基金会TOC审核的开源项目&#xff0c;在云原生论坛上向全球的开源开发者介绍了下一代云原生CloudIDE的全新使用体验&#xff0c;并且正式发布了 SmartIDE v1.0 版本。作者&#xff1a;徐磊文章首发…

修改docker的默认存储位置及镜像存储位置

2019独角兽企业重金招聘Python工程师标准>>> 方法一、软链接 默认情况下Docker的存放位置为&#xff1a;/var/lib/docker 可以通过下面命令查看具体位置&#xff1a; sudo docker info | grep "Docker Root Dir" 解决这个问题&#xff0c;最直接的方法当然…

微信小程序第三方服务公司有哪些

虽然微信小程序还没有正式推出&#xff0c;但围绕着微信小程序第三方服务公司之间的战争早已经开始。他们在小程序生成工具&#xff08;一键生成小程序&#xff0c;无需开发&#xff09;、微信小程序开发工具、小程序数据统计等领域展开激烈竞争&#xff0c;我们一起来看看微信…

Blazor University (49)依赖注入 —— 比较依赖范围

原文链接&#xff1a;https://blazor-university.com/dependency-injection/dependency-lifetimes-and-scopes/comparing-dependency-scopes/比较依赖范围源代码[1]在本节中&#xff0c;我们将创建一个 Blazor 应用程序来演示各种依赖注入作用域的不同生命周期。为此&#xff0…

设计模式概论

此文转载于 http://blog.csdn.net/hguisu/article/details/74968191. 设计模式设计模式&#xff08;Design pattern&#xff09;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性…

只需要2个工具,百度云盘大文件就能用迅雷和IDM下载

不会代码&#xff0c;不懂脚本&#xff0c;没关系 &#xff0c;能找到一座通往它们的桥梁&#xff0c;照样能到达彼岸。 这里以360极速浏览器为例。 在浏览器地址框输入以下地址直接到达浏览器安装扩展插件的地方&#xff08;偷个懒&#xff0c;复制网址吧&#xff09;&#xf…

rsync服务器的配置

一、rsync 简介Rsync&#xff08;remote synchronize&#xff09;是一个远程数据同步工具&#xff0c;可通过LAN/WAN快速同步多台主机间的文件&#xff0c;也可以使用 Rsync 同步本地硬盘中的不同目录。 Rsync 是用于取代rcp的一个工具&#xff0c;Rsync使用所谓的 “Rsync 算法…

Vue学习笔记入门篇——数据及DOM

本文为转载&#xff0c;原文&#xff1a;Vue学习笔记入门篇——数据及DOM 数据 data 类型 Object | Function 详细 Vue 实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter&#xff0c;从而让 data 的属性能够响应数据变化。对象必须是纯粹的对象(含有零个或多个…

BZOJ 3144 [Hnoi2013]切糕

3144: [Hnoi2013]切糕 Description Input 第一行是三个正整数P,Q,R&#xff0c;表示切糕的长P、 宽Q、高R。第二行有一个非负整数D&#xff0c;表示光滑性要求。接下来是R个P行Q列的矩阵&#xff0c;第z个 矩阵的第x行第y列是v(x,y,z) (1≤x≤P, 1≤y≤Q, 1≤z≤R)。 100%的数据…

《ASP.NET Core 6框架揭秘》实例演示[18]:HttpClient处理管道

在《《ASP.NET Core 6框架揭秘》实例演示[17]&#xff1a;利用IHttpClientFactory工厂来创建HttpClient》之后&#xff0c;我们将关注点放到HttpClient对象上。我们知道ASP.NET的核心就是由中间件组成的请求处理管道&#xff0c;HttpClient也采用了类似的设计。HttpClient管道由…

雅诗兰黛天猫超级品牌日:未央唇膏、红装小棕瓶“当红不让”

随着年末圣诞季的临近&#xff0c;各大美妆品牌陆续推出了圣诞套装&#xff0c;红红火火的超豪华套装&#xff0c;算是对用户最实在的回馈。高端美妆品牌的“领头羊”雅诗兰黛&#xff0c;当然也“当红不让”&#xff0c;趁着圣诞季&#xff0c;与天猫超级品牌日联手打造了一场…

JAVA常见算法题(三十一)---冒泡排序

package com.jege.spring.boot.hello.world;/*** java算法之冒泡排序<br>* 将数组按照从大到小的顺序排列<br>* * * author Administrator**/ public class BubbleSort{public static void main(String[] args){int score[] {67, 69, 75, 87, 89, 90, 99, 100};fo…