検索
タグ
ASP.NET
.NET
ASP.NET MVC
Visual Studio
F#
Azure
ASP.NET Core
ライトニングトーク
Plone
Selenium
AJAX
C#
jQuery
SQL Server
JavaScript
ADO.NET Entity Framework
EFCore
WebMatrix
LINQ
Fizz-Buzz
カテゴリ
最新の記事
最新のコメント
記事ランキング
最新のトラックバック
以前の記事
2024年 11月 2024年 10月 2024年 09月 2024年 08月 2024年 04月 2024年 03月 2024年 02月 2024年 01月 2023年 12月 2023年 11月 2023年 10月 2023年 09月 2023年 08月 2023年 07月 2023年 06月 2023年 05月 2023年 04月 2023年 03月 2023年 02月 2023年 01月 2022年 12月 2022年 11月 2022年 10月 2022年 09月 2022年 08月 2022年 07月 2022年 06月 2022年 05月 2022年 04月 2022年 03月 2022年 02月 2022年 01月 2021年 12月 2021年 11月 2021年 10月 2021年 09月 2021年 08月 2021年 07月 2021年 06月 2021年 05月 2021年 04月 2021年 03月 2021年 02月 2021年 01月 2020年 12月 2020年 11月 2020年 10月 2020年 09月 2020年 08月 2020年 07月 2020年 06月 2020年 05月 2020年 04月 2020年 03月 2020年 02月 2020年 01月 2019年 12月 2019年 11月 2019年 10月 2019年 09月 2019年 08月 2019年 07月 2019年 06月 2019年 05月 2019年 04月 2019年 03月 2019年 02月 2019年 01月 2018年 12月 2018年 11月 2018年 10月 2018年 09月 2018年 08月 2018年 07月 2018年 06月 2018年 05月 2018年 04月 2018年 03月 2018年 02月 2018年 01月 2017年 12月 2017年 11月 2017年 10月 2017年 09月 2017年 08月 2017年 07月 2017年 06月 2017年 05月 2017年 04月 2017年 02月 2017年 01月 2016年 12月 2016年 11月 2016年 10月 2016年 09月 2016年 08月 2016年 07月 2016年 06月 2016年 05月 2016年 04月 2016年 03月 2016年 02月 2016年 01月 2015年 12月 2015年 11月 2015年 10月 2015年 09月 2015年 08月 2015年 07月 2015年 05月 2015年 04月 2015年 03月 2015年 02月 2015年 01月 2014年 12月 2014年 11月 2014年 10月 2014年 09月 2014年 08月 2014年 06月 2014年 04月 2014年 03月 2014年 02月 2014年 01月 2013年 12月 2013年 10月 2013年 09月 2013年 08月 2013年 07月 2013年 06月 2013年 05月 2013年 04月 2013年 03月 2013年 02月 2013年 01月 2012年 12月 2012年 11月 2012年 10月 2012年 09月 2012年 08月 2012年 07月 2012年 06月 2012年 05月 2012年 04月 2012年 03月 2012年 02月 2012年 01月 2011年 12月 2011年 11月 2011年 10月 2011年 09月 2011年 08月 2011年 07月 2011年 06月 2011年 05月 2011年 04月 2011年 03月 2011年 02月 2011年 01月 2010年 12月 2010年 11月 2010年 10月 2010年 09月 2010年 08月 2010年 07月 2010年 06月 2010年 05月 2010年 04月 2010年 03月 2010年 02月 2010年 01月 2009年 12月 2009年 10月 2009年 09月 2009年 07月 2009年 06月 2009年 05月 2009年 04月 2009年 03月 2009年 02月 2009年 01月 2008年 12月 2008年 11月 2008年 10月 2008年 09月 2008年 08月 2008年 07月 2008年 06月 2008年 05月 2008年 04月 2008年 03月 2008年 02月 2008年 01月 2007年 12月 2007年 11月 2007年 04月 2007年 03月 2007年 02月 2007年 01月 2006年 11月 2006年 10月 2006年 09月 2006年 08月 2006年 07月 |
2023年 11月 30日
.NET プログラミングにおける、HTTP 要求送信に使われる、System.Net.Http 名前空間の HttpClient クラスの話。 HttpClient クラスには TimeSpan 型の "Timeout" というパブリックプロパティが設けられている。下記公式ドキュメントの説明に依れば「要求がタイムアウトするまで待機する期間を取得または設定」するプロパティであるとのこと。 しかし自分は、この HttpClient.Timeout プロパティによる "HTTP 要求のタイムアウト (時間切れ)" がどのように・何に対して機能するのかよくわかっていなかった。相手先サーバーとの TCP 接続が確立されるまでの時間なのだろうか? 接続が確立してから最初の応答バイト列が返ってくるまでの時間なのだろうか? ある程度応答が返ってきても、すべての応答が返却し終わる前に暫し応答が来なくなったらそれはタイムアウトになるのだろうか? 今までちゃんと理解しないで HttpClient を使ってきたので、今回、改めて調べてみることにした。なお、.NET ランタイムは GtHub 上でソースコードが公開されているので (下記リンク先)、ソースコードを確認すればこれら挙動について理解できることとも思う。 だが今回は実験方式で、 ASP.NET Core Minimal API による HTTP Web API サーバーと、その API サーバーに HttpClient を使って HTTP 要求を送信するコンソールアプリケーションとを C# でそれぞれ実装して、実際に実行して動作を観察してみた。 API サーバー側の C# ソースコードは以下。"/hello" の URL パスに HTTP GET 要求を送ると、"World!" の文字列が返却される。 var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/hello", () => "World!"); app.Run(); コンソールアプリケーション側の C# ソースコードは以下。前述の API サーバーの "/hello" へ HTTP GET 要求で文字列取得を試みるコードだ。以下では HttpClient の Timeout には 3 秒を設定してある。 using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(3), BaseAddress = new Uri("http://localhost:****") }; var message = await httpClient.GetStringAsync("hello"); Console.WriteLine($"message: [{message}]"); TCP 接続が確立できないとき接続先のホスト上でこの API サーバーが起動していない場合や、指定した IP アドレスではホストが TCP ネットワーク上に存在しない場合など、そもそも TCP 接続が確立しない場合を確認してみた。 この場合、HttpClient の Timeout プロパティに指定した時間が経過したのち、TaskCanceledException 例外が発生した。 Unhandled exception. System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 3 seconds elapsing. ---> System.TimeoutException: A task was canceled. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled. なお、HttpClient からの HTTP 要求送信開始後 (GetStringAsync 呼び出し後)、Timeout プロパティに指定した時間が経過してしまう前に、API サーバー側プロセスを起動するなどして TCP 確立が成功する状態にすると、例外は発生せずに要求は成功した。 TCP 接続は成功するが、最初の応答がすぐに返らないときAPI サーバー側実装を以下のように変え、最初の応答を返すまで指定時間待機するようにしてみた。 app.MapGet("/hello", async () => { await Task.Delay(4000); // 4秒待つ return "World!"; }); 念のため curl コマンドで "curl -i http://localhost:***/hello" を実行し、応答ボディのみならず最初の応答ヘッダも含めて、Task.Delay() で指定した時間、何の応答も返らないことを確認する。 この状態でコンソールアプリ側を実行すると、HttpClient の Timeout プロパティに指定した時間が経過したのち、TaskCanceledException 例外が発生した。例外中のコールスタックが、そもそも TCP 接続が確立できなかった場合に比べて微妙に変わっている。 Unhandled exception. System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 3 seconds elapsing. ---> System.TimeoutException: The operation was canceled. ---> System.Threading.Tasks.TaskCanceledException: The operation was canceled. ---> System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request. TCP 接続は成功し、最初の応答もすぐ返るが、すべての応答に時間がかかるときさらに API サーバー側実装を以下のように変え、最初の応答はすぐに返るが、すべての応答を実施しきるまでに数百ミリ秒の待機を挟みながら、だらだら応答を続けるようにしてみる。 var utf8 = Encoding.UTF8; app.MapGet("/hello", async (HttpResponse res) => { foreach (var term in new[] { "lorem", "ipsum", "dolor", "sit", "amet" }) { await res.BodyWriter.WriteAsync(utf8.GetBytes(term + " ")); await res.BodyWriter.FlushAsync(); await Task.Delay(600); } }); こちらも念のため curl コマンドで動作を確認しておく。 この状態でコンソールアプリ側を実行すると、この場合も同じく、HttpClient の Timeout プロパティに指定した時間が経過したのち、TaskCanceledException 例外が発生した。 Unhandled exception. System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 3 seconds elapsing. ---> System.TimeoutException: The operation was canceled. ---> System.Threading.Tasks.TaskCanceledException: The operation was canceled. ---> System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request. まとめ以上の実験と観察結果を見ると、どうやら、HttpClient の Timeout プロパティに指定したタイムアウト時間というのは、「HTTP 要求を開始してから、すべての応答を受信し終わるまでの時間全体」を指しているらしい。 そもそも TCP 接続が確立できないままタイムアウト時間が経過した場合はもちろん、HTTP 応答が断片的に返って来続けている場合でも、Timeout プロパティに指定されたタイムアウト時間が経過してしまったら、TaskCanceledException 例外で中断されるようだ。また、タイムアウトが発生した場合は、発生理由の如何に依らず一律、TaskCanceledException 例外のスローという形で表現されることもわかった。 なお、今回は HttpClient の GetStringAsync メソッドに基づく実験だったが、GetStreamAsync で応答ストリームを入手してそのストリームから読み取りを繰り返すような場合は、もしかするともう少し違った挙動になるかもしれない。そうでないと、MJPEG をはじめ、HTTP 上でのストリーミング通信ができないのではないかと想像される。 今後要確認である。
by developer-adjust
| 2023-11-30 08:35
| .NET
|
ファン申請 |
||