.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,一经查实,立即删除!

相关文章

Young氏矩阵

Young氏矩阵 利用堆思想实现Young氏矩阵 #include <iostream> class YoungTableau {private:int *array;int row;int column;int heap_size; public:YoungTableau

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;单元测试都是强制性的。生产…

算法-排序-快速排序(包含多种快速排序)

快速排序 特点&#xff1a;原址排序&#xff0c;最坏的时间复杂度O&#xff08;n^2) 平均时间复杂度O&#xff08;nlgn&#xff09; 比归并排序系数常数项小 不稳定 底部有最坏时间复杂度为Ω(nlgn)的快速排序地址 void quick_sort(int *array,int start,int end); int parti…

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%。除了上述两种方法之…

对区间的模糊排序

对区间的模糊排序 算法导论第三版第二部分7-6题 Interval find_intersection(vector<Interval> &array,int start,int end) {int random random_include_left_right(start,end);Interval key array[end];array[end] array[random];array[random] key;key arra…

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

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

算法-排序-计数排序(包含对非负数和整数的排序)

计数排序 时间复杂度O&#xff08;n&#xff09; 特点&#xff1a;稳定 限制&#xff1a;对0到maximum中的数进行排序 maximum要求&#xff0c;比数组中最大值大或者相等 有拓展版本的计数排序&#xff08;在文章基数排序内&#xff09; 基数排序链接 void counting_sort(int…

[高等数学]这你不背?

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

怎么开会才不浪费时间?

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

在O(1)的时间内计算n个整数落在区间[a,b]的个数(预处理时间为O(n+k))

在O&#xff08;1&#xff09;的时间内计算n个整数落在区间[a,b]的个数 预处理时间为O&#xff08;nk) 算法导论第三版8.2-4题 int find_inverter_count(int *array,int length,int maximum,int inverter_left,int inverter_right) {if(inverter_left<0 || inverter_right…

汇编语言中常见的标志位: CF, PF, AF, ZF, SF,TF,IF,DF, OF

一、运算结构标志位 1.CF(进位标志位)&#xff1a;主要用来反映运算是否产生进位或借位&#xff0c;产生进位或借位则CF1&#xff0c;否则CF0。 2.PF(奇偶标志位)&#xff1a;用于反映运算结果中“1”的个数的奇偶性&#xff0c;如果“1”的个数为偶数&#xff0c;则PF1&…

算法-排序-基数排序(对任意整数排序)

基数排序 时间复杂度&#xff1a;Θ(d(nk)) d&#xff1a;元素的位数&#xff0c;k元素中每位数的取值区间大小 非原址排序 1⃣️特点该排序只能每次为基数为1位数进行排序 void radix_sort(int *array,int length,int digits){vector<KeyValuePair> temp_array(length…

.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;午日节、重五节、五月节、浴兰节、女儿节、天中节、地…

工具类—KeyValuePair

工具类KeyValuePair .h文件 #ifndef C11LEARN_KEYVALUEPAIR_H #define C11LEARN_KEYVALUEPAIR_Hclass KeyValuePair { public:int key;int value; public:KeyValuePair();KeyValuePair(int key,int value); };#endif //C11LEARN_KEYVALUEPAIR_H.cpp文件 #include "KeyV…

汇编cmp比较指令详解

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