Выбор между HttpClient и WebClient
Наше веб-приложение работает в.Net Framework 4.0. Пользовательский интерфейс вызывает методы контроллера через вызовы ajax.
Нам нужно воспользоваться услугой REST от нашего поставщика. Я оцениваю лучший способ вызвать службу REST в.Net 4.0. Службе REST требуется базовая схема аутентификации, и она может возвращать данные как в формате XML, так и в формате JSON. Нет необходимости загружать / скачивать огромные данные, и я ничего не вижу в будущем. Я взглянул на несколько проектов с открытым исходным кодом для потребления REST и не нашел в них никакой ценности, чтобы оправдать дополнительную зависимость в проекте. Начал оценивать WebClient
а также HttpClient
, Я скачал HttpClient для.Net 4.0 с NuGet.
Я искал различия между WebClient
а также HttpClient
и на этом сайте упоминалось, что один HttpClient может обрабатывать одновременные вызовы и может повторно использовать разрешенный DNS, конфигурацию cookie и аутентификацию. Мне еще предстоит увидеть практические ценности, которые мы можем получить из-за различий.
Я сделал быстрый тест производительности, чтобы найти, как WebClient
(синхронизация звонков), HttpClient
(синхронизировать и асинхронно) выполнить. и вот результаты:
Используя то же самое HttpClient
экземпляр для всех запросов (мин. - макс.)
Синхронизация WebClient: 8 мс - 167 мс
Синхронизация HttpClient: 3 мс - 7228 мс
HttpClient async: 985 - 10405 мс
Используя новый HttpClient
за каждый запрос (мин. - макс.)
Синхронизация WebClient: 4 мс - 297 мс
Синхронизация HttpClient: 3 мс - 7953 мс
HttpClient async: 1027 - 10834 мс
Код
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. Начнем с того, что вызовы выполняются в другом потоке и не блокируют пользовательский интерфейс. Итак, я могу просто придерживаться синхронизации вызовов?
- Приведенный выше код был запущен в моей локальной коробке. В настройке prod будут задействованы DNS и прокси. Есть ли преимущество использования
HttpClient
надWebClient
? - Является
HttpClient
параллелизм лучше чемWebClient
? Из результатов теста я вижуWebClient
синхронизация звонков работает лучше. - Будет
HttpClient
быть лучшим выбором дизайна, если мы перейдем на.Net 4.5? Производительность является ключевым фактором дизайна.
3 ответа
Я живу в мире F# и Web API.
С Web API происходит много хорошего, особенно в форме обработчиков сообщений для безопасности и т. Д.
Я знаю, что мое мнение только одно, но я бы порекомендовал использовать только HttpClient
для любой будущей работы. Возможно, есть какой-то способ использовать некоторые из других частей, выходящих из System.Net.Http
без использования этой сборки напрямую, но я не могу представить, как это будет работать в настоящее время.
Говоря о сравнении этих двух
- HttpClient ближе к HTTP, чем WebClient.
- HttpClient не предназначался для полной замены веб-клиента, поскольку существуют такие вещи, как ход выполнения отчета, настраиваемая схема URI и выполнение FTP-вызовов, которые предоставляет WebClient, но HttpClient этого не делает.
Если вы используете.NET 4.5, пожалуйста, используйте асинхронные свойства с HttpClient, который Microsoft предоставляет разработчикам. HttpClient очень симметричен по отношению к собратьям на стороне сервера HTTP, а именно HttpRequest и HttpResponse.
Обновление: 5 причин использования нового API HttpClient:
- Сильно типизированные заголовки.
- Общие кэши, файлы cookie и учетные данные
- Доступ к файлам cookie и общим файлам cookie
- Контроль над кешированием и общим кешем.
- Вставьте ваш модуль кода в конвейер ASP.NET. Более чистый и модульный код.
ссылка
C# 5.0 Джозеф Албахари
(Channel9 - Video Build 2013)
Пять великих причин использовать новый HttpClient API для подключения к веб-сервисам
HttpClient является более новым из API, и он имеет преимущества
- имеет хорошую модель асинхронного программирования
- над ним работал Генрик Ф. Нильсон, который в основном является одним из изобретателей HTTP, и он разработал API, чтобы вам было легко следовать стандарту HTTP, например, создавать заголовки, соответствующие стандартам.
- находится в.Net Framework 4.5, поэтому он имеет некоторый гарантированный уровень поддержки на обозримое будущее
- также имеет версию библиотеки xcopyable/portable-framework, если вы хотите использовать ее на других платформах - .Net 4.0, Windows Phone и т. д.
Если вы пишете веб-сервис, который выполняет вызовы REST для других веб-сервисов, вы должны использовать модель асинхронного программирования для всех ваших вызовов REST, чтобы вы не столкнулись с истощением потоков. Возможно, вы также захотите использовать новейший компилятор C#, который имеет поддержку async/await.
Примечание: это не более производительный AFAIK. Вероятно, это будет примерно так же эффективно, если вы создадите честный тест.
Непопулярное мнение с 2020 года:
Когда дело доходит до приложений ASP.NET, я по-прежнему предпочитаюWebClient
над HttpClient
потому как:
- Современная реализация поставляется с асинхронными / ожидаемыми методами на основе задач.
- Имеет меньший объем памяти и в 2-5 раз быстрее (это уже упоминается в других ответах)
- Предлагается "повторно использовать один экземпляр HttpClient на протяжении всего срока службы вашего приложения". Но ASP.NET не имеет "времени жизни приложения", только время жизни запроса.
HttpClientFactory
Важно оценить различные способы создания HttpClient, и частью этого является понимание HttpClientFactory.
Я знаю, что это не прямой ответ, но лучше начать с этого, чем закончить new HttpClient(...)
везде.
Во-первых, я не являюсь авторитетом в WebClient против HttpClient, в частности. Во-вторых, из ваших комментариев выше, кажется, можно предположить, что WebClient синхронизирован ТОЛЬКО, тогда как HttpClient - оба.
Я провел быстрый тест производительности, чтобы выяснить, как работают WebClient (синхронизация вызовов), HttpClient (синхронизация и асинхронизация). и вот результаты.
Я вижу в этом огромную разницу, думая о будущем, то есть о длительно запущенных процессах, адаптивном графическом интерфейсе и т. Д. (Добавьте к преимуществу, которое вы предлагаете с помощью платформы 4.5, которая в моем реальном опыте значительно быстрее в IIS)
Возможно, вы могли бы подумать о проблеме по-другому. WebClient
а также HttpClient
по сути, являются разными реализациями одного и того же. Я рекомендую реализовать шаблон внедрения зависимостей с контейнером IoC во всем приложении. Вы должны создать клиентский интерфейс с более высоким уровнем абстракции, чем передача HTTP низкого уровня. Вы можете написать конкретные классы, которые используют обаWebClient
а также HttpClient
, а затем используйте контейнер IoC, чтобы внедрить реализацию через config.
Это позволит вам переключаться между HttpClient
а также WebClient
легко, чтобы вы могли объективно протестировать в производственной среде.
Итак, вопросы вроде:
Будет ли HttpClient лучшим выбором при обновлении до.Net 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
может работать лучше для других. Вот почему так важна абстракция. Это означает, что код можно быстро заменить или изменить с помощью конфигурации без изменения основного дизайна приложения.
У меня есть тест между HttpClient, WebClient, HttpWebResponse, затем вызовите Rest Web Api
и результат Call Rest Web Api Benchmark
--------------------- Этап 1 ---- 10 Запрос
{00:00:17.2232544} ====>HttpClinet
{00:00:04.3108986} ====>WebRequest
{00:00:04.5436889} ====> Веб-клиент
--------------------- Этап 1 ---- 10 Запрос - малый размер
{00:00:17.2232544} ====>HttpClinet
{00:00:04.3108986} ====>WebRequest
{00:00:04.5436889} ====> Веб-клиент
--------------------- Этап 3 ---- 10 запросов синхронизации - малый размер
{00:00:15.3047502} ====> HttpClinet
{00:00:03.5505249} ====> WebRequest
{00:00:04.0761359} ====> Веб-клиент
--------------------- Этап 4 ---- 100 запросов синхронизации - малый размер
{00:03:23.6268086} ====> HttpClinet
{00:00:47.1406632} ====> WebRequest
{00:01:01.2319499} ====> Веб-клиент
--------------------- Этап 5 ---- 10 запросов синхронизации - максимальный размер
{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;
}