前言
在之前的文章我們介紹過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。以上只是本人理解,如果處在理解不正確或者不恰當的地方,忘多多包涵,同時也期望能指出理解不周的地方。我寫文章的主要一部分是想把我的理解傳遞給大家,歡迎大家多多交流。