一. 背景

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. Centos7下安装redis并能使得外网访问

    一.安装脚本 #!/bin/bash #FileName: install_redis_centos7.sh #Date: #Author: LiLe #Contact: @qq.com #Versi ...

  2. MySQL5.7 安装和配置环境变量

    安装 1.下载安装包 官网地址:https://dev.mysql.com/downloads/mysql/ 2.选择 Custom,自定义 3.根据自己系统选择 x64还是x86,然后点击第一个箭头 ...

  3. 0x02 Python logging模块利用配置加载logger

    目录 logging模块利用配置加载logger 方式一模板:logging.config.dictConfig(config_dict) logging模块利用配置加载logger logging. ...

  4. 'root'@'localhost'不能登录问题

    鉴于我在遇到这个问题时,在网上搜索到几十个答案都是进行更改密码解决该问题,然并没有卵用.最后还是让小编找到了解决方法,希望大家遇到该问题时能够节省时间.解决方法如下:   #mysql -u root ...

  5. maven仓库报错 sqljdbc4、ojdbc6、tomcat-jdbc-8.5.14

    报错:Cannot resolve com.microsoft.sqlserver:sqljdbc4:4.0  和  Missing artifact com.microsoft.sqlserver: ...

  6. 【JavaScript】内部与外部引入方式

    1.内部引入方式: script的type属性默认为"text/javascript",可以不写 <script type="text/javascript&quo ...

  7. soeasy的键盘鼠标事件

    在web自动化中,我们可能会遇到需要通过键盘或者鼠标去操作某些元素,那么我们就需要用到键盘事件和鼠标事件了,今天对键盘和鼠标操作进行一个总结 鼠标事件 鼠标事件需要引入ActionChains类,查看 ...

  8. 等了半年的AMD锐龙3000系列台式机处理器今天终于上市开卖了!

    第三代AMD锐龙台式机处理器参数:

  9. 达信:深度解读COSO新版企业风险管理框架(ERM)

    http://www.sohu.com/a/124375769_489979 2016年6月,美国反欺诈财务报告委员会(The Committee of Sponsoring Organization ...

  10. 2014-2015 ACM-ICPC, Asia Tokyo Regional Contest

    2014-2015 ACM-ICPC, Asia Tokyo Regional Contest A B C D E F G H I J K O O O O   O O         A - Bit ...