Asp.Net Core 輕鬆學-HttpClient的演進和避坑

来源:https://www.cnblogs.com/viter/archive/2018/12/08/10086997.html
-Advertisement-
Play Games

在 Asp.Net Core 1.0 時代,由於設計上的問題, HttpClient 給開發者帶來了無盡的困擾,用 Asp.Net Core 開發團隊的話來說就是:我們註意到,HttpClient 被很多開發人員不正確的使用。得益於 .Net Core 不斷的版本快速升級;解決方案也一一浮出水面,本... ...


前言

    在 Asp.Net Core 1.0 時代,由於設計上的問題, HttpClient 給開發者帶來了無盡的困擾,用 Asp.Net Core 開發團隊的話來說就是:我們註意到,HttpClient 被很多開發人員不正確的使用。得益於 .Net Core 不斷的版本快速升級;解決方案也一一浮出水面,本文嘗試從各個業務場景去剖析 HttpClient 的各種使用方式,從而在開發中正確的使用 HttpClient 進行網路請求。

1.0時代發生的事情

1.1 在 1.0 時代,部署在 Linux 上的 Asp.Net Core 應用程式進程出現 “套接字資源耗盡” 的異常,該異常通常是由於不停的創建 HttpClient 的實例後產生,因為每創建一個新的 HttpClient 對象,將會消耗一個套接字資源,而在對象使用完成後,由於設計上的問題,即使手動釋放 HttpClient,也極有可能無法釋放套接字資源。

1.2 思考下麵的代碼,在遠古時代,下麵的代碼將會造成 “套接字資源耗盡” 的異常

        public HttpClient CreateHttpClient()
        {
            return new HttpClient();
        }

        // 或者
        public async Task<string> GetData()
        {
            using (var client = new HttpClient())
            {
                var data = await client.GetAsync("https://www.cnblogs.com");
            }

            return null;
        }

1.3 繼而引出了下麵的使用方法,利用靜態對象進行網路請求

        private static HttpClient httpClient = null;
        public HttpClient CreateHttpClient()
        {
            if (httpClient == null)
                httpClient = new HttpClient();

            return httpClient;
        }

1.4 上面使用靜態對象的方式可以避免 “套接字資源耗盡” 的異常,但是,有一個致命的問題,當主機 DNS 更新時,你可能會收到另外一個異常

An error occurred while sending the request. Couldn't resolve host name An error occurred while sending the request. Couldn't resolve host name

1.5 該異常指示無法解析主機名稱,其實就是因為靜態 HttpClient 對象不會隨著主機 DNS 更新而更新,這個時候,你通常需要做的就是:重啟服務!

2. 正確的使用 HttpClient

2.1 時間來到了 .Net Core 的 2.2 時代(其實2.1就可以),官方推薦我們應該使用依賴註入的方式去使用 HttpClient,比如在 Startup.cs 的 ConfigureServices 方法中加入下麵的代碼

        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddHttpClient();
        }

2.2 然後再控制器中通過構造方法註入 HttpClient 對象進行使用

    public class ValuesController : ControllerBase
    {
        private HttpClient httpClient;
        public ValuesController(HttpClient httpClient)
        {
            this.httpClient = httpClient;
        }

        ...
    }

2.3 在新版本的 Asp.Net Core 中,Asp.Net Core 開發團隊引入了 HttpClientFactory

        public HttpClient CreateHttpClient()
        {
            return HttpClientFactory.Create();
        }

2.4 HttpClientFactory 的主要工作就是創建 HttpClient 對象,但是在創建過程中,通過為每個 HttpClient 對象創建一個單獨的清理句柄來對 HttpClient 進行跟蹤和管理,以確保在對象使用完成後能夠及時的釋放網路請求的資源,也就是套接字,具體 HttpClientFactory 內部原理可參考 李志章-DotNetCore深入瞭解之三HttpClientFactory類.

2.5 更重要的是,HttpClientFactory 內部管理著一個連接句柄池,一旦高併發的到來,HttpClientFactory 內句柄池內使用完成但是未被釋放的句柄將被重新使用,雖然使用 HttpClientFactory.Create() 每次都是返回一個新的 HttpClient 對象,但是其背後的管理句柄是可以復用的,換句話說就是 “套接字復用”,而且還不會有 DNS 無法同步更新的問題

2.6 所以現在我們明白了為什麼要使用 HttpClientFactory 來創建 HttpClient 對象

3. 使用類型化的 HttpClient 客戶端

3.1 在常規應用和微服務的應用場景中,都可以使用類型化的客戶端,類型化客戶端這個詞如果不太好理解,那麼你可以理解為為每個業務單獨的使用一個 HttpClient 客戶端,比如獲取天氣預報,思考下麵的代碼

public class WeatherService
{
    private HttpClient httpClient;
    public WeatherService(HttpClient httpClient)
    {
        this.httpClient = httpClient;
        this.httpClient.BaseAddress = new Uri("http://www.weather.com.cn");
        this.httpClient.Timeout = TimeSpan.FromSeconds(30);
    }

    public async Task<string> GetData()
    {
        var data = await this.httpClient.GetAsync("/data/sk/101010100.html");
        var result = await data.Content.ReadAsStringAsync();

        return result;
    }
}

3.2 為了在控制器中更好的使用 WeatherService,我們需要把 WeatherService 註入到服務中

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddHttpClient();
    }

    // 然後,在控制器中使用如下代碼

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private WeatherService weatherService;
        public ValuesController(WeatherService weatherService)
        {
            this.weatherService = weatherService;
        }

        [HttpGet]
        public async Task<ActionResult> Get()
        {
            string result = string.Empty;
            try
            {
                result = await weatherService.GetData();
            }
            catch { }

            return new JsonResult(new { result });
        }
    }

3.3 運行程式,你將得到 北京市 的天氣

{
result: "{"weatherinfo":{"city":"北京","cityid":"101010100","temp":"27.9","WD":"南風","WS":"小於3級","SD":"28%","AP":"1002hPa","njd":"暫無實況","WSE":"<3","time":"17:55","sm":"2.1","isRadar":"1","Radar":"JC_RADAR_AZ9010_JB"}}"
}

3.4 在微服務中,這種做法很常見,而且非常有用,通過使用類型化的客戶端,除了在構造方法中註入 HttpClient 外,我們還可以註入任何需要的東西到 WeatherService 中,更重要的是,可以對業務應用擴展策略,還方便了管理
3.5 在 WeatherService 類型化客戶端中,雖然每次都是創建了一個新的 HttpClient 對象,但是其內部的句柄和其它 HttpClient 是共用同一個句柄池,無需擔心

4.對 HttpClient 應用策略

4.1 下麵說到的策略組件是業內大名鼎鼎的 Polly (波莉),GitHub 地址:https://github.com/App-vNext/Polly
4.2 使用重試策略,參考 Polly 的 Wiki 示例代碼,使用起來非常簡單

首先需要從 NuGet 中引用包
Polly
Polly.Extensions.Http

4.3 接著在 Startup.cs ConfigureServices 方法中應用策略

        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddHttpClient<WeatherService>()
                    .SetHandlerLifetime(TimeSpan.FromMinutes(5))
                    .AddPolicyHandler(policy =>
                    {
                        return HttpPolicyExtensions.HandleTransientHttpError()
                                                   .WaitAndRetryAsync(3,
                                                   retryAttempt => TimeSpan.FromSeconds(2),
                                                   (exception, timeSpan, retryCount, context) =>
                                                   {
                                                       Console.ForegroundColor = ConsoleColor.Yellow;
                                                       Console.WriteLine("請求出錯了:{0} | {1} ", timeSpan, retryCount);
                                                       Console.ForegroundColor = ConsoleColor.Gray;
                                                   });
                    });
        }

4.4 以上代碼表示在請求發生錯誤的情況下,重試 3 次,每次 2 秒,針對高併發的請求,重試請求間隔建議使用隨機值

結語

  • 本章著重介紹了 HttpClient 在 Asp.Net Core 中的前世今生,簡單介紹了使用原理,介紹了各種使用 HttpClient 的方式
  • 介紹了使用了 Polly 對在類型化的客戶端上使用 HttpClient 重試策略,因為對 Polly 理解不夠,其它的策略就不再介紹,大家可以到 Polly 的 Wiki 上深入瞭解
  • 最後通過一個簡單的獲取天氣預報的小實例來演示類型化的客戶端使用場景

示例代碼下載

https://files.cnblogs.com/files/viter/Ron.HttpClientDemo.zip


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 定義二維數組 可以不指定列數 + 獲取行 如果使用第一個例子,這裡就是返回4 + 獲取列 使用第一個例子,這裡就是返回5 + 定義一個對象數組 Book[] books = new Book[50]; 這裡的Book是個實體類,之後的用法與一維數組的用法是一樣的 ...
  • 一. MVC作用 一. SpringMVC 是一個輕量級的,基於請求響應的框架 一. 為什麼使用springMVC a) 性能較struts2好,struts2效率高(包含ActionContext) b) 簡單便捷,易學 c) 天生和spring無縫集成(主要銜接springIOC,AOP) d) ...
  • 網上普遍使用的化學方程式的格式普遍如下 例: KMnO4+FeSO4+H2SO4=Fe2(SO4)3+MnSO4+K2SO4+H2O 要把化學方程式格式化,單單一個正則表達式是非常反人類的,故可選用 C++ 實現化學方程式的格式化。 首先,寫一個 for 迴圈,傳入字元數組 input[] ,依次地 ...
  • 正式開始 首先我在這介紹兩個新的小知識,要在下麵用到。一個是函數 id() ,另一個是運算符 is。id() 函數就是返回對象的記憶體地址;is 是比較兩個變數的對象引用是否指向同一個對象,在這裡請不要和 == 混了,== 是比較兩個變數的值是否相等。 copy 這個詞有兩種叫法,一種是根據它的發音音 ...
  • 這裡主要介紹ANSI C的特性:當執行算術運算時,操作數的類型如果不同,就會發生轉換。數據類型一般朝著精度更高、長度更長的方向轉換,整型數如果轉換為signed不會丟失信息,就轉換為signed,否則轉換為unsigned。 一、算術轉換(K&R C) 首先: 任何類型為char或short的操作數 ...
  • using System; namespace ConsoleApp1 { interface IInterface1 { void ft(); } interface IInterface2 { void ft(); } class MyClass : IInterface1, IInterfa.... ...
  • 本周終於發佈了.NET Core 2.2,ASP.NET Core 2.2以及Entity Framework Core 2.2,雖然更大的新聞可能是.NET Core 3.0的特性公佈,但不妨先將現有的.NET Core版本升級到2.2,及時體驗一下新的功能。 .NET Core 2.2 分層編譯 ...
  • 1、錯誤案例: Index:1 Uncaught ReferenceError: 系統管理 is not defined at HTMLAnchorElement.onclick (Index:1) 2、錯誤寫法 3、正確寫法(加入轉義符號 ' \ ' ) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...