1 背景

从园子里看到一些朋友在某些项目开发中,选择的架构是spring cloud搭建底层微服务框架,dotnet core来编写业务逻辑,SteeltoeOSS.Discovery是dotnet和spingcloud的桥梁,为dotnet提供服务注册和服务发现相关功能。在阅读朋友们文章的时候,我发现相关代码里的一些HttpClient相关问题,同时对dotnet寄居于spingcloud下由于没有Feign而产生的那些丑陋的http请求代码进行思考。本文将围绕原生的HttpClient的创建与释放的正确姿势和使用WebApiClient让dotnet也有媲美Feign的服务客户端两个面展开。

2 正确使用HttpClient

2.1 HttpClient的创建和释放

HttpClient有三个构造函数,最终都是调用到public HttpClient(HttpMessageHandler handler, bool disposeHandler)这个函数,HttpClient除了其handler参数之外,本身没有使用到需要Dispose的资源,其实现的IDispose也是为了Dispose掉handler参数而已。

HttpMessageHandler是一个抽象类,目前主要的HttpMessageHandler具体类型有HttpClientHanlder、SocketsHttpHandler和WebRequestHandler,但HttpClientHanlder在dotnet core2.1下是对和SocketsHttpHandler的包装实现。除了这些主要HttpMessageHandler,还有一个抽象的DelegatingHandler类型,用于实现请求管道,影响请求前后的数据逻辑。HttpClient的默认构造器,使用了HttpClientHanlder类型,同时disposeHandler为true,这时如果对HttpClient实例Dispose了,其内部的HttpClientHanlder自然也被Dispose了,

正确的创建和释放HttpClient例子

  1. 默认构造器
var httpClient = new HttpClient();
...你的代码...
httpClient.Dispose();
  1. HttpClient控制HttpMessageHandler
var handler = new HttpClientHandler();
var httpClient = new HttpClient(handler, true);
...你的代码...
httpClient.Dispose();
  1. HttpClient不控制外部HttpMessageHandler
var handler = 从外部来的HttpMessageHandler;
var httpClient = new HttpClient(handler, false);
...你的代码... // 这里调用httpClient.Dispose()是无效的
// handler的生命周期应该由它的创建者来维护
// 如果这里Dispose掉handler,其它使用了这个handler的HttpClient实例受影响

不正确的创建和释放HttpClient的例子

  1. HttpMessageHandler被创建了,但没有释放
private readonly DiscoveryHttpClientHandler _handler;
private const string ProductUrl = "http://product/api/values"; public ValuesController(IDiscoveryClient client, ILoggerFactory logFactory)
{
_handler = new DiscoveryHttpClientHandler(client);
} [HttpGet("product")]
public async Task<string> GoProductAsync()
{
var client = new HttpClient(_handler, false);
return await client.GetStringAsync(ProductUrl);
}

2.2 HttpClient的生命周期

HttpClient在设计之初,是非常适合使用单例模式的,也就是在应用域中,只维护一份HttpClient的实例就够了,因为它天生支持向不同域名同时多个并发请求。但单例的HttpClient没有遵守DNS 生存时间 (TTL) 设置,如果其HttpClientHandler第一次请求到www.baidu.com指向的ip为123.123.123.123,中途这个域名解析到的ip变化了,但HttpClient并不会自动应用这些变化。

asp.net core里,微软创建一个HttpClientFactory项目,用于提供HttpClient的创建和生命周期自动管理,完美解决到底选择单例还是每个请求创建和释放HttpClient这个左右难为的问题。所以在asp.net core项目开发中,请别再写手动new HttpClient了,所有HttpClient的实例,都要由HttpClientFactory来创建,所有的外部HttpMessageHandler,也应该配置到HttpClientFactory,让它与HttpClient关联起来。

HttpClientFactory有三种使用方式:

  1. Using HttpClientFactory Directly
  2. Named Clients
  3. Typed Clients

具体的使用,可以查看3 ways to use HTTPClientFactory in ASP.NET Core 2.1这篇好文。

3 寄居下也有Feign

虽然已经讲解了怎么new一个HttpClient,怎么利用HttpClientFactory,但如果要寄居在spingcloud下,你还是得为请求一个服务接口编写大量的代码,这在java的Feign前面如同马车见到宝马。如果能利用WebApiClient这把利剑,结合HttpClientFactory与DiscoveryHttpClientHandler,你也能变成宝马。将这三者有机结合起来的项目,叫WebApiClient.Extensions.DiscoveryClient,它是WebApiClient的SteeltoeOSS.Discovery扩展项目,使用非常简单。

3.1 Nuget引用

PM> install-package WebApiClient.Extensions.DiscoveryClient

3.2 声明微服务的WebApiClient调用接口

[HttpHost("http://NET-API")]
public interface INetApi : IHttpApi
{
[HttpGet("api/values")]
ITask<string[]> GetValuesAsync(); [HttpGet("api/values/{id}")]
ITask<string> GetValuesAsync(int id);
}

3.3 Startup相关配置

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDiscoveryClient(Configuration);
services.AddDiscoveryTypedClient<INetApi>();
...
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseDiscoveryClient();
}
3.4 Controller
public class HomeController : Controller
{
public async Task<string> Index([FromServices]INetApi netApi, int id = 0)
{
var values = await netApi.GetValuesAsync();
var value = await netApi.GetValuesAsync(id);
return "ok";
}
}

4. 结束语

本博主对HttpClient有着比较深入的了解,一直在维护WebApiClient项目,期间走过许许多多的“坑”,如果你有遇到HttpClient的相关问题,欢迎一起讨论解决。

WebApiClient的SteeltoeOSS.Discovery扩展的更多相关文章

  1. .NET Core微服务之基于Steeltoe使用Eureka实现服务注册与发现

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 =>  Steeltoe目录快速导航: 1. 基于Steeltoe使用Spring Cloud Eureka 2. 基于Steelt ...

  2. asp.net core microservices 架构之eureka服务发现

    一 简介 微服务将需多的功能拆分为许多的轻量级的子应用,这些子应用相互调度.好处就是轻量级,完全符合了敏捷开发的精神.我们知道ut(单元测试),不仅仅提高我们的程序的健壮性,而且可以强制将类和方法的设 ...

  3. .NET Core HttpClient+Consul实现服务发现

    简介 随着.NET Core的不断发展与成熟,基于.NET Core实现微服务的解决方案也越来越多.这其中必然需要注册中心,Consul成为了.NET Core实现服务注册与发现的首选.类似的解决方案 ...

  4. Elasticsearch是一个分布式可扩展的实时搜索和分析引擎,elasticsearch安装配置及中文分词

    http://fuxiaopang.gitbooks.io/learnelasticsearch/content/  (中文) 在Elasticsearch中,文档术语一种类型(type),各种各样的 ...

  5. Windows Azure 安全最佳实践 - 第 6 部分:Azure 服务如何扩展应用程序安全性

    多种Windows Azure服务可以帮助您将应用程序安全性扩展到云. 有三种服务可提供多个提供程序之间的身份标识映射.内部部署数据中心间的连接和相互发送消息的应用程序功能(无论应用程序位于何处). ...

  6. [认证授权] 5.OIDC(OpenId Connect)身份认证授权(扩展部分)

    在上一篇[认证授权] 4.OIDC(OpenId Connect)身份认证授权(核心部分)中解释了OIDC的核心部分的功能,即OIDC如何提供id token来用于认证.由于OIDC是一个协议族,如果 ...

  7. 使用WebApiClient请求和管理Restful Api

    前言 本篇文章的内容是WebApiClient应用说明篇,如果你没有了解过WebApiClient,可以先阅读以下相关文章: WebApi client 的面向切面编程 我来给.Net设计一款Http ...

  8. 2015 ICL, Finals, Div. 1 Ceizenpok’s formula(组合数取模,扩展lucas定理)

    J. Ceizenpok’s formula time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  9. [认证授权] 5.OIDC(OpenId Connect)身份认证(扩展部分)

    在上一篇[认证授权] 4.OIDC(OpenId Connect)身份认证(核心部分)中解释了OIDC的核心部分的功能,即OIDC如何提供id token来用于认证.由于OIDC是一个协议族,如果只是 ...

随机推荐

  1. loj6045 「雅礼集训 2017 Day8」价

    我们考虑最小割. 我一开始觉得是裸的最小割,就直接S到每个减肥药连up+p[i]的边,减肥药到药材连inf边,药材到T连up,然后得到了40分的好成绩. 之后我发现这是一个假的最小割,最小割割的是代价 ...

  2. Python&Appium实现滑动引导页进入APP

    最近在研究安卓APP的自动化测试.首先遇到的问题是,当一个session建立的时候,最先进入的是欢迎页和引导页,引导页有三张,最后一张上显示"enter"按钮,点击才能进入主界面. ...

  3. Charles 如何破解与连接手机进行抓包

    破解charles: 由于本人工作原因,现使用的为mac笔记本,但是基本的使用原理都是一样的,以下为如何破解charles与连接手机进行抓包详解.工具如有需要着请留言. 当前下载使用的版本为:char ...

  4. Python-常用 Linux 命令的基本使用

    常用 Linux 命令的基本使用 操作系统 作用:管理好硬件设备,让软件可以和硬件发生交互类型 桌面操作系统 Windows macos linux 服务器操作系统 linux Windows ser ...

  5. Redis学习——详解Redis配置文件(三)

    一.Redis脚本简介 在我们介绍Redis的配置文件之前,我们先来说一下Redis安装完成后生成的几个可执行文件: redis-server .redis-cli .redis-benchmark ...

  6. Spring Boot入门(五):使用JDBC访问MySql数据库

    本系列博客记录自己学习Spring Boot的历程,如帮助到你,不胜荣幸,如有错误,欢迎指正! 在程序开发的过程中,操作数据库是必不可少的部分,前面几篇博客中,也一直未涉及到数据库的操作,本篇博客 就 ...

  7. “==”、“equals()”、“hashcode()”之间的秘密

    前言 万丈高楼平地起,今天的聊点基础而又经常让人忽视的话题,比如“==”与“equals()”区别?为何当我们重写完"equals()"后也要有必要去重写"hashcod ...

  8. 如何理解Axis?

    前言 只有光头才能变强. 回顾前面: 从零开始学TensorFlow[01-搭建环境.HelloWorld篇] 什么是TensorFlow? TensorFlow读写数据 不知道大家最开始接触到axi ...

  9. Spring学习(一):理解IoC容器

    序言 记得刚毕业那会儿,出来招工作被问到Spring的核心时,都觉得简单的一笔,直接说不就是IoC(控制反转)和DI(依赖注入)么,然后省略一万字对两个名词的解释.最近空来整理了一下Spring中Io ...

  10. 内部类、异常以及 LeetCode 每日一题

    1 内部类 内部类的作用: 内部类提供了更好的封装,可以把内部类隐藏于外部类之内,不允许同一个包中的其他类访问该类.(例如给“牛”这个类组合一个“牛腿”,则可以把牛腿定义成内部类,因为牛腿脱离了牛没有 ...