.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;
}
//这里处理输出的唯一类型HttpResponseMessage
return 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的SendAsync
Task<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。以上只是本人理解,如果处在理解不正确或者不恰当的地方,忘多多包涵,同时也期望能指出理解不周的地方。我写文章的主要一部分是想把我的理解传递给大家,欢迎大家多多交流。
.NET Core HttpClient源码探究的更多相关文章
- 浅谈.Net Core DependencyInjection源码探究
前言 相信使用过Asp.Net Core开发框架的人对自带的DI框架已经相当熟悉了,很多刚开始接触.Net Core的时候觉得不适应,主要就是因为Core默认集成它的原因.它是Asp.Net ...
- .Netcore HttpClient源码探究
源码搜索与概述 搜索HttpClient源码 https://source.dot.net/#System.Net.Http/System/Net/Http/HttpClient.cs 1.HttpC ...
- .NET Core Session源码探究
前言 随着互联网的兴起,技术的整体架构设计思路有了质的提升,曾经Web开发必不可少的内置对象Session已经被慢慢的遗弃.主要原因有两点,一是Session依赖Cookie存放Session ...
- .Net Core Configuration源码探究
前言 上篇文章我们演示了为Configuration添加Etcd数据源,并且了解到为Configuration扩展自定义数据源还是非常简单的,核心就是把数据源的数据按照一定的规则读取到指定的字 ...
- spring-cloud-sleuth+zipkin源码探究
1. spring-cloud-sleuth+zipkin源码探究 1.1. 前言 粗略看了下spring cloud sleuth core源码,发现内容真的有点多,它支持了很多类型的链路追踪, ...
- spring-boot-2.0.3之quartz集成,数据源问题,源码探究
前言 开心一刻 着火了,他报警说:119吗,我家发生火灾了. 119问:在哪里? 他说:在我家. 119问:具体点. 他说:在我家的厨房里. 119问:我说你现在的位置. 他说:我趴在桌子底下. 11 ...
- Vue源码探究-全局API
Vue源码探究-全局API 本篇代码位于vue/src/core/global-api/ Vue暴露了一些全局API来强化功能开发,API的使用示例官网上都有说明,无需多言.这里主要来看一下全局API ...
- Vue源码探究-事件系统
Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短 ...
- Vue源码探究-状态初始化
Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...
随机推荐
- Bind+DLZ+MySQL智能DNS的正向解析和反向解析实现方法
使用文本配置文件的配置方式结合bind的最新的acl和view特性来实现智能DNS想必很多人已经很熟悉了,使用MySQL数据库来存放zone文件的方式可能也不少.对于两者都熟悉的,实现 Bind+DL ...
- 中国AI觉醒 阿里王坚:云智能将成为大趋势
2019独角兽企业重金招聘Python工程师标准>>> <麻省理工科技评论>新兴科技峰会EmTech China于北京召开.大会中,其中一项热门的讨论便是:中国和美国的科 ...
- 代码,绘画,设计常用的颜色名称-16进制HEX编码-RGB编码 对照一览表
排列方式,英文名称的字典序 颜色名 HEX16进制编码 RGB编码 AliceBlue F0F8FF 240,248,255 AntiqueWhite FAEBD7 250,235,215 Aqua ...
- css属性、样式、边框、选择器
CSS 层叠样式表 (Cascading Style Sheets,缩写为 CSS),是一种 样式表 语言, 用来描述 HTML或 XML(包括如 SVG.MathML.XHTML 之类的 XML 分 ...
- MySQL JDBC Driver 8.0+设置服务器时区
遇到一个问题,线下环境测试数据的查询完全没有问题,但是线上环境却没法查询出数据,并且从mybatis输出的日志来看,查询参数也没有问题,数据库中数据也是存在的,查询参数类型是java.util.Dat ...
- [计算机视觉]从零开始构建一个微软how-old.net服务/面部属性识别
大概两三年前微软发布了一个基于Cognitive Service API的how-old.net网站,用户可以上传一张包含人脸的照片,后台通过调用深度学习算法可以预测照片中的人脸.年龄以及性别,然后将 ...
- K. Road Widening
\(考虑每个区域可行的区间\) \(x[1]=s[1]\ \ y[1]=s[1]+g[1]\) \(x[i]=max(x[i-1]-1,s[i]),y[i]=min(y[i-1]+1,s[i]+g[i ...
- PHP循环引用会遇到的坑
今天遇到这样一个问题: 如果foreach循环一个数组,引用去对它的元素做一些操作,会有什么问题吗? 比如 [1, 2, 3],foreach循环的时候,引用给每个元素 * 2,再去foreach输出 ...
- react中redux的理解
定义 redux可以看作是flux的进阶版,主要用于react中公共状态(数据)的管理 redux底层原理 redux有一个createStore方法,这个方法用户创建公共存储空间,createSto ...
- 机器学习的hello world——MNIST
MNIST:一个由60000行训练数据集和10000行的测试数据集(机器学习模型设计时必须有一个单独的数据集用于评估模型的性能)组成的数据集. 下载mnist的数据集后,将文件放入C:\Users\m ...