HttpClientとWebClientの間の決定


218

私たちのWebアプリは.Net Framework 4.0で実行されています。UIはajax呼び出しを通じてコン​​トローラーメソッドを呼び出します。

ベンダーのRESTサービスを利用する必要があります。.Net 4.0でRESTサービスを呼び出す最良の方法を評価しています。RESTサービスは基本認証スキームを必要とし、XMLとJSONの両方でデータを返すことができます。巨大なデータをアップロード/ダウンロードする必要はなく、今後何も表示されません。RESTを使用するためのいくつかのオープンソースコードプロジェクトを調べたところ、プロジェクトに追加の依存関係を正当化するための価値が見つかりませんでした。評価WebClientを開始しましたHttpClient。NuGetから.Net 4.0用のHttpClientをダウンロードしました。

私は間の違いを検索WebClientし、HttpClientそしてこのサイトは、単一のHttpClientは同時コールを処理することができますし、それが解決DNS、クッキーの設定や認証を再利用できることを述べました。違いによって得られる実用的な値はまだわかりません。

WebClient(同期呼び出し)、HttpClient(同期および非同期)のパフォーマンスを確認するために、簡単なパフォーマンステストを行いました。そしてここに結果があります:

HttpClientすべてのリクエストに同じインスタンスを使用する(最小-最大)

WebClient同期:8 ms-167 ms
HttpClient同期:3 ms-7228 ms
HttpClient async:985-10405 ms

HttpClientリクエストごとに新しいものを使用する(最小-最大)

WebClient同期:4 ms-297 ms
HttpClient同期:3 ms-7953 ms
HttpClient async:1027-10834 ms

コード

public class AHNData
{
    public int i;
    public string str;
}

public class Program
{
    public static HttpClient httpClient = new HttpClient();
    private static readonly string _url = "http://localhost:9000/api/values/";

    public static void Main(string[] args)
    {
       #region "Trace"
       Trace.Listeners.Clear();

       TextWriterTraceListener twtl = new TextWriterTraceListener(
           "C:\\Temp\\REST_Test.txt");
       twtl.Name = "TextLogger";
       twtl.TraceOutputOptions = TraceOptions.ThreadId | TraceOptions.DateTime;

       ConsoleTraceListener ctl = new ConsoleTraceListener(false);
       ctl.TraceOutputOptions = TraceOptions.DateTime;

       Trace.Listeners.Add(twtl);
       Trace.Listeners.Add(ctl);
       Trace.AutoFlush = true;
       #endregion

       int batchSize = 1000;

       ParallelOptions parallelOptions = new ParallelOptions();
       parallelOptions.MaxDegreeOfParallelism = batchSize;

       ServicePointManager.DefaultConnectionLimit = 1000000;

       Parallel.For(0, batchSize, parallelOptions,
           j =>
           {
               Stopwatch sw1 = Stopwatch.StartNew();
               GetDataFromHttpClientAsync<List<AHNData>>(sw1);
           });
       Parallel.For(0, batchSize, parallelOptions,
            j =>
            {
                Stopwatch sw1 = Stopwatch.StartNew();
                GetDataFromHttpClientSync<List<AHNData>>(sw1);
            });
       Parallel.For(0, batchSize, parallelOptions,
            j =>
            {
                using (WebClient client = new WebClient())
                {
                   Stopwatch sw = Stopwatch.StartNew();
                   byte[] arr = client.DownloadData(_url);
                   sw.Stop();

                   Trace.WriteLine("WebClient Sync " + sw.ElapsedMilliseconds);
                }
           });

           Console.Read();
        }

        public static T GetDataFromWebClient<T>()
        {
            using (var webClient = new WebClient())
            {
                webClient.BaseAddress = _url;
                return JsonConvert.DeserializeObject<T>(
                    webClient.DownloadString(_url));
            }
        }

        public static void GetDataFromHttpClientSync<T>(Stopwatch sw)
        {
            HttpClient httpClient = new HttpClient();
            var response = httpClient.GetAsync(_url).Result;
            var obj = JsonConvert.DeserializeObject<T>(
                response.Content.ReadAsStringAsync().Result);
            sw.Stop();

            Trace.WriteLine("HttpClient Sync " + sw.ElapsedMilliseconds);
        }

        public static void GetDataFromHttpClientAsync<T>(Stopwatch sw)
        {
           HttpClient httpClient = new HttpClient();
           var response = httpClient.GetAsync(_url).ContinueWith(
              (a) => {
                 JsonConvert.DeserializeObject<T>(
                    a.Result.Content.ReadAsStringAsync().Result);
                 sw.Stop();
                 Trace.WriteLine("HttpClient Async " + sw.ElapsedMilliseconds);
              }, TaskContinuationOptions.None);
        }
    }
}

私の質問

  1. REST呼び出しは3〜4秒で戻りますが、これは許容範囲です。RESTサービスの呼び出しは、ajax呼び出しから呼び出されるコントローラーメソッドで開始されます。まず、呼び出しは別のスレッドで実行され、UIをブロックしません。だから、私は同期呼び出しだけを続けることができますか?
  2. 上記のコードは私のlocalboxで実行されました。製品のセットアップでは、DNSとプロキシのルックアップが関係します。HttpClient以上に使用する利点はありますWebClientか?
  3. あるHttpClient並行処理は、より良いですかWebClient?テスト結果から、WebClient同期呼び出しのパフォーマンスが向上していることがわかり ます。
  4. ウィルHttpClient我々は純4.5にアップグレードする場合は、より良い設計上の選択でありますか?パフォーマンスは重要な設計要素です。

5
テストはGetDataFromHttpClientAsync最初に実行されるため、不公平です。他の呼び出しは、データ(ローカルマシン上、または宛先との間の透過的なプロキシ上にある)を潜在的にキャッシュし、より高速になります。また、適切な条件下では、var response = httpClient.GetAsync("http://localhost:9000/api/values/").Result;スレッドプールスレッドを使い果たしてデッドロックが発生する可能性があります。ThreadPool threadsのスレッドプールに依存するアクティビティをブロックしないでください。await代わりに、スレッドがプールに返されるようにする必要があります。
Scott Chamberlain

1
Web APIクライアントを使用したHttpClientは、JSON / XML RESTクライアントに最適です。
Cory Nelson

@スコット・チェンバレン-お返事ありがとうございます。すべてのテスト呼び出しはParallel.Foreachで実行されるため、どれが最初に実行されるかは保証されません。また、サービスへの最初の呼び出しがGetDataFromHttpClientAsyncからのものであった場合、GetDataFromHttpClientAsyncからの後続のすべての呼び出しはキャッシュの恩恵を受け、より高速に実行されるはずです。結果にはそれがありませんでした。Rgdが待っています。まだ4.0を使用しています。HttpClientを同期させるとデッドロックが発生し、設計上の考慮事項からそのオプションを除外することに同意します。
user3092913 2013

@CoryNelson JSONやXMLのRESTクライアントにとって、Web APIクライアントを備えたHttpClientが素晴らしい理由を詳しく説明していただけますか?
user3092913 2013

2
:ここでのHttpClientとWebクライアントの違いについていくつかの単語ですblogs.msdn.com/b/henrikn/archive/2012/02/11/...
JustAndrei

回答:


243

私はF#とWeb APIの両方の世界に住んでいます。

特にセキュリティのためのメッセージハンドラーなど、Web APIには多くの優れた点があります。

私は私の意見は1つだけだと知っていますがHttpClient、今後の作業にのみ使用することをお勧めします。おそらくSystem.Net.Http、そのアセンブリを直接使用せずに出てくる他の部分を活用する方法があるかもしれませんが、現時点でそれがどのように機能するか想像できません。

これら二つを比較するといえば

  • HttpClientはWebClientよりもHTTPに近いです。
  • レポートの進捗状況、カスタムURIスキーム、WebClientが提供するFTP呼び出しなどがあるため、HttpClientはWebクライアントの完全な置き換えになることを意図していませんでしたが、HttpClientにはありません。
+--------------------------------------------+--------------------------------------------+
|               WebClient                    |               HttpClient                   |
+--------------------------------------------+--------------------------------------------+
| Available in older versions of .NET        | .NET 4.5 only.  Created to support the     |
|                                            | growing need of the Web API REST calls     |
+--------------------------------------------+--------------------------------------------+
| WinRT applications cannot use WebClient    | HTTPClient can be used with WinRT          |
+--------------------------------------------+--------------------------------------------+
| Provides progress reporting for downloads  | No progress reporting for downloads        |
+--------------------------------------------+--------------------------------------------+
| Does not reuse resolved DNS,               | Can reuse resolved DNS, cookie             |
| configured cookies                         | configuration and other authentication     |
+--------------------------------------------+--------------------------------------------+
| You need to new up a WebClient to          | Single HttpClient can make concurrent      |
| make concurrent requests.                  | requests                                   |
+--------------------------------------------+--------------------------------------------+
| Thin layer over WebRequest and             | Thin layer of HttpWebRequest and           |
| WebResponse                                | HttpWebResponse                            |
+--------------------------------------------+--------------------------------------------+
| Mocking and testing WebClient is difficult | Mocking and testing HttpClient is easy     |
+--------------------------------------------+--------------------------------------------+
| Supports FTP                               | No support for FTP                         |
+--------------------------------------------+--------------------------------------------+
| Both Synchronous and Asynchronous methods  | All IO bound methods in                    |
| are available for IO bound requests        | HTTPClient are asynchronous                |
+--------------------------------------------+--------------------------------------------+

.NET 4.5を使用している場合は、Microsoftが開発者に提供するHttpClientで非同期の良さを使用してください。HttpClientは、HTTPのサーバー側の兄弟と非常に対称的で、それらはHttpRequestとHttpResponseです。

更新:新しいHttpClient APIを使用する5つの理由:

  • 強く型付けされたヘッダー。
  • 共有キャッシュ、Cookie、資格情報
  • Cookieおよび共有Cookieへのアクセス
  • キャッシングと共有キャッシュの制御。
  • コードモジュールをASP.NETパイプラインに挿入します。よりクリーンでモジュール化されたコード。

参照

C#5.0ジョセフアルバハリ

(Channel9-ビデオビルド2013)

新しいHttpClient APIを使用してWebサービスに接続する5つの理由

WebClientとHttpClientとHttpWebRequest


4
HttpClientは.NET 4.0でも利用可能であることに注意してください。
トッドメニア2015

2
これは、WebClientがHttpClientよりもはるかに高速に見える理由を説明していません。また、WebClient今非同期メソッドを持っているようです。
クラッシュ

8
@crushは、OPがすべてのリクエストに対してHttpClientの新しいインスタンスを作成しているためです。代わりに、アプリケーションの存続期間中はHttpClientの単一のインスタンスを使用する必要があります。stackoverflow.com/a/22561368/57369を
ガブリエル

6
これの注目に値するはWebClientでは使用できません.Net Coreが、HttpClientです。
Pranav Singh

3
.Net Core 2.0 WebClient(他の何千ものAPIの中でも)が復活し、利用可能になりました。
CoderBang 2017

56

HttpClientは新しいAPIであり、次の利点があります。

  • 優れた非同期プログラミングモデルがある
  • 基本的にHTTPの発明者の一人であるHenrik F Nielsonによって作業されており、彼は、標準に準拠したヘッダーを生成するなど、HTTP標準に従うのが簡単になるようにAPIを設計しました。
  • .NET Framework 4.5に含まれているため、当面のサポートはある程度保証されています
  • 他のプラットフォーム(.Net 4.0、Windows Phoneなど)で使用する場合は、ライブラリのxcopyable / portable-frameworkバージョンもあります。

他のWebサービスへのREST呼び出しを行うWebサービスを作成している場合は、すべてのREST呼び出しに非同期プログラミングモデルを使用して、スレッドが不足しないようにする必要があります。また、async / awaitをサポートする最新のC#コンパイラを使用することもできます。

注:それはより高性能のAFAIKではありません。公正なテストを作成する場合、それはおそらくいくぶん同様に実行されます。


プロキシを切り替える方法があったとしても、それは非常識です
ed22

3

まず、私は特にWebClientとHttpClientの関係ではありません。次に、上記のコメントから、HttpClientは両方であるのに対し、WebClientは同期のみであることが示唆されているようです。

WebClient(同期呼び出し)、HttpClient(同期および非同期)のパフォーマンスを確認するために、簡単なパフォーマンステストを行いました。そしてここに結果があります。

私はそれを将来を考えるときの大きな違い、つまり長時間実行されるプロセス、レスポンシブGUIなどとして見ています(フレームワーク4.5で提案する利点に加えてください-実際の経験ではIISで非常に高速です)。


4
WebClient最新の.NETバージョンには非同期機能があるようです。なぜこれほど大規模なHttpClientよりもパフォーマンスが優れているように見えるのか知りたいのですが。
クラッシュ

1
stackoverflow.com/a/4988325/1662973によると、一方が他方の抽象化であるという事実以外は、同じように見えます。多分、それはオブジェクトがどのように使用/ロードされるかに依存します。最小時間は、webclientが実際にはHttpClientの抽象化であるため、ミリ秒に相当するオーバーヘッドがあるというステートメントをサポートします。フレームワークは、Webクライアントを実際にプールまたは破棄する方法に「卑劣」である可能性があります。
Anthony Horne

3

HttpClientFactory

HttpClientを作成するさまざまな方法を評価することが重要であり、その一部はHttpClientFactoryを理解することです。

https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

これは私が知っている直接的な答えではありませんが、new HttpClient(...)どこにでも到達するよりも、ここから始めた方がよいでしょう。


2

HttpClient、WebClient、HttpWebResponseのベンチマークがあり、Rest Web Apiを呼び出します

と結果Call Rest Web Apiベンチマーク

---------------------ステージ1 ---- 10リクエスト

{00:00:17.2232544} ====> HttpClinet

{00:00:04.3108986} ====> WebRequest

{00:00:04.5436889} ====> WebClient

---------------------ステージ1 ---- 10リクエスト-小サイズ

{00:00:17.2232544} ====> HttpClinet

{00:00:04.3108986} ====> WebRequest

{00:00:04.5436889} ====> WebClient

---------------------ステージ3 ---- 10同期リクエスト-小サイズ

{00:00:15.3047502} ====> HttpClinet

{00:00:03.5505249} ====> WebRequest

{00:00:04.0761359} ====> WebClient

---------------------ステージ4 ---- 100同期要求-小サイズ

{00:03:23.6268086} ====> HttpClinet

{00:00:47.1406632} ====> WebRequest

{00:01:01.2319499} ====> WebClient

---------------------ステージ5 ---- 10同期リクエスト-最大サイズ

{00:00:58.1804677} ====> HttpClinet

{00:00:58.0710444} ====> WebRequest

{00:00:38.4170938} ====> WebClient

---------------------ステージ6 ---- 10同期リクエスト-最大サイズ

{00:01:04.9964278} ====> HttpClinet

{00:00:59.1429764} ====> WebRequest

{00:00:32.0584836} ====> WebClient

_____ WebClientはより高速です()

var stopWatch = new Stopwatch();
        stopWatch.Start();
        for (var i = 0; i < 10; ++i)
        {
            CallGetHttpClient();
            CallPostHttpClient();
        }

        stopWatch.Stop();

        var httpClientValue = stopWatch.Elapsed;

        stopWatch = new Stopwatch();

        stopWatch.Start();
        for (var i = 0; i < 10; ++i)
        {
            CallGetWebRequest();
            CallPostWebRequest();
        }

        stopWatch.Stop();

        var webRequesttValue = stopWatch.Elapsed;


        stopWatch = new Stopwatch();

        stopWatch.Start();
        for (var i = 0; i < 10; ++i)
        {

            CallGetWebClient();
            CallPostWebClient();

        }

        stopWatch.Stop();

        var webClientValue = stopWatch.Elapsed;

// - - - - - - - - - - - - -関数

private void CallPostHttpClient()
    {
        var httpClient = new HttpClient();
        httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
        var responseTask = httpClient.PostAsync("PostJson", null);
        responseTask.Wait();

        var result = responseTask.Result;
        var readTask = result.Content.ReadAsStringAsync().Result;

    }
    private void CallGetHttpClient()
    {
        var httpClient = new HttpClient();
        httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
        var responseTask = httpClient.GetAsync("getjson");
        responseTask.Wait();

        var result = responseTask.Result;
        var readTask = result.Content.ReadAsStringAsync().Result;

    }
    private string CallGetWebRequest()
    {
        var request = (HttpWebRequest)WebRequest.Create("https://localhost:44354/api/test/getjson");

        request.Method = "GET";
        request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;

        var content = string.Empty;

        using (var response = (HttpWebResponse)request.GetResponse())
        {
            using (var stream = response.GetResponseStream())
            {
                using (var sr = new StreamReader(stream))
                {
                    content = sr.ReadToEnd();
                }
            }
        }

        return content;
    }
    private string CallPostWebRequest()
    {

        var apiUrl = "https://localhost:44354/api/test/PostJson";


        HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(new Uri(apiUrl));
        httpRequest.ContentType = "application/json";
        httpRequest.Method = "POST";
        httpRequest.ContentLength = 0;

        using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
        {
            using (Stream stream = httpResponse.GetResponseStream())
            {
                var json = new StreamReader(stream).ReadToEnd();
                return json;
            }
        }

        return "";
    }

    private string CallGetWebClient()
    {
        string apiUrl = "https://localhost:44354/api/test/getjson";


        var client = new WebClient();

        client.Headers["Content-type"] = "application/json";

        client.Encoding = Encoding.UTF8;

        var json = client.DownloadString(apiUrl);


        return json;
    }

    private string CallPostWebClient()
    {
        string apiUrl = "https://localhost:44354/api/test/PostJson";


        var client = new WebClient();

        client.Headers["Content-type"] = "application/json";

        client.Encoding = Encoding.UTF8;

        var json = client.UploadString(apiUrl, "");


        return json;
    }

1
上記のガブリエルのコメントを参照してください。つまり、HttpClientのインスタンスを1つ作成して再利用すると、HttpClientの方がはるかに高速になります。
LT Dan

1

おそらく、別の方法で問題について考えることができます。WebClientそしてHttpClient本質的に同じものの異なる実装です。私がお勧めするのは、アプリケーション全体でIoCコンテナを使用して依存性注入パターンを実装することです。低レベルのHTTP転送よりも抽象化のレベルが高いクライアントインターフェイスを構築する必要があります。との両方を使用する具象クラスを記述し、IoCコンテナを使用して、設定を介して実装を注入できます。WebClientHttpClient

これはあなたができるようになることを切り替えることであろうHttpClientと、WebClient簡単にあなたが本番環境における客観テストすることができること。

のような質問:

.Net 4.5にアップグレードした場合、HttpClientはより良い設計の選択肢になりますか?

IoCコンテナーを使用して2つのクライアント実装を切り替えることにより、実際に客観的に答えることができます。以下は、依存する可能性のあるインターフェースの例で、HttpClientまたはに関する詳細は含まれていませんWebClient

/// <summary>
/// Dependency Injection abstraction for rest clients. 
/// </summary>
public interface IClient
{
    /// <summary>
    /// Adapter for serialization/deserialization of http body data
    /// </summary>
    ISerializationAdapter SerializationAdapter { get; }

    /// <summary>
    /// Sends a strongly typed request to the server and waits for a strongly typed response
    /// </summary>
    /// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
    /// <typeparam name="TRequestBody">The type of the request body if specified</typeparam>
    /// <param name="request">The request that will be translated to a http request</param>
    /// <returns></returns>
    Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(Request<TRequestBody> request);

    /// <summary>
    /// Default headers to be sent with http requests
    /// </summary>
    IHeadersCollection DefaultRequestHeaders { get; }

    /// <summary>
    /// Default timeout for http requests
    /// </summary>
    TimeSpan Timeout { get; set; }

    /// <summary>
    /// Base Uri for the client. Any resources specified on requests will be relative to this.
    /// </summary>
    Uri BaseUri { get; set; }

    /// <summary>
    /// Name of the client
    /// </summary>
    string Name { get; }
}

public class Request<TRequestBody>
{
    #region Public Properties
    public IHeadersCollection Headers { get; }
    public Uri Resource { get; set; }
    public HttpRequestMethod HttpRequestMethod { get; set; }
    public TRequestBody Body { get; set; }
    public CancellationToken CancellationToken { get; set; }
    public string CustomHttpRequestMethod { get; set; }
    #endregion

    public Request(Uri resource,
        TRequestBody body,
        IHeadersCollection headers,
        HttpRequestMethod httpRequestMethod,
        IClient client,
        CancellationToken cancellationToken)
    {
        Body = body;
        Headers = headers;
        Resource = resource;
        HttpRequestMethod = httpRequestMethod;
        CancellationToken = cancellationToken;

        if (Headers == null) Headers = new RequestHeadersCollection();

        var defaultRequestHeaders = client?.DefaultRequestHeaders;
        if (defaultRequestHeaders == null) return;

        foreach (var kvp in defaultRequestHeaders)
        {
            Headers.Add(kvp);
        }
    }
}

public abstract class Response<TResponseBody> : Response
{
    #region Public Properties
    public virtual TResponseBody Body { get; }

    #endregion

    #region Constructors
    /// <summary>
    /// Only used for mocking or other inheritance
    /// </summary>
    protected Response() : base()
    {
    }

    protected Response(
    IHeadersCollection headersCollection,
    int statusCode,
    HttpRequestMethod httpRequestMethod,
    byte[] responseData,
    TResponseBody body,
    Uri requestUri
    ) : base(
        headersCollection,
        statusCode,
        httpRequestMethod,
        responseData,
        requestUri)
    {
        Body = body;
    }

    public static implicit operator TResponseBody(Response<TResponseBody> readResult)
    {
        return readResult.Body;
    }
    #endregion
}

public abstract class Response
{
    #region Fields
    private readonly byte[] _responseData;
    #endregion

    #region Public Properties
    public virtual int StatusCode { get; }
    public virtual IHeadersCollection Headers { get; }
    public virtual HttpRequestMethod HttpRequestMethod { get; }
    public abstract bool IsSuccess { get; }
    public virtual Uri RequestUri { get; }
    #endregion

    #region Constructor
    /// <summary>
    /// Only used for mocking or other inheritance
    /// </summary>
    protected Response()
    {
    }

    protected Response
    (
    IHeadersCollection headersCollection,
    int statusCode,
    HttpRequestMethod httpRequestMethod,
    byte[] responseData,
    Uri requestUri
    )
    {
        StatusCode = statusCode;
        Headers = headersCollection;
        HttpRequestMethod = httpRequestMethod;
        RequestUri = requestUri;
        _responseData = responseData;
    }
    #endregion

    #region Public Methods
    public virtual byte[] GetResponseData()
    {
        return _responseData;
    }
    #endregion
}

完全なコード

HttpClientの実装

実装で非同期的に実行Task.Runするために使用できますWebClient

Dependency Injectionが適切に実行されると、低レベルの決定を前もって行う必要があるという問題を軽減するのに役立ちます。結局のところ、真の答えを知る唯一の方法は、ライブ環境で試してみて、どちらが最も効果的かを確認することです。これはWebClient、一部の顧客にとってはうまく機能する可能性があり、他の顧客にとってはうまく機能する可能性がありHttpClientます。これが、抽象化が重要な理由です。つまり、アプリの基本的なデザインを変更することなく、コードをすばやく入れ替えたり、構成を変更したりできます。


0

2020年からの不人気な意見:

それが来るとASP.NETにアプリ私はまだ好むWebClient超えるHttpClientので:

  1. 最新の実装には非同期/待機可能なタスクベースのメソッドが付属しています
  2. 他のコメンターが推奨するように「アプリケーションの存続期間中にHttpClientの単一のインスタンスを再利用する」ことができないシナリオでは特に、メモリフットプリントが小さく、2倍から5倍高速です(他の回答はすでに言及しています)。そして、ASP.NETはそれらのシナリオの1つです。「アプリケーションのライフタイム」はなく、リクエストのライフタイムのみです。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.