一. 背景

1.前世

  提到HttpClient,在传统的.Net版本中简直臭名昭著,因为我们安装官方用法 using(var httpClient = new HttpClient()),当然可以Dispose,但是在高并发的情况下,连接来不及释放,socket被耗尽,然后就会出现一个喜闻乐见的错误:即各种套接字的问题。

Unable to connect to the remote serverSystem.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.

PS:当然我们可以通过修改注册表的默认值,来人为的减少超时时间,但可能会引起其他莫名其妙的问题:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay])

2.我们之前的解决方案

  把HttpClient做成全局单例的,通过共享一个实例,减少了套接字的浪费,实际上由于套接字重用而传输快一点。

关于单例的封装,详见这篇: https://www.cnblogs.com/yaopengfei/p/10301779.html

  封装成单例的也会有些不灵活的地方:

(1).因为是复用的 HttpClient,那么一些公共的设置就没办法灵活的调整了,如请求头的自定义。

(2).因为 HttpClient 请求每个 url 时,会缓存该url对应的主机 ip,从而会导致 DNS 更新失效(TTL 失效了)。

3.HttpClientFactiory应运而生

  HttpClientFactory 是 ASP.NET CORE 2.1 中新增加的功能。顾名思义 HttpClientFactory 就是 HttpClient 的工厂,内部已经帮我们处理好了对 HttpClient 的管理,不需要我们人工进行对象释放,同时,支持自定义请求头,支持 DNS 更新。

分析:

  HttpClient 继承自 HttpMessageInvoker,而 HttpMessageInvoker 实质就是HttpClientHandler。HttpClientFactory 创建的 HttpClient,也即是 HttpClientHandler,只是这些个 HttpClient 被放到了“池子”中,工厂每次在 create 的时候会自动判断是新建还是复用。(默认生命周期为 2min)。简单的理解成 Task 和 Thread 的关系。

4. 补充请求方式的说明

  其中Post请求有两种,分别是: "application/x-www-form-urlencoded"表单提交的方式 和 "application/json" Json格式提交的方式。

(1). Post的表单提交的格式为:"userName=admin&pwd=123456"。

(2). Post的Json的提交格式为:将实体(类)转换成json字符串。

二. 几种用法

用到的服务器端的方法:

    /// <summary>
/// 充当服务端接口
/// </summary>
public class ServerController : Controller
{
[HttpGet]
public string CheckLogin(string userName, string pwd)
{
if (userName == "admin" && pwd == "")
{
return "ok";
}
else
{
return "error";
}
} [HttpPost]
public string Register1(string userName, string pwd)
{
if (userName == "admin" && pwd == "")
{
return "ok";
}
else
{
return "error";
}
} [HttpPost]
public string Register2([FromBody]UserInfor model)
{
if (model.userName == "admin" && model.pwd == "")
{
return "ok";
}
else
{
return "error";
}
} }

1. 基本用法

A.步骤

  (1).在ConfigService方法中注册服务:services.AddHttpClient();

  (2).通过构造函数全局注入IHttpClientFactory对象,或者通过[FromServices]给某个方法注入。

  (3).利用SendAsync方法和HttpRequestMessage对象(可以配置请求方式、表头、请求内容)进行各种请求的异步和同步发送

  (PS:下面无论哪种用法,这里都通过SendAsync和HttpRequestMessage进行演示)

B.适用场景

   以这种方式使用 IHttpClientFactory 适合重构现有应用。 这不会影响 HttpClient 的使用方式。 在当前创建HttpClient 实例的位置, 使用对 CreateClient 的调用替换这些匹配项。

特别补充:利用GetAsync和PostAsync方法来发送请求的方式,详见下面代码。

2. 命名客户端

A.步骤

  (1).在ConfigService方法中注册服务:services.AddHttpClient("client1",c=>{ 相关默认配置 });

  (2).通过构造函数全局注入IHttpClientFactory对象,或者通过[FromServices]给某个方法注入

  (3).创建Client对象的时候指定命名 CreateClient("client1");其它用法都相同了。

B.适用场景

  应用需要有许多不同的 HttpClient 用法(每种用法的配置都不同),可以视情况使用命名客户端。可以在 HttpClient 中注册时指定命名 Startup.ConfigureServices 的配置请求地址的公共部分,表头等。

3. 类型化客户端

A.本质

  新建一个类,在类里注入HttpClient对象,在构造函数中进行配置,或者在Startup中进行配置,然后把请求相关的业务封装到方法中,外层直接调用方法即可。

B.步骤

  (1).新建UserService类,通过构造函数注入HttpClient对象,然后将请求相关的配置封装在一个方法Login中。

  (2).在ConfigService方法中注册服务:services.AddHttpClient<UserService>();

  (3).通过构造函数全局注入UserService对象,或者通过[FromServices]给某个方法注入。

  (4).调用对应的方法即可。

C.适用场景

  根据个人喜好选择即可

分享上述全部代码:

    public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
}); //下面是注册HttpClient服务
//1. 基本用法
services.AddHttpClient();
//2. 命名客户端
services.AddHttpClient("client1", c =>
{
c.BaseAddress = new Uri("http://localhost:15319/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
//3. 类型化客户端
services.AddHttpClient<UserService>();
//可以根据喜好在注册服务的时候配置,就不需要在UserService构造函数中配置了
//services.AddHttpClient<UserService>(c =>
//{
// c.BaseAddress = new Uri("http://localhost:15319/");
// c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
//});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

ConfigureServices

  public class HomeController : Controller
{
private readonly IHttpClientFactory _clientFactory;
public HomeController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
} public async Task<IActionResult> Index([FromServices] UserService userService)
{
string url1 = "http://localhost:15319/Server/CheckLogin?userName=admin&pwd=123456";
string url2 = "http://localhost:15319/Server/Register1";
string url3 = "http://localhost:15319/Server/Register2"; string url4 = "Server/CheckLogin?userName=admin&pwd=123456"; /*********************************************一.基本用法*********************************************/ #region 01-基本用法(Get请求)
{
var request = new HttpRequestMessage(HttpMethod.Get, url1);
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
string result = "";
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadAsStringAsync();
}
ViewBag.result = result; }
#endregion #region 02-基本用法(Post请求-表单提交)
{
var request = new HttpRequestMessage(HttpMethod.Post, url2);
//表头的处理
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
//内容的处理
request.Content = new StringContent("userName=admin&pwd=123456", Encoding.UTF8, "application/x-www-form-urlencoded");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
string result = "";
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadAsStringAsync();
}
}
#endregion #region 03-基本用法(Post请求-JSON格式提交)
{
var request = new HttpRequestMessage(HttpMethod.Post, url3);
//表头的处理
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
//内容的处理
var user = new
{
userName = "admin",
pwd = ""
};
request.Content = new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
string result = "";
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadAsStringAsync();
}
}
#endregion #region 04-补充其他写法
{
//上面的三个方法都是利用SendAsync方法配合HttpRequestMessage类来发送Get和两种post请求的,所有的参数设置都是基于HttpRequestMessage对象。
//在这里再次补充一下直接利用 GetAsync 和 PostAsync 方法直接来发送Get和post请求(直接.Result不异步了) //1. Get请求
{
var client = _clientFactory.CreateClient();
//配置表头
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
var response = client.GetAsync(url1).Result;
string result = "";
if (response.IsSuccessStatusCode)
{
result = response.Content.ReadAsStringAsync().Result;
}
}
//2. Post请求-表单提交
{
var client = _clientFactory.CreateClient();
//配置表头
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
//配置请求内容
var content = new StringContent("userName=admin&pwd=123456", Encoding.UTF8, "application/x-www-form-urlencoded");
var response = client.PostAsync(url2, content).Result;
string result = "";
if (response.IsSuccessStatusCode)
{
result = response.Content.ReadAsStringAsync().Result;
} } //3. Post请求-JSON提交
{
var client = _clientFactory.CreateClient();
//配置表头
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
//配置请求内容
var user = new
{
userName = "admin",
pwd = ""
};
var content = new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json");
var response = client.PostAsync(url3, content).Result;
string result = "";
if (response.IsSuccessStatusCode)
{
result = response.Content.ReadAsStringAsync().Result;
}
} }
#endregion /*********************************************二.命名客户端*********************************************/ #region 命名客户端(Get请求)
{
var request = new HttpRequestMessage(HttpMethod.Get, url4);
//配置调用的名称
var client = _clientFactory.CreateClient("client1");
var response = await client.SendAsync(request);
string result = "";
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadAsStringAsync();
}
}
#endregion /*********************************************三.类型化客户端*********************************************/ #region 类型化客户端(Get请求)
{
string result = await userService.Login(url4);
}
#endregion return View();
}
}
  /// <summary>
/// 类型化客户端
/// </summary>
public class UserService
{
public HttpClient _client;
public UserService(HttpClient client)
{
client.BaseAddress = new Uri("http://localhost:15319/");
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
_client = client;
} public async Task<string> Login(string urlContent)
{
var response = await _client.GetAsync(urlContent);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
return result;
}
}

类型化客户端-UserService

4. 总结

  它们之间不存在严格的优先级。 最佳方法取决于应用的约束条件。

三. 与Refit结合

不做深入研究,可参考:

   https://www.xcode.me/code/refit-the-automatic-type-safe-rest-library
   https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.1#outgoing-request-middleware

四. 结合框架进行封装

  封装思路:将内容和请求方式等一系列操作封装到方法里,如果需要配置表头,建议采用命名客户端的方式在ConfigureService中进行配置, 需要事先注册好服务,然后把实例化好的IHttpClientFactory对象传入到方法里。

  封装了三个方法,分别来处理:Get、两种Post请求

  需要用到:【Microsoft.Extensions.Http】程序集,Core MVC中已经默认引入了。

代码分享:

     /// <summary>
/// 基于HttpClientFactory的请求封装
/// </summary>
public class RequestHelp
{
/// <summary>
/// Get请求
/// </summary>
/// <param name="clientFactory">实例化好的HttpClientFactory对象</param>
/// <param name="url">请求地址</param>
/// <param name="clientName">注册名称,默认不指定</param>
/// <returns></returns>
public static string MyGetRequest(IHttpClientFactory clientFactory, string url, string clientName = "")
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
var client = clientName == "" ? clientFactory.CreateClient() : clientFactory.CreateClient(clientName);
var response = client.SendAsync(request).Result;
var myResult = response.Content.ReadAsStringAsync().Result;
return myResult;
} /// <summary>
/// Post请求-表单形式
/// </summary>
/// <param name="clientFactory">实例化好的HttpClientFactory对象</param>
/// <param name="url">请求地址</param>
/// <param name="content">请求内容</param>
/// <param name="clientName">注册名称,默认不指定</param>
/// <returns></returns>
public static string MyPostRequest(IHttpClientFactory clientFactory, string url,string content, string clientName = "")
{
var request = new HttpRequestMessage(HttpMethod.Post, url);
//内容的处理
request.Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
var client = clientName == "" ? clientFactory.CreateClient() : clientFactory.CreateClient(clientName);
var response = client.SendAsync(request).Result;
var myResult = response.Content.ReadAsStringAsync().Result;
return myResult;
} /// <summary>
/// Post请求-Json形式
/// </summary>
/// <param name="clientFactory">实例化好的HttpClientFactory对象</param>
/// <param name="url">请求地址</param>
/// <param name="content">请求内容</param>
/// <param name="clientName">注册名称,默认不指定</param>
/// <returns></returns>
public static string MyPostRequestJson(IHttpClientFactory clientFactory, string url, object content, string clientName = "")
{
var request = new HttpRequestMessage(HttpMethod.Post, url);
//内容的处理
request.Content = new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json");
var client = clientName == "" ? clientFactory.CreateClient() : clientFactory.CreateClient(clientName);
var response = client.SendAsync(request).Result;
var myResult = response.Content.ReadAsStringAsync().Result;
return myResult;
} }
   public class HomeController : Controller
{
private readonly IHttpClientFactory _clientFactory;
public HomeController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
} public async Task<IActionResult> Index([FromServices] UserService userService)
{
string url1 = "http://localhost:15319/Server/CheckLogin?userName=admin&pwd=123456";
string url2 = "http://localhost:15319/Server/Register1";
string url3 = "http://localhost:15319/Server/Register2";
string url4 = "Server/CheckLogin?userName=admin&pwd=123456"; /*********************************************测试框架封装*********************************************/ #region 测试框架封装
{
var result1 = RequestHelp.MyGetRequest(_clientFactory, url1);
var result2 = RequestHelp.MyGetRequest(_clientFactory, url4, "client1");
//Post-表单提交
var result3 = RequestHelp.MyPostRequest(_clientFactory, url2, "userName=admin&pwd=123456");
//Post-Json提交
var user = new
{
userName = "admin",
pwd = ""
};
var result4 = RequestHelp.MyPostRequestJson(_clientFactory, url3, user); }
#endregion return View();
}
}

五. 如何在客户端调用

  前面的介绍都是基于Core MVC来发送请求,然后可以注入 IHttpClientFactory 对象,那么如何在控制台上调用呢? 这是一类通用的问题,看下面代码,获取 IHttpClientFactory 对象,然后后续的Get和Post请求的写法和前面介绍的完全相同,可以根据自己的喜好进行选择。

var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
IHttpClientFactory httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();

分享一个完整的客户端请求写法:

             var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
IHttpClientFactory httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
string url2 = "http://XXX:8055/Home/SendAllMsg"; var client = httpClientFactory.CreateClient();
var content = new StringContent("msg=123", Encoding.UTF8, "application/x-www-form-urlencoded");
var response = client.PostAsync(url2, content).Result;
string result = "";
if (response.IsSuccessStatusCode)
{
result = response.Content.ReadAsStringAsync().Result;
}

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

第十七节:.Net Core中新增HttpClientFactory的前世今生的更多相关文章

  1. 在 .NET Core 中结合 HttpClientFactory 使用 Polly(下篇)

    译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题).其中可能会去除一些不影响理解但本人实在不知道如 ...

  2. 在 .NET Core 中结合 HttpClientFactory 使用 Polly(中篇)

    译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题).其中可能会去除一些不影响理解但本人实在不知道如 ...

  3. 在 .NET Core 中结合 HttpClientFactory 使用 Polly(上篇)

    译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq 译者序一:前两天写了一篇文章 .NET Core 开源项目 Polly 介绍,在写这篇文章查看 Polly 资料时,看到了 ...

  4. 在ASP.NET Core中用HttpClient(六)——ASP.NET Core中使用HttpClientFactory

    ​到目前为止,我们一直直接使用HttpClient.在每个服务中,我们都创建了一个HttpClient实例和所有必需的配置.这会导致了重复代码.在这篇文章中,我们将学习如何通过使用HttpClient ...

  5. 第三百六十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)scrapy写入数据到elasticsearch中

    第三百六十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)scrapy写入数据到elasticsearch中 前面我们讲到的elasticsearch( ...

  6. 工厂参观记:.NET Core 中 HttpClientFactory 如何解决 HttpClient 臭名昭著的问题

    在 .NET Framework 与 .NET Core 中 HttpClient 有个臭名昭著的问题,HttpClient 实现了 IDispose 接口,但当你 Dispose 它时,它不会立即关 ...

  7. .net core 中使用httpclient,HttpClientFactory的问题

    Microsoft 在.Net Framework 4.5中引入了HttpClient,并且是在.NET服务器端代码中使用Web API的最常用方法.但它有一些严重的问题,如释放HttpClient对 ...

  8. 第五节:EF Core中的三类事务(SaveChanges、DbContextTransaction、TransactionScope)

    一. 说明 EF版本的事务介绍详见: 第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges.DBContextTransaction.TransactionScope). 本节主 ...

  9. Egret入门学习日记 --- 第十七篇(书中 7.4~8.2节 内容)

    第十七篇(书中 7.4~8.2节 内容) 昨天看到 7.3 节,那么今天. 开始 7.4节.     好吧,这些其他的服务器运行知识,就不搞了... 至此,7.4节 内容结束. 开始 7.5节 内容. ...

随机推荐

  1. 将Redis设置为后台启动

    Linux 在执行redis-server  /etc/redis.conf 时默认开启的是一个前台的进程,也就是说启动的redis 就做不了其他的操作了,只有关闭redis 才能做其他的操作.非常的 ...

  2. web安全常用工具

    简单工具:明小子,阿d注入工具,namp,穿山甲,御剑,旁注 漏洞扫描工具:appscan .awvs.nbsi 端口扫描工具:nessus.namp.天镜脆弱性扫描与管理系统 数据库备份工具:中国菜 ...

  3. Bean的一生(Bean的生命周期)

    1. 什么是Bean? Bean是spring中组成应用程序的主体及由spring IoC容器所管理的对象(IoC容器初始化.装配及管理的对象).如果把spring比作一座大型工厂,那么bean就是该 ...

  4. Django框架(二)-- 基本配置:app注册、模板配置、静态文件配置、数据库连接配置post和get

    一.app 在Django中,APP可以用以下类比 大学 --------------------项目 计算机学院------------app01 土木学院 ------------ app02 1 ...

  5. springboot中配置urlrewrite实现url伪静态强化网站seo

    关于urlrewrite urlrewrite使用强大的自定义规则来使用用户更容易记住.搜索引擎更容易找到的URL(对于seo比较重要).通过使用规则模板.重写映射,Web管理员可以轻松地设置规则,根 ...

  6. django+sqlite3进行web开发(一)

    服务器配置 安装django sudo apt-get install python-django -y 安装mysql(可选) 也可以直接使用sqlite sudo apt-get install ...

  7. axios之Vue请求初始化数据放在Created还是Mounted?

    先分析下生命周期 beforecreated:el 和 data 并未初始化 created:完成了 data 数据的初始化,el没有 beforeMount:完成了 el 和 data 初始化 mo ...

  8. 在CentOS7.5上安装Docker,在Docker中拉取CentOS7.5镜像并安装SSH服务

    # 安装docker yum install -y docker # 启动docker systemctl start docker # 加入开机启动 systemctl enable docker ...

  9. 03-C#笔记-数据类型

    --- # 1 数据类型 --- ## 1 和C++不同的数据类型: byte: 8位,0~255 decimal:范围大于double,128位 sbyte: 8位,-128~127 uint 32 ...

  10. 小程序开发第一天josn和wxml

    视频中只有app.josn路径还有wxm文本.js中没有调用page.原视频中是可以出来文本内容的. 但是把js调用page以后是可以呈现的 所以疑问点就是为什么以前可以? 1.微信开发工具改了,强制 ...