文章目錄
個別 HttpClient request 使用不同 Timeout 時間
在透過 HttpClient 與 partner 介接時,常會使用同一個 named-client 來建立 instance,這也是可以共用 pool 與 存留期設定的推薦方式,雖然可以在 AddHttpClient
時加上指定 Timeout
的方式來針對該 named-client 設定 Timeout 但如此一來同樣的 named-client instance 都會套用同一個 Timeout
設定,如果遇到同個 named-client 但對於不同 request 需要有不同 Timeout 時間就沒辦法滿足,今天就來紀錄一下在同個 named-client instance 設定不同 Timeout 的做法
基本環境說明
- macOS Big Sur 11.5.1
- .NET Core SDK 5.0.202
ASP.NET Core Web Api 預設專案範本
server (修改
WeatherForecastController.cs
模擬不同 response time)[HttpGet("test1")] public IEnumerable<WeatherForecast> Get1() { var rng = new Random(); Thread.Sleep(500); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } [HttpGet("test2")] public IEnumerable<WeatherForecast> Get2() { var rng = new Random(); Thread.Sleep(1500); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } [HttpGet("test3")] public IEnumerable<WeatherForecast> Get3() { var rng = new Random(); Thread.Sleep(2500); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }
client (修改
Startup.cs
的ConfigureServices
)services.AddHttpClient( "cts", c => { c.BaseAddress = new Uri("http://localhost:5000/"); c.Timeout= TimeSpan.FromSeconds(30); } );
設定方式
建立 httpclient instance (與一般用法無異)
private readonly HttpClient _httpClient; public WeatherForecastControlle(ILogger<WeatherForecastController> logger,IHttpClientFactoryhttpClientFactory) { _logger = logger; _httpClient = httpClientFactory.CreateClient("cts"); }
針對不同 request 使用不同 Timeout:使用
CancellationTokenSource
關於 CancellationTokenSource 可以參考 [Microsoft Docs] CancellationTokenSource 類別
HttpClient 的
Timeout
與CancellationTokenSource
的 Timeout 都設定的情況,會採用時間較短的設定值程式碼
var _timeout = 2000;//Timeout 時間(毫秒) var cts = new CancellationTokenSource(_timeout); var result = await _httpClient.GetAsync($"{request_target}", cts.Token);
實際案例
[HttpGet] [Route("test1")] public async Task<string> Get1() { var cts1S = new CancellationTokenSource(1000); var result = await _httpClient.GetAsync("test1" : target, cts1S.Token); return await result.Content.ReadAsStringAsync(new CancellationToken()); } [HttpGet] [Route("test2")] public async Task<string> Get2() { var cts2S = new CancellationTokenSource(2000); var result = await _httpClient.GetAsync("test2", cts2S.Token); return await result.Content.ReadAsStringAsync(new CancellationToken()); } [HttpGet] [Route("test3")] public async Task<string> Get3() { var cts3S = new CancellationTokenSource(3000); var result = await _httpClient.GetAsync("test3", cts3S.Token); return await result.Content.ReadAsStringAsync(new CancellationToken()); }
對應的 endpoint 都可以正常服務
使用短 Timeout 連線長時間 response 模擬斷線
程式碼
使用
1000
毫秒 timeout 去請求 sleep 2500 的 api (test3)[HttpGet [Route("test1")] public async Task<string> Get1(string target = "") { var cts1S = new CancellationTokenSource(1000); var result = await _httpClient.GetAsync(string.IsNullOrWhiteSpace(target) ? "test1" : target, cts1S.Token); return await result.Content.ReadAsStringAsync(new CancellationToken()); }
斷線錯誤
心得
原本我也在懷疑是不是有必要這麼細膩地控制不同 request 的 Timeout 時間,覺得統一設定個 20 秒就夠了吧,如果 20 秒沒辦法回應就以最長的 response 時間為主,但後來遇到 DNS 解析異常,從一開始秒回的 api 就開始卡:統一使用較長 response time 的設定該系統除錯加上了一定難度也錯過最快找到問題的機會
不過針對每個 api 設定不同的 Timeout 也是有風險的:必需持續觀察並評估調整,避免因為上游調整或是異常讓 response time 增加而造成本地系統誤判
至於需不需要針對每個 api 設定 Timeout 還是得看每個系統的特性,站在個人立場是支持的,原因是最小知識原則:單一 api 的 request 原本就不用也不該知道其他 api 的狀況
原始程式碼請參考:[GitHub]yowko/CancellationTokenSourceForHttpclient
參考資訊
文章作者 Yowko Tsai
上次更新 2021-08-25
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。