web-dev-qa-db-ja.com

HttpClientとHttpWebRequestにより、パフォーマンス、セキュリティ、接続数を削減

単一のHttpClientを複数のリクエストで共有できることを発見しました。共有され、リクエストが同じ宛先にある場合、複数のリクエストが接続を再利用できます。 WebRequestは、要求ごとに接続を再作成する必要があります。

例でHttpClientを使用する他の方法に関するドキュメントも参照しました。

次の記事では、高速NTLM認証接続共有の概要を説明します。 HttpWebRequest.UnsafeAuthenticatedConnectionSharing

私が試した可能な実装を以下に示します

A)

private WebRequestHandler GetWebRequestHandler()
{
    CredentialCache credentialCache = new CredentialCache();
    credentialCache.Add(ResourceUriCanBeAnyUri, "NTLM", CredentialCache.DefaultNetworkCredentials);
    WebRequestHandler handler = new WebRequestHandler
    {
        UnsafeAuthenticatedConnectionSharing = true,
        Credentials = credentialCache
    };

    return handler;
}

using (HttpClient client = new HttpClient(GetWebRequestHandler(), false))
{
}

B)

using (HttpClient client = new HttpClient)
{
}

C)

HttpWebRequest req = (HttpWebRequest)WebRequest.Create("some uri string")

最大のパフォーマンスを実現し、接続を最小限に抑え、セキュリティが影響を受けないようにするために、どのアプローチを採用する必要があるかを理解していただけると助かります。

非同期でそれらのいずれかを使用する場合、応答を待機しているリソースをブロックせず、良好なスループットが得られるため、パフォーマンスの観点から適切です。

すぐに使用可能な非同期メソッドのため、HttpClientはHttpWebRequestよりも優先され、begin/endメソッドの作成について心配する必要はありません。

基本的に(クラスのいずれかを使用して)非同期呼び出しを使用する場合、応答を待機しているリソースはブロックされず、他の要求はリソースを使用して追加の呼び出しを行います。

「using」ブロックでHttpClientを使用して、他のWeb要求で同じリソースを何度も再利用できるようにしないでください。

詳細については、次のスレッドを参照してください

HttpClientとHttpClientHandlerは破棄する必要がありますか?

21
Sujit Singh

これは、1回だけHttpClientを作成するApiClientです。このオブジェクトをシングルトンとして依存性注入ライブラリに登録します。ステートレスであるため、再利用しても安全です。要求ごとにHTTPClientを再作成しないでください。 Httpclientを可能な限り再利用する

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
//You need to install package Newtonsoft.Json > https://www.nuget.org/packages/Newtonsoft.Json/
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;


public class MyApiClient : IDisposable
{
    private readonly TimeSpan _timeout;
    private HttpClient _httpClient;
    private HttpClientHandler _httpClientHandler;
    private readonly string _baseUrl;
    private const string ClientUserAgent = "my-api-client-v1";
    private const string MediaTypeJson = "application/json";

    public MyApiClient(string baseUrl, TimeSpan? timeout = null)
    {
        _baseUrl = NormalizeBaseUrl(baseUrl);
        _timeout = timeout ?? TimeSpan.FromSeconds(90);   
    }

    public async Task<string> PostAsync(string url, object input)
    {
        EnsureHttpClientCreated();

        using (var requestContent = new StringContent(ConvertToJsonString(input), Encoding.UTF8, MediaTypeJson))
        {
            using (var response = await _httpClient.PostAsync(url, requestContent))
            {
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
        }
    }

    public async Task<TResult> PostAsync<TResult>(string url, object input) where TResult : class, new()
    {
        var strResponse = await PostAsync(url, input);

        return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });
    }

    public async Task<TResult> GetAsync<TResult>(string url) where TResult : class, new()
    {
        var strResponse = await GetAsync(url);

        return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });
    }

    public async Task<string> GetAsync(string url)
    {
        EnsureHttpClientCreated();

        using (var response = await _httpClient.GetAsync(url))
        {
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
    }

    public async Task<string> PutAsync(string url, object input)
    {
        return await PutAsync(url, new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, MediaTypeJson));
    }

    public async Task<string> PutAsync(string url, HttpContent content)
    {
        EnsureHttpClientCreated();

        using (var response = await _httpClient.PutAsync(url, content))
        {
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
    }

    public async Task<string> DeleteAsync(string url)
    {
        EnsureHttpClientCreated();

        using (var response = await _httpClient.DeleteAsync(url))
        {
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
    }

    public void Dispose()
    {
        _httpClientHandler?.Dispose();
        _httpClient?.Dispose();
    }

    private void CreateHttpClient()
    {
        _httpClientHandler = new HttpClientHandler
        {
            AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
        };

        _httpClient = new HttpClient(_httpClientHandler, false)
        {
            Timeout = _timeout
        };

        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);

        if (!string.IsNullOrWhiteSpace(_baseUrl))
        {
            _httpClient.BaseAddress = new Uri(_baseUrl);
        }

        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
    }

    private void EnsureHttpClientCreated()
    {
        if (_httpClient == null)
        {
            CreateHttpClient();
        }
    }

    private static string ConvertToJsonString(object obj)
    {
        if (obj == null)
        {
            return string.Empty;
        }

        return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });
    }

    private static string NormalizeBaseUrl(string url)
    {
        return url.EndsWith("/") ? url : url + "/";
    }
}

使用法;

using ( var client = new MyApiClient("http://localhost:8080"))
{
    var response = client.GetAsync("api/users/findByUsername?username=alper").Result;
    var userResponse = client.GetAsync<MyUser>("api/users/findByUsername?username=alper").Result;
}
1
Alper Ebicoglu
  1. 実装 'A'に問題があります。 GetWebRequestHandler()から返されるインスタンスの有効期間は短命です(たぶん例のためだけでしょうか?)。これが意図的に行われた場合、falseコンストラクターの2番目のパラメーターのHttpClientの受け渡しを無効にします。 falseの値は、基になるHttpMessageHandlerを破棄しないようにHttpClientに指示します(要求のポートを閉じないため、スケーリングに役立ちます)。もちろん、これはHttpMessageHandlerのライフタイムが、ポートを開いたり閉じたりしない利点を利用するのに十分な長さであることを前提としています(これはサーバーのスケーラビリティに大きな影響を与えます)。したがって、オプション「D」について以下の推奨事項があります。

  2. 上記にリストされていないオプション「D」もあります-Httpclientインスタンスstaticを作成し、すべてのAPI呼び出しで再利用します。これは、メモリ割り当てとGCの観点から、さらにクライアントのポートを開くことからもはるかに効率的です。 HttpClientインスタンス(およびそのすべての基本オブジェクト)にメモリを割り当てて作成するオーバーヘッドがないため、GCによるクリーンアップも回避できます。

同様の質問について提供された私の答えを参照してください- WebAPIクライアントの呼び出しごとに新しいHttpClientを作成するオーバーヘッドは何ですか?

0
Dave Black