.NET Core HttpClient源碼探究

前言

    在以前的文章咱們介紹過HttpClient相關的服務發現,確實HttpClient是目前.NET Core進行Http網絡編程的的主要手段。在以前的介紹中也看到了,咱們使用了一個很重要的抽象HttpMessageHandler,接下來咱們就探究一下HttpClient源碼,並找尋它和HttpMessageHandler的關係到底是怎麼樣的。git

HttpClient源碼解析

    首先咱們找到HttpClient源碼的位置,微軟也提供了專門的網站能夠查找.Net Core源碼有興趣的同窗能夠自行查閱。接下來咱們查閱一下HttpClient的核心代碼。首先,咱們能夠看到HttpClient繼承自HttpMessageInvoker這個類,待會咱們在探究這個類。github

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);
}

經過這裏咱們能夠大體瞭解到。其實大部分最簡單的調用方式,每每都是從最複雜的調用方式,一步步的封裝起來的,只是系統幫咱們初始化了一部分參數,讓咱們按需使用。順着方法一直向下找,最後找到了這裏。ide

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,實現代碼以下this

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

👇歡迎掃碼關注👇
相關文章
相關標籤/搜索