.NET Core HttpClient源码探究

前言

    在之前的文章我们介绍过HttpClient相关的服务发现,确实HttpClient是目前.NET Core进行Http网络编程的的主要手段。在之前的介绍中也看到了,我们使用了一个很重要的抽象HttpMessageHandler,接下来我们就探究一下HttpClient源码,并找寻它和HttpMessageHandler的关系究竟是怎么样的。

HttpClient源码解析

    首先我们找到HttpClient源码的位置,微软也提供了专门的网站可以查找.Net Core源码有兴趣的同学可以自行查阅。接下来我们查阅一下HttpClient的核心代码。首先,我们可以看到HttpClient继承自HttpMessageInvoker这个类,待会我们在探究这个类。

public class HttpClient : HttpMessageInvoker
{
}

然后我们看下几个核心的构造函数

public HttpClient(): this(new HttpClientHandler())
{
}public HttpClient(HttpMessageHandler handler): this(handler, true)
{
}public HttpClient(HttpMessageHandler handler, bool disposeHandler): base(handler, disposeHandler)
{_timeout = s_defaultTimeout;_maxResponseContentBufferSize = HttpContent.MaxBufferSize;_pendingRequestsCts = new CancellationTokenSource();
}

通过这几个构造函数我们看出,我们可以传递自定义的HttpMessageHandler。我们再看无参默认的构造,其实也是实例化了HttpClientHandler传递给了自己的另一个构造函数,我们之前讲解过HttpClientHandler是继承自了HttpMessageHandler,通过最后一个构造函数可知最终HttpMessageHandler,传给了父类HttpMessageInvoker。到了这里我们基本上就可以感受到HttpMessageHandler在HttpClient中存在的意义。
    接下来,我们从一个最简单,而且最常用的方法为入口开始探索HttpClient的工作原理。这种方式可能是我们最常用而且最有效的的探索源码的方式了。个人建议没看过源码,或者刚开始入门看源码的小伙伴们,找源码的入口一定是你最有把握的的一个,然后逐步深入了解。接下来我们选用HttpClient的GetAsync开始入手,而且是只传递Url的那一个。

public Task<HttpResponseMessage> GetAsync(string? requestUri)
{return GetAsync(CreateUri(requestUri));
}public Task<HttpResponseMessage> GetAsync(Uri? requestUri)
{return GetAsync(requestUri, defaultCompletionOption);
}

通过这里我们可以大致了解到。其实大部分最简单的调用方式,往往都是从最复杂的调用方式,一步步的封装起来的,只是系统帮我们初始化了一部分参数,让我们按需使用。顺着方法一直向下找,最后找到了这里。

public Task<HttpResponseMessage> GetAsync(Uri? requestUri, HttpCompletionOption completionOption,CancellationToken cancellationToken)
{return SendAsync(CreateRequestMessage(HttpMethod.Get, requestUri), completionOption, cancellationToken);
}

由此可以看出这里是所有GetAsync方法的执行入口,我们通过查找SendAsync引用可以发现。不仅仅是GetAsync, PostAsync,PutAsync,DeleteAsync最终都是调用了这个方法。也就是说SendAsync是所有发送请求的真正执行者。接下来我们就查看SendAsync方法,部分边角料代码我粘贴的时候将会做删减。

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption,CancellationToken cancellationToken)
{if (request == null){throw new ArgumentNullException(nameof(request));}CheckDisposed();CheckRequestMessage(request);SetOperationStarted();//这里会把发送请求的HttpRequestMessage准备妥当PrepareRequestMessage(request);CancellationTokenSource cts;bool disposeCts;bool hasTimeout = _timeout != s_infiniteTimeout;long timeoutTime = long.MaxValue;if (hasTimeout || cancellationToken.CanBeCanceled){disposeCts = true;cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _pendingRequestsCts.Token);if (hasTimeout){timeoutTime = Environment.TickCount64 + (_timeout.Ticks / TimeSpan.TicksPerMillisecond);cts.CancelAfter(_timeout);}}else{disposeCts = false;cts = _pendingRequestsCts;}Task<HttpResponseMessage> sendTask;try{//***这里是核心,最终执行调用的地方!!!sendTask = base.SendAsync(request, cts.Token);}catch (Exception e){HandleFinishSendAsyncCleanup(cts, disposeCts);if (e is OperationCanceledException operationException && TimeoutFired(cancellationToken, timeoutTime)){throw CreateTimeoutException(operationException);}throw;}//这里处理输出的唯一类型HttpResponseMessagereturn completionOption == HttpCompletionOption.ResponseContentRead && !string.Equals(request.Method.Method, "HEAD", StringComparison.OrdinalIgnoreCase) ?FinishSendAsyncBuffered(sendTask, request, cts, disposeCts, cancellationToken, timeoutTime) :FinishSendAsyncUnbuffered(sendTask, request, cts, disposeCts, cancellationToken, timeoutTime);
}

通过分析这段代码可以得知,HttpClient类中最终执行的是父类的SendAsync的方法。看来是时候查看父类HttpMessageInvoker的源码了。

HttpMessageInvoker源码解析

public class HttpMessageInvoker : IDisposable
{private volatile bool _disposed;private readonly bool _disposeHandler;private readonly HttpMessageHandler _handler;public HttpMessageInvoker(HttpMessageHandler handler): this(handler, true){}public HttpMessageInvoker(HttpMessageHandler handler, bool disposeHandler){if (NetEventSource.IsEnabled) NetEventSource.Enter(this, handler);if (handler == null){throw new ArgumentNullException(nameof(handler));}if (NetEventSource.IsEnabled) NetEventSource.Associate(this, handler);_handler = handler;_disposeHandler = disposeHandler;if (NetEventSource.IsEnabled) NetEventSource.Exit(this);}public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,CancellationToken cancellationToken){if (request == null){throw new ArgumentNullException(nameof(request));}CheckDisposed();if (NetEventSource.IsEnabled) NetEventSource.Enter(this, request);//***这里是HttpClient调用的本质,其实发送请求的根本是HttpMessageHandler的SendAsyncTask<HttpResponseMessage> task = _handler.SendAsync(request, cancellationToken);if (NetEventSource.IsEnabled) NetEventSource.Exit(this, task);return task;}public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}protected virtual void Dispose(bool disposing){if (disposing && !_disposed){_disposed = true;if (_disposeHandler){_handler.Dispose();}}}private void CheckDisposed(){if (_disposed){throw new ObjectDisposedException(GetType().ToString());}}
}

    是的,你并没有看错,整个HttpMessageInvoker就这么多代码,而且还是靠子类初始化过来的基本属性。找到SendAsync方法,这里基本上可以总结一点,负责调用输入输出的类只有两个。一个是提供请求参数的HttpRequestMessage,另一个是接收输出的HttpResponseMessage。这里也给我们日常工作编码中提供了一个很好的思路。针对具体某个功能的操作方法,最好只保留一个,其外围调用,都是基于该方法的封装。然后我们找到了发送请求的地方_handler.SendAsync(request, cancellationToken),而handler正是我们通过HttpClient传递下来的HttpMessageHandler.由此可知,HttpClient的本质是HttpMessageHandler的包装类。

自定义HttpClient

    探究到这里我们也差不多大概了解到HttpClient类的本质是什么了。其实到这里我们可以借助HttpMessageHandler的相关子类,封装一个简单的Http请求类.接下来我将动手实现一个简单的Http请求类,我们定义一个类叫MyHttpClient,实现代码如下

public class MyHttpClient : IDisposable
{private readonly MyHttpClientHandler _httpClientHandler;private readonly bool _disposeHandler;private volatile bool _disposed;public MyHttpClient():this(true){}public MyHttpClient(bool disposeHandler){_httpClientHandler = new MyHttpClientHandler();_disposeHandler = disposeHandler;}public Task<HttpResponseMessage> GetAsync(string url){return GetAsync(new Uri(url));}public Task<HttpResponseMessage> GetAsync(Uri uri){HttpRequestMessage httpRequest = new HttpRequestMessage{Method = HttpMethod.Get,RequestUri = uri};return SendAsync(httpRequest,CancellationToken.None);}public Task<HttpResponseMessage> PostAsync(string url, HttpContent content){return PostAsync(new Uri(url),content,null);}public Task<HttpResponseMessage> PostAsync(Uri uri, HttpContent content,Dictionary<string,string> headers){HttpRequestMessage httpRequest = new HttpRequestMessage{Method = HttpMethod.Post,RequestUri = uri,Content = content};if (headers != null && headers.Any()){foreach (var head in headers){httpRequest.Headers.Add(head.Key,head.Value);}}return SendAsync(httpRequest, CancellationToken.None);}private Task<HttpResponseMessage> SendAsync(HttpRequestMessage httpRequest, CancellationToken cancellationToken){if (httpRequest.RequestUri == null || string.IsNullOrWhiteSpace(httpRequest.RequestUri.OriginalString)){throw new ArgumentNullException("RequestUri");}return _httpClientHandler.SendRequestAsync(httpRequest, cancellationToken);}public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}protected virtual void Dispose(bool disposing){if (disposing && !_disposed){_disposed = true;if (_disposeHandler){_httpClientHandler.Dispose();}}}
}

由于HttpMessageHandler的SendAsync是protected非子类无法直接调用,所以我封装了一个MyHttpClientHandler继承自HttpClientHandler在MyHttpClient中调用,具体实现如下

public class MyHttpClientHandler : HttpClientHandler
{public Task<HttpResponseMessage> SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken){return this.SendAsync(request, cancellationToken);}protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){return base.SendAsync(request, cancellationToken);}
}

最后写了一段测试代码

using (MyHttpClient httpClient = new MyHttpClient())
{Task<HttpResponseMessage> httpResponse = httpClient.GetAsync("http://localhost:5000/Person/GetPerson?userId=1");HttpResponseMessage responseMessage = httpResponse.Result;if (responseMessage.StatusCode == HttpStatusCode.OK){string content = responseMessage.Content.ReadAsStringAsync().Result;if (!string.IsNullOrWhiteSpace(content)){System.Console.WriteLine(content);}}
}

到这里自己实现MyHttpClient差不多到此结束了,因为只是讲解大致思路,所以方法封装的相对简单,只是封装了Get和Post相关的方法。

总结

    通过本文分析HttpClient的源码,我们大概知道了HttpClient本质还是HttpMessageHandler的包装类。最终的发送还是调用的HttpMessageHandler的SendAsync方法。最后,我根据HttpClientHandler实现了一个MyHttpClient。以上只是本人理解,如果处在理解不正确或者不恰当的地方,望多多包涵,同时也期望能指出理解不周的地方。我写文章的主要一部分是想把我的理解传递给大家,欢迎大家多多交流。

????欢迎扫码关注????

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

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

相关文章

Java 多线程:线程优先级

1 优先级取值范围 Java 线程优先级使用 1 ~ 10 的整数表示&#xff1a; 最低优先级 1&#xff1a;Thread.MIN_PRIORITY 最高优先级 10&#xff1a;Thread.MAX_PRIORITY 普通优先级 5&#xff1a;Thread.NORM_PRIORITY 2 获取线程优先级 public static void main(String[]…

《Unit Testing》1.1 -1.2 单元测试的目的

本系列是《Unit Testing》 一书的读书笔记 精华提取。书中的例子 C# 语言编写&#xff0c;但概念是通用的&#xff0c;只要懂得面向对象编程就可以。 单元测试当前的状态目前&#xff0c;在&#xff08;美国的&#xff09;大部分公司里&#xff0c;单元测试都是强制性的。生产…

Java Exception

Exception 异常捕获 将代码块选中->ctrlaltt->选中try-catch 01: public class Exception01 {public static void main(String[] args) {int n1 10;int n2 0;try {int res n1/n2;} catch (Exception e) { // e.printStackTrace();System.out.println(e.…

《Unit Testing》1.3 使用覆盖率指标来度量测试套件的好坏

使用覆盖率来度量测试套件&#xff08;Test Suite&#xff09;的质量有两种比较流行的测试覆盖率的度量方法&#xff1a;代码覆盖率分支覆盖率覆盖率度量会显示一个测试套件&#xff08;Test Suite&#xff09;会执行多少代码&#xff0c;范围从 0 至 100%。除了上述两种方法之…

Linux创始人:v5.8是有史以来最大的发行版之一

导语Linux v5.8已经修改了所有文件的20&#xff05;&#xff0c;是迄今为止变化最大的一次发行版。正文Linux创始人Linus Torvalds表示&#xff1a;Linux内核5.8版是“我们有史以来最大的发行版之一”。如果一切顺利&#xff0c;Linux v5.8稳定版应该在2020年8月的某个时候出现…

[高等数学]这你不背?

求导及求微分的基本公式: 泰勒中值定理: 麦克劳林公式: 不定积分公式: 凑微分: 第二类换元积分法常用的三种情况: 求高阶导数的几个公式: 二阶常系数非齐次线性微分方程的特解: 排列组合公式: C的计算&#xff1a; 下标的数字乘以上标的数字的个数,且每个数字都要-1.再除以上标…

怎么开会才不浪费时间?

这里是Z哥的个人公众号每周五11&#xff1a;45 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「148」篇原创敬上大家好&#xff0c;我是Z哥&#xff0c;先祝大家端午节日快乐。节日期间就发篇比较短的文章吧。人在职场混&#xff0c;开会应该是本职工作之外花…

.NET 5.0预览版6发布:支持Windows ARM64设备

2020年6月25日&#xff0c;微软dotnet团队在博客宣布了第六个 .NET 5.0 的预览版&#xff1a;https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-6/&#xff0c;在改进性能的同时增加了一些新的功能。ASP.NET Core和 EF Core也将于今日发布了。注意&#xff1…

利用真值表法求取主析取范式以及主合取范式的实现(C++)

代码如下: #include <iostream> #include <stack> #include <string> #include <vector> using namespace std; const int N 300; stack<char> s; stack<char> v; int seq; bool vis[N]; bool flag[N]; void dfs(int n); vector<int&…

基于 Blazor 开发五子棋小游戏

今天是农历五月初五&#xff0c;端午节。在此&#xff0c;祝大家端午安康&#xff01;端午节是中华民族古老的传统节日之一。端午也称端五&#xff0c;端阳。此外&#xff0c;端午节还有许多别称&#xff0c;如&#xff1a;午日节、重五节、五月节、浴兰节、女儿节、天中节、地…

汇编cmp比较指令详解

刚刚看到了cmp指令&#xff0c;一开始有点晕。后来上网找了些资料&#xff0c;终于看明白了&#xff0c;为了方便初学者&#xff0c;我就简单写下我的思路吧。高手绕过&#xff0c;谢谢&#xff01; cmp(compare)指令进行比较两个操作数的大小例:cmp oprd1,oprd2为第一个操作减…

如何在ASP.NET Core中使用SignalR构建与Angular通信的实时通信应用程序

图片假设我们要创建一个监视Web应用程序&#xff0c;该应用程序为用户提供了一个能够显示一系列信息的仪表板&#xff0c;这些信息会随着时间的推移而更新。第一种方法是在定义的时间间隔&#xff08;轮询&#xff09;定期调用API 以更新仪表板上的数据。无论如何&#xff0c;还…

LED计数电路,5输入按键编码器,7段数码管显示驱动集成为LED计数测试电路

LED计数电路: 5输入按键编码器: 7段数码管显示驱动真值表: 集成:

越卖越涨?腾讯股票3月后大涨45%,超越“阿里”成中国第一,市值相当于14.3个百度!...

01 腾讯股价大涨据股市最新消息&#xff1a;腾讯股价已连续3个交易日上涨, 其中6月22日腾讯股价重返470港元关口&#xff0c;公司市值突破4.5万亿港元&#xff0c;折合4.0万亿人民币&#xff1b;而6月23日上午腾讯股价再度大涨4.05%&#xff0c;刷出493.8港元的新高&#xf…

4位无符号比较器设计

4位比较器原理&#xff1a; 4位比较 a3a2a1a0 : b3b2b1b0&#xff0c;比较顺序从高位到低位&#xff0c;当高位大、小关系确定时则无需看低位&#xff0c;当高位相等时再看相邻低位的关系。 注意&#xff1a;对于三个比较结果&#xff0c;已知其中任意两个&#xff0c;可以用…

关于技术文章“标题党”一事我想说两句

阅读本文大概需要 1.8 分钟。前天发表的一篇文章&#xff0c;标题是&#xff1a;“面试官&#xff1a;你刚说你喜欢研究新技术&#xff0c;那么请说说你对 Blazor 的了解”。确实&#xff0c;这篇文章有标题党的味道&#xff0c;如果因此给部分童鞋带来不适&#xff0c;我在这先…

使用 nuget server 的 API 来实现搜索安装 nuget 包

使用 nuget server 的 API 来实现搜索安装 nuget 包Intronuget 现在几乎是 dotnet 开发不可缺少的一部分了&#xff0c;还没有用过 nuget 的就有点落后时代了&#xff0c;还不快用起来nuget 是 dotnet 里的包管理机制&#xff0c;类似于前端的 npm &#xff0c;php 的 composer…