HttpClient와 WebClient 중에서 결정
웹 응용 프로그램이 에서 실행되고 있습니다.NET Framework 4.0.UI는 Ajax 호출을 통해 컨트롤러 메서드를 호출합니다.
우리는 공급업체의 REST 서비스를 소비해야 합니다.에서 REST 서비스를 호출하는 가장 좋은 방법을 검토하고 있습니다.NET 4.0.REST 서비스에는 기본 인증 체계가 필요하며 XML 및 JSON 모두에서 데이터를 반환할 수 있습니다.
대용량 데이터를 업로드/다운로드할 필요가 없으며 향후에는 아무것도 보이지 않습니다.REST 사용을 위한 몇 가지 오픈 소스 코드 프로젝트를 살펴보았지만 프로젝트의 추가 종속성을 정당화할 만한 가치를 찾지 못했습니다.는 나는평를시다니습했작가다니를 평가하기 했습니다.WebClient
그리고.HttpClient
의 HttpClient를 다운로드했습니다.NuGet의 NET 4.0.
나는 사이의 차이점을 검색했습니다.WebClient
그리고.HttpClient
이 사이트에서는 단일 HttpClient가 동시 호출을 처리하고 해결된 DNS, 쿠키 구성 및 인증을 재사용할 수 있다고 언급했습니다.저는 아직 차이로 인해 우리가 얻을 수 있는 실용적인 가치를 보지 못했습니다.
빠른 성능 테스트를 수행하여 다음과 같은 방법을 찾았습니다.WebClient
인 통화), ( (인통화적통화),인),▁(▁(),HttpClient
(비동기식 및 비동기식)을 수행합니다.결과는 다음과 같습니다.
나는 같은 것을 사용하고 있습니다.HttpClient
모든 요청에 대한 인스턴스(minimum - maximum)입니다.
동기화: - ms WebClient 파일: 8 ms - 167 ms
ms - ms HttpClient 파일: 3 ms - 7228 ms
- 파일: 985 - 10405ms
새항사 사용HttpClient
각 요청(최소 - 최대):
동기화: - ms WebClient 파일: 4 ms - 297 ms
ms - ms HttpClient 파일: 3 ms - 7953 ms
- 파일: 1027 - 10834ms
코드
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);
}
}
}
내 질문
- REST 호출은 허용 가능한 3-4초 후에 반환됩니다.REST 서비스에 대한 호출은 Ajax 호출에서 호출되는 컨트롤러 메서드에서 시작됩니다.우선, 통화는 다른 스레드에서 실행되며 UI를 차단하지 않습니다.그럼, 제가 동기식 통화만 하면 될까요?
- 위의 코드는 나의 로컬 박스에서 실행되었습니다.프로덕션 설정에서는 DNS 및 프록시 조회가 포함됩니다.를 사용할 경우의 이점이 있습니까?
HttpClient
1파운드가WebClient
? - 아이즈
HttpClient
다나은동보다 더 나은WebClient
검사 결과를 보니, 그렇군요.WebClient
동기식 호출이 더 잘 수행됩니다. - 할 것이다
HttpClient
로 업그레이드하면 더 나은 디자인 선택이 됩니다.NET 4.5 ? 성능이 주요 설계 요소입니다.
HttpClient는 최신 API이며 다음과 같은 이점이 있습니다.
- 좋은 비동기 프로그래밍 모델을 가지고 있습니다.
- 기본적으로 HTTP의 발명가 중 한 명인 Henrik F Nielson에 의해 연구되고 있으며, 그는 당신이 HTTP 표준을 쉽게 따를 수 있도록 API를 설계했습니다. 예를 들어 표준 준수 헤더 생성.
- 에 있습니다.NET Framework 4.5. 따라서 가까운 미래에 대한 어느 정도의 지원이 보장됩니다.
- 또한 라이브러리를 다른 플랫폼에서 사용하려는 경우 x복사 가능/휴대용 라이브러리 버전이 있습니다.NET 4.0, 윈도우 폰 등.
다른 웹 서비스에 REST 호출을 하는 웹 서비스를 작성하는 경우 스레드 기아에 빠지지 않도록 모든 REST 호출에 비동기 프로그래밍 모델을 사용해야 합니다.또한 비동기/대기 기능을 지원하는 최신 C# 컴파일러를 사용할 수도 있습니다.
참고: AFAIK는 더 이상 성능이 좋지 않습니다.공정한 테스트를 만들면 어느 정도 비슷한 성능을 발휘할 수 있습니다.
HttpClientFactory
HttpClient를 만들 수 있는 다양한 방법을 평가하는 것이 중요합니다. HttpClientFactory에 대한 이해도 중요합니다.
이것은 내가 아는 직접적인 대답이 아니다 - 하지만 당신은 여기서 시작하는 것이 끝이 나는 것보다 낫습니다.new HttpClient(...)
온통.
ASP에 관한 한.여전히 선호하는 NET 앱WebClient
1파운드가 HttpClient
이유:
- 현대적인 구현에는 비동기/대기 가능한 작업 기반 방법이 포함됩니다.
- 메모리 설치 공간이 더 작고 2-5배 더 빠름(다른 응답자들은 이미 언급함)
- "애플리케이션의 수명 동안 HttpClient의 단일 인스턴스를 재사용"하는 것이 좋습니다.하지만 ASP.NET에는 "애플리케이션 수명"이 없고 요청 수명만 있습니다.ASP에 대한 현재 지침입니다.NET 5는 다음을 사용합니다.
HttpClientFactory
하지만 의존성 주입을 통해서만 사용할 수 있습니다.어떤 사람들은 더 간단한 해결책을 원합니다. - 가장 중요한 것은 MS가 제안하는 바와 같이 HttpClient의 싱글톤 인스턴스를 사용하는 경우 알려진 문제가 있다는 것입니다.예를 들어, DNS 캐싱 문제 - HttpClient는 TTL을 무시하고 DNS를 "영원히" 캐시합니다.그러나 해결 방법이 있습니다.HttpClient의 문제와 혼동에 대해 자세히 알고 싶다면 Microsoft GitHub에서 이 설명을 읽어 보십시오.
첫째, 저는 WebClient와특히 HttpClient.둘째로, 위의 의견을 보면 WebClient는 동기식인 반면 HttpClient는 둘 다 동기식인 것 같습니다.
WebClient(동기식 호출), HttpClient(동기식 및 비동기식)의 성능을 알아보기 위해 빠른 성능 테스트를 수행했습니다.그리고 여기 결과가 있습니다.
저는 미래를 위해 생각할 때, 즉 장기간 실행 중인 프로세스, 대응력이 뛰어난 GUI 등을 고려할 때 큰 차이가 있다고 생각합니다(당신이 제안하는 이점에 추가).NET 프레임워크 4.5 - 실제 경험에 따르면 IIS에서 훨씬 더 빠릅니다.
아마도 당신은 그 문제에 대해 다른 방식으로 생각할 수 있을 것입니다. WebClient
그리고.HttpClient
본질적으로 같은 것의 다른 구현입니다.IoC 컨테이너로 Dependency Injection 패턴을 응용 프로그램 전체에 구현하는 것이 좋습니다.낮은 수준의 HTTP 전송보다 높은 수준의 추상화로 클라이언트 인터페이스를 구성해야 합니다.두 가지를 모두 사용하는 구체적인 수업을 작성할 수 있습니다.WebClient
그리고.HttpClient
그런 다음 IoC 컨테이너를 사용하여 구성을 통해 구현을 주입합니다.
이를 통해 당신이 할 수 있는 것은 두 사람 사이를 전환입니다.HttpClient
그리고.WebClient
운영 환경에서 객관적으로 테스트할 수 있습니다.
다음과 같은 질문이 있습니다.
로 업그레이드하면 HttpClient가 더 나은 설계 선택이 될까요?넷 4.5?
실제로 IoC 컨테이너를 사용하여 두 클라이언트 구현 간을 전환하여 객관적으로 답변할 수 있습니다.은 다은에대한세정포있함어지예다입니의않인스은터에 대한 자세한 내용을 입니다.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
}
사용할 수 있습니다.Task.Run
만들기 위해서WebClient
실행 시 비동기식으로 실행됩니다.
종속성 주입은 잘 수행되면 낮은 수준의 결정을 미리 내려야 하는 문제를 완화하는 데 도움이 됩니다.궁극적으로, 진정한 답을 아는 유일한 방법은 실제 환경에서 시도하고 어떤 것이 가장 잘 작동하는지 보는 것입니다.이 꽤 있습니다WebClient
일부 고객에게는 더 효과적일 수 있습니다.HttpClient
다른 사람들을 위해 더 잘 작동할 수도 있습니다.이것이 추상화가 중요한 이유입니다.에 따라 할 수 입니다."앱 기 본 설 의 계 를 변 즉 경 인 나 있 수 다 거 구 니 습 변 할 경 따 에 라 성 스 하 빠 왑 게 르 하 를 드 지 고 도 코 않 ▁it , ▁app ▁can ▁quickly ▁of 즉 ▁with ▁that 앱 ▁in 의 ▁be ▁code uration ▁changed ▁without ▁config 다 기 ▁changing
BTW: 이러한 낮은 수준의 API 중 하나를 직접 호출하는 대신 추상화를 사용해야 하는 많은 다른 이유가 있습니다.하나는 유닛 테스트 가능성입니다.
HttpClient, WebClient, HttpWebResponse 중에서 벤치마크를 한 다음 REST Web API를 호출했습니다.
결과:
Call REST 웹 API 벤치마크
---------------------Stage 1 ---- 10 Request
{00:00:17.2232544} ====>HttpClinet
{00:00:04.3108986} ====>WebRequest
{00:00:04.5436889} ====>WebClient
---------------------Stage 1 ---- 10 Request--Small Size
{00:00:17.2232544}====>HttpClinet
{00:00:04.3108986}====>WebRequest
{00:00:04.5436889}====>WebClient
---------------------Stage 3 ---- 10 sync Request--Small Size
{00:00:15.3047502}====>HttpClinet
{00:00:03.5505249}====>WebRequest
{00:00:04.0761359}====>WebClient
---------------------Stage 4 ---- 100 sync Request--Small Size
{00:03:23.6268086}====>HttpClinet
{00:00:47.1406632}====>WebRequest
{00:01:01.2319499}====>WebClient
---------------------Stage 5 ---- 10 sync Request--Max Size
{00:00:58.1804677}====>HttpClinet
{00:00:58.0710444}====>WebRequest
{00:00:38.4170938}====>WebClient
---------------------Stage 6 ---- 10 sync Request--Max Size
{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;
//-------------------------Functions
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;
}
언급URL : https://stackoverflow.com/questions/20530152/deciding-between-httpclient-and-webclient
'programing' 카테고리의 다른 글
이벤트 이미터의 올바른 사용 방법은 무엇입니까? (0) | 2023.05.12 |
---|---|
데이터베이스의 컬렉션 수 제한 (0) | 2023.05.12 |
sed가 \t를 탭으로 인식하지 못하는 이유는 무엇입니까? (0) | 2023.05.12 |
콘솔 대신 파일에 로그하도록 Node.js 구성 (0) | 2023.05.12 |
Azure: Azure에서 배포된 파일을 볼 수 있는 방법이 있습니까? (0) | 2023.05.12 |