우리의 웹 응용 프로그램은 .Net Framework 4.0에서 실행됩니다. UI는 아약스 호출을 통해 컨트롤러 메소드를 호출합니다.
공급 업체의 REST 서비스를 사용해야합니다. .Net 4.0에서 REST 서비스를 호출하는 가장 좋은 방법을 평가하고 있습니다. REST 서비스에는 기본 인증 체계가 필요하며 XML과 JSON으로 데이터를 리턴 할 수 있습니다. 대용량 데이터를 업로드 / 다운로드 할 필요가 없으며 앞으로는 아무것도 보이지 않습니다. REST 소비를위한 오픈 소스 코드 프로젝트를 몇 가지 살펴 보았고 프로젝트의 추가 종속성을 정당화 할 가치가 없었습니다. 평가하기 시작 WebClient
하고 HttpClient
. NuGet에서 .Net 4.0 용 HttpClient를 다운로드했습니다.
나는 차이를 검색 WebClient
하고 HttpClient
그리고 이 사이트는 그 하나의 HttpClient를 처리 할 수있는 동시 통화를 언급하며 DNS, 쿠키 설정 및 인증을 해결 재사용 할 수 있습니다. 차이로 인해 얻을 수있는 실질적인 가치를 아직 보지 못했습니다.
WebClient
(동기 호출), HttpClient
(동기 및 비동기) 수행 방법을 찾기 위해 빠른 성능 테스트를 수행했습니다. 결과는 다음과 같습니다.
HttpClient
모든 요청에 동일한 인스턴스 사용 (최소-최대)
WebClient 동기화 : 8ms
-167ms
HttpClient 동기화 : 3ms
-7228ms HttpClient 비동기 : 985-10405ms
HttpClient
각 요청에 새로운 사용 (최소-최대)
WebClient 동기화 :
4ms-297ms HttpClient 동기화 : 3ms-7953ms
HttpClient 비동기 : 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
over 를 사용하면 어떤 이점이WebClient
있습니까? - 가
HttpClient
동시성보다 더WebClient
? 테스트 결과WebClient
동기화 호출이 더 잘 수행되는 것을 알 수 있습니다. - 윌
HttpClient
우리가 닷넷 4.5로 업그레이드하는 경우 더 나은 디자인 선택? 성능은 핵심 설계 요소입니다.
답변
나는 F #과 Web API 세계에 살고 있습니다.
웹 API에는 특히 보안을위한 메시지 처리기 등의 많은 좋은 일들이 있습니다.
나는 내 의견이 단 하나의 의견이라는 것을 알고 있지만 앞으로의 작업에 사용하는 것이 좋습니다HttpClient
. 어쩌면 System.Net.Http
그 어셈블리를 직접 사용하지 않고 나오는 다른 조각을 활용할 수있는 방법이있을 수 있지만 현재로서는 어떻게 작동하는지 상상할 수 없습니다.
이 두 가지를 비교하면
- HttpClient는 WebClient보다 HTTP에 더 가깝습니다.
- HttpClient는 보고서 진행, 사용자 지정 URI 체계 및 WebClient가 제공하는 FTP 호출과 같은 것들이 있지만 HttpClient는 그렇지 않기 때문에 Web Client를 완전히 대체하기위한 것이 아닙니다.
+--------------------------------------------+--------------------------------------------+
| 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는 HttpRequest 및 HttpResponse와 같은 HTTP의 서버 측 형제와 매우 대칭입니다.
업데이트 : 새로운 HttpClient API를 사용해야하는 5 가지 이유 :
- 강력하게 입력 된 헤더.
- 공유 캐시, 쿠키 및 자격 증명
- 쿠키 및 공유 쿠키에 액세스
- 캐싱 및 공유 캐시를 제어합니다.
- 코드 모듈을 ASP.NET 파이프 라인에 삽입하십시오. 보다 깨끗하고 모듈화 된 코드.
참고
C # 5.0 조셉 알바 하리
(채널 9 — 비디오 제작 2013)
새로운 HttpClient API를 사용하여 웹 서비스에 연결해야하는 5 가지 이유
답변
HttpClient는 최신 API이며 다음과 같은 이점이 있습니다.
- 좋은 비동기 프로그래밍 모델이 있습니다
- 기본적으로 HTTP 발명자 중 한 명인 Henrik F Nielson이 작업 중이며 API를 설계하여 표준 규격 헤더 생성과 같은 HTTP 표준을 쉽게 따르도록합니다.
- .Net framework 4.5에 있으므로, 예측 가능한 미래에 대해 어느 정도 보장 된 수준의 지원을 제공합니다
- .Net 4.0, Windows Phone 등의 다른 플랫폼에서 사용하려는 경우 xcopyable / portable-framework 버전의 라이브러리도 있습니다.
다른 웹 서비스에 대해 REST 호출을 수행하는 웹 서비스를 작성중인 경우 모든 REST 호출에 대해 비동기 프로그래밍 모델을 사용하여 스레드 기아 상태에 도달하지 않아야합니다. 비동기 / 대기 지원 기능이있는 최신 C # 컴파일러를 사용하고 싶을 수도 있습니다.
참고 : 성능이 뛰어난 AFAIK는 아닙니다. 공정한 테스트를 만들면 아마도 비슷한 성능을 보일 것입니다.
답변
첫째, 나는 특히 WebClient 대 HttpClient의 권위자가 아닙니다. 둘째, 위의 의견에서 WebClient는 Sync ONLY이고 HttpClient는 둘 다 제안하는 것 같습니다.
WebClient (동기화 호출), HttpClient (동기화 및 비 동기화)가 수행되는 방법을 찾기 위해 빠른 성능 테스트를 수행했습니다. 결과는 다음과 같습니다.
나는 미래를 생각할 때, 즉 장기 실행 프로세스, 반응 형 GUI 등 큰 차이로 본다 (프레임 워크 4.5에서 제안한 이점에 추가하십시오-실제 경험에서 IIS에서 훨씬 빠릅니다)
답변
HttpClientFactory
HttpClient를 만들 수있는 다양한 방법을 평가하는 것이 중요하며 그 중 일부는 HttpClientFactory를 이해하는 것입니다.
이것은 내가 아는 직접적인 대답은 아니지만 new HttpClient(...)
모든 곳에서 끝나는 것보다 여기에서 시작하는 것이 좋습니다 .
답변
HttpClient, WebClient, HttpWebResponse 사이에 벤치 마크가 있고 Rest Web Api를 호출합니다.
결과 콜 레스트 웹 API 벤치 마크
——————— 1 단계 —- 10 요청
{00 : 00 : 17.2232544} ====> HttpClinet
{00 : 00 : 04.3108986} ====> 웹 요청
{00 : 00 : 04.5436889} ====> 웹 클라이언트
——————— 1 단계 —- 10 요청-작은 크기
{00 : 00 : 17.2232544} ====> HttpClinet
{00 : 00 : 04.3108986} ====> WebRequest
{00 : 00 : 04.5436889} ====> 웹 클라이언트
——————— 3 단계 —- 10 sync 요청-작은 크기
{00 : 00 : 15.3047502} ====> HttpClinet
{00 : 00 : 03.5505249} ====> 웹 요청
{00 : 00 : 04.0761359} ====> 웹 클라이언트
——————— 4 단계 —- 100 sync 요청-작은 크기
{00 : 03 : 23.6268086} ====> HttpClinet
{00 : 00 : 47.1406632} ====> WebRequest
{00 : 01 : 01.2319499} ====> 웹 클라이언트
——————— 단계 5 —- 10 sync 요청-최대 크기
{00 : 00 : 58.1804677} ====> HttpClinet
{00 : 00 : 58.0710444} ====> WebRequest
{00 : 00 : 38.4170938} ====> 웹 클라이언트
——————— 단계 6 —- 10 동기화 요청-최대 크기
{00 : 01 : 04.9964278} ====> HttpClinet
{00 : 00 : 59.1429764} ====> WebRequest
{00 : 00 : 32.0584836} ====> 웹 클라이언트
_____ 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;
}
답변
아마도 당신은 다른 방식으로 문제에 대해 생각할 수 있습니다. WebClient
와 HttpClient
같은 것을 본질적으로 다른 구현이다. 권장 사항은 응용 프로그램 전체 에서 IoC 컨테이너로 Dependency Injection 패턴 을 구현하는 것 입니다. 저수준 HTTP 전송보다 추상화 수준이 높은 클라이언트 인터페이스를 구성해야합니다. 당신은 모두를 사용하여 구체적인 클래스를 쓸 수 있습니다 및 다음 설정을 통해 구현을 주입하는 IoC 컨테이너를 사용합니다.WebClient
HttpClient
이것이 당신이 할 수있는 것입니다 것은 사이를 전환하는 것 HttpClient
하고 WebClient
쉽게 그래서 당신은 프로덕션 환경에서 객관적으로 테스트 할 수 있음.
따라서 다음과 같은 질문이 있습니다.
.Net 4.5로 업그레이드하면 HttpClient가 더 나은 디자인 선택입니까?
실제로 IoC 컨테이너를 사용하여 두 클라이언트 구현간에 전환하여 객관적으로 답변 할 수 있습니다. 다음은 HttpClient
or에 대한 세부 정보가 포함되지 않은 인터페이스 예 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
.
Dependency Injection은 잘 수행되면 낮은 수준의 의사 결정을 미리 내릴 때 발생하는 문제를 완화하는 데 도움이됩니다. 궁극적으로 정답을 알 수있는 유일한 방법은 실제 환경에서 가장 효과적인 방법을 확인하는 것입니다. 그것은 그 꽤 가능성이 WebClient
일부 고객을 위해 더 잘 작동 할 수 있으며, HttpClient
다른 사람을 위해 잘 작동 할 수 있습니다. 이것이 추상화가 중요한 이유입니다. 즉, 앱의 기본 디자인을 변경하지 않고도 코드를 신속하게 교체하거나 구성으로 변경할 수 있습니다.
답변
2020 년의 인기없는 의견 :
ASP.NET 앱과 관련 하여 다음 WebClient
과 같은 HttpClient
이유로 여전히 선호합니다 .
- 최신 구현에는 비동기 / 대기 가능한 작업 기반 방법이 제공됩니다.
- 더 작은 메모리 공간과 2x-5x 더 빠름 (다른 답변은 이미 언급했습니다)
- ” 응용 프로그램 수명 동안 HttpClient의 단일 인스턴스를 재사용하는 것이 좋습니다 “. 그러나 ASP.NET에는 “응용 프로그램 수명”이 없으며 요청 수명 만 있습니다.
