我们的网络应用程序在.Net Framework 4.0中运行。 UI通过ajax调用调用控制器方法。
我们需要从供应商处使用REST服务。我正在评估在.Net 4.0中调用REST服务的最佳方法。 REST服务需要基本身份验证方案
可以返回XML和JSON中的数据。没有要求上传/下载大量数据,我将来也看不到任何东西。我看了几个用于REST消费的开源代码项目,并没有找到任何值来证明项目中的额外依赖性。开始评估WebClient
和HttpClient
。我从NuGet下载了用于.Net 4.0的HttpClient。
我搜索了WebClient
和HttpClient
以及this site之间的差异,提到单个HttpClient可以处理并发调用,它可以重用已解析的DNS,Cookie配置和身份验证。我还没有看到由于差异而可能获得的实用价值。
我做了一个快速的性能测试,以了解WebClient
(同步调用),HttpClient
(同步和异步)的执行方式。以下是结果:
对所有请求使用相同的HttpClient
实例(min - max)
WebClient同步:8毫秒 - 167毫秒
HttpClient同步:3毫秒 - 7228毫秒
HttpClient async:985 - 10405 ms
为每个请求使用新的HttpClient
(最小 - 最大)
WebClient同步:4毫秒 - 297毫秒
HttpClient同步:3毫秒 - 7953毫秒
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);
}
}
}
HttpClient
优于WebClient
?HttpClient
并发性优于WebClient
吗?从测试结果中,我看到WebClient
同步调用效果更好。HttpClient
会是更好的设计选择吗?性能是关键的设计因素。答案 0 :(得分:195)
我生活在F#和Web API世界中。
Web API发生了很多好事,特别是以安全性的消息处理程序等形式出现。
我知道我的意见只有一个,但是我只会建议您使用HttpClient
进行任何工作。也许有一些方法可以利用System.Net.Http
中出现的其他部分,而不直接使用该程序集,但我无法想象这在当时是如何工作的。
说到比较这两个
如果您使用的是.NET 4.5,请使用Microsoft为开发人员提供的HttpClient的async优度。 HttpClient与HTTP的服务器端兄弟非常对称,即HttpRequest和HttpResponse。
更新:使用新HttpClient API的5个理由:
<强>参考强>
C#5.0 Joseph Albahari(Channel9 - Video Build 2013)
Five Great Reasons to Use the New HttpClient API to Connect to Web Services
答案 1 :(得分:55)
HttpClient是最新的API,它具有
的优点如果您正在编写一个对其他Web服务进行REST调用的Web服务,您应该希望为所有REST调用使用异步编程模型,这样就不会遇到线程饥饿。您可能还想使用具有async / await支持的最新C#编译器。
注意:它不是更高效的AFAIK。如果你创建一个公平的测试,它可能在某种程度上同样有效。
答案 2 :(得分:6)
评估创建HttpClient的不同方法很重要,其中一部分是了解HttpClientFactory。
这不是我知道的直接答案-但是,最好从这里开始,而不是到处都以new HttpClient(...)
结尾。
答案 3 :(得分:3)
首先,我不是WebClient与HttpClient的权威,特别是。其次,根据您上面的评论,似乎表明WebClient只是同步,而HttpClient是两者。
我做了一个快速的性能测试,以找到WebClient(同步调用),HttpClient(同步和异步)的执行方式。以下是结果。
我认为这对于未来的思考是一个巨大的差异,即长时间运行的流程,响应式GUI等等(增加你建议的框架4.5的好处 - 在我的实际经验中,在IIS上的速度要快得多)
答案 4 :(得分:2)
从2020年开始不受欢迎的观点:
当涉及到ASP.NET应用 时,与WebClient
相比,我仍然更喜欢HttpClient
,因为:
答案 5 :(得分:1)
也许您可以以不同的方式考虑问题。 WebClient
和HttpClient
本质上是同一事物的不同实现。我建议在整个应用程序中使用Dependency Injection pattern来实现IoC Container。您应该使用比低级别HTTP传输更高抽象级别的客户端接口。您可以编写同时使用WebClient
和HttpClient
的具体类,然后使用IoC容器通过config注入实现。
这将使您可以轻松地在HttpClient
和WebClient
之间进行切换,以便能够在生产环境中进行客观的测试。
诸如此类的问题
如果我们升级到.Net 4.5,HttpClient将成为更好的设计选择吗?
实际上可以通过使用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
可能对其他客户更好。这就是为什么抽象很重要的原因。这意味着无需更改应用程序的基本设计即可快速交换代码或通过配置更改代码。
答案 6 :(得分:0)
我在HttpClient,WebClient,HttpWebResponse之间建立了基准,然后调用Rest Web Api
和结果 致电Rest Web Api Benchmark
---------------------阶段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;
}