写点什么

.NET Core HttpClient 源码探究

用户头像
yi念之间
关注
发布于: 1 小时前
.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。以上只是本人理解,如果处在理解不正确或者不恰当的地方,望多多包涵,同时也期望能指出理解不周的地方。我写文章的主要一部分是想把我的理解传递给大家,欢迎大家多多交流。


👇欢迎扫码关注👇


发布于: 1 小时前阅读数: 4
用户头像

yi念之间

关注

星光不问赶路人,时光不负有心人。 2018.08.22 加入

普通程序员,主攻.net core方向,顺便学习Java和Python。喜欢架构设计,励志成为一名真正的架构师,喜欢研究新技术,喜欢阅读源码。

评论

发布
暂无评论
.NET Core HttpClient源码探究