WebApiClient的SteeltoeOSS.Discovery擴展


1 背景

從園子里看到一些朋友在某些項目開發中,選擇的架構是spring cloud搭建底層微服務框架,dotnet core來編寫業務邏輯,SteeltoeOSS.Discovery是dotnet和spingcloud的橋梁,為dotnet提供服務注冊和服務發現相關功能。在閱讀朋友們文章的時候,我發現相關代碼里的一些HttpClient相關問題,同時對dotnet寄居於spingcloud下由於沒有Feign而產生的那些丑陋的http請求代碼進行思考。本文將圍繞原生的HttpClient的創建與釋放的正確姿勢和使用WebApiClient讓dotnet也有媲美Feign的服務客戶端兩個面展開。

2 正確使用HttpClient

2.1 HttpClient的創建和釋放

HttpClient有三個構造函數,最終都是調用到public HttpClient(HttpMessageHandler handler, bool disposeHandler)這個函數,HttpClient除了其handler參數之外,本身沒有使用到需要Dispose的資源,其實現的IDispose也是為了Dispose掉handler參數而已。

HttpMessageHandler是一個抽象類,目前主要的HttpMessageHandler具體類型有HttpClientHanlder、SocketsHttpHandler和WebRequestHandler,但HttpClientHanlder在dotnet core2.1下是對和SocketsHttpHandler的包裝實現。除了這些主要HttpMessageHandler,還有一個抽象的DelegatingHandler類型,用於實現請求管道,影響請求前后的數據邏輯。HttpClient的默認構造器,使用了HttpClientHanlder類型,同時disposeHandler為true,這時如果對HttpClient實例Dispose了,其內部的HttpClientHanlder自然也被Dispose了,

正確的創建和釋放HttpClient例子

  1. 默認構造器
var httpClient = new HttpClient();
...你的代碼...
httpClient.Dispose();
  1. HttpClient控制HttpMessageHandler
var handler = new HttpClientHandler();
var httpClient = new HttpClient(handler, true);
...你的代碼...
httpClient.Dispose();
  1. HttpClient不控制外部HttpMessageHandler
var handler = 從外部來的HttpMessageHandler;
var httpClient = new HttpClient(handler, false);
...你的代碼...

// 這里調用httpClient.Dispose()是無效的
// handler的生命周期應該由它的創建者來維護
// 如果這里Dispose掉handler,其它使用了這個handler的HttpClient實例受影響

不正確的創建和釋放HttpClient的例子

  1. HttpMessageHandler被創建了,但沒有釋放
private readonly DiscoveryHttpClientHandler _handler;
private const string ProductUrl = "http://product/api/values";

public ValuesController(IDiscoveryClient client, ILoggerFactory logFactory)
{
    _handler = new DiscoveryHttpClientHandler(client);
}

[HttpGet("product")]
public async Task<string> GoProductAsync()
{
    var client = new HttpClient(_handler, false);
    return await client.GetStringAsync(ProductUrl);
}

2.2 HttpClient的生命周期

HttpClient在設計之初,是非常適合使用單例模式的,也就是在應用域中,只維護一份HttpClient的實例就夠了,因為它天生支持向不同域名同時多個並發請求。但單例的HttpClient沒有遵守DNS 生存時間 (TTL) 設置,如果其HttpClientHandler第一次請求到www.baidu.com指向的ip為123.123.123.123,中途這個域名解析到的ip變化了,但HttpClient並不會自動應用這些變化。

asp.net core里,微軟創建一個HttpClientFactory項目,用於提供HttpClient的創建和生命周期自動管理,完美解決到底選擇單例還是每個請求創建和釋放HttpClient這個左右難為的問題。所以在asp.net core項目開發中,請別再寫手動new HttpClient了,所有HttpClient的實例,都要由HttpClientFactory來創建,所有的外部HttpMessageHandler,也應該配置到HttpClientFactory,讓它與HttpClient關聯起來。

HttpClientFactory有三種使用方式:

  1. Using HttpClientFactory Directly
  2. Named Clients
  3. Typed Clients

具體的使用,可以查看3 ways to use HTTPClientFactory in ASP.NET Core 2.1這篇好文。

3 寄居下也有Feign

雖然已經講解了怎么new一個HttpClient,怎么利用HttpClientFactory,但如果要寄居在spingcloud下,你還是得為請求一個服務接口編寫大量的代碼,這在java的Feign前面如同馬車見到寶馬。如果能利用WebApiClient這把利劍,結合HttpClientFactory與DiscoveryHttpClientHandler,你也能變成寶馬。將這三者有機結合起來的項目,叫WebApiClient.Extensions.DiscoveryClient ,它是WebApiClient的SteeltoeOSS.Discovery擴展項目,使用非常簡單。

3.1 Nuget引用

PM> install-package WebApiClient.Extensions.DiscoveryClient

3.2 聲明微服務的WebApiClient調用接口

[HttpHost("http://NET-API")]
public interface INetApi : IHttpApi
{
    [HttpGet("api/values")]
    ITask<string[]> GetValuesAsync();

    [HttpGet("api/values/{id}")]
    ITask<string> GetValuesAsync(int id);
}

3.3 Startup相關配置

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddDiscoveryClient(Configuration);
    services.AddDiscoveryTypedClient<INetApi>();
    ...
}


// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    app.UseDiscoveryClient();
}
3.4 Controller
public class HomeController : Controller
{
    public async Task<string> Index([FromServices]INetApi netApi, int id = 0)
    {
        var values = await netApi.GetValuesAsync();
        var value = await netApi.GetValuesAsync(id);
        return "ok";
    }
}

4. 結束語

本博主對HttpClient有着比較深入的了解,一直在維護WebApiClient項目,期間走過許許多多的“坑”,如果你有遇到HttpClient的相關問題,歡迎一起討論解決。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM