理解并使用.NET 4.5中的HttpClient(转)
原文地址:http://www.cnblogs.com/wywnet/p/httpclient.html
HttpClient介绍
HttpClient是.NET4.5引入的一个HTTP客户端库,其命名空间为System.Net.Http。.NET 4.5之前我们可能使用WebClient和HttpWebRequest来达到相同目的。但是有几点值得关注:
可以使用单个HttpClient实例发任意数目的请求
一个HttpClient实例不会跟某个HTTP服务器或主机绑定,也就是说我们可以用一个实例同时给www.a.com和www.b.com发请求
可以继承HttpClient达到定制目的
HttpClient利用了最新的面向任务模式,使得处理异步请求非常容易
异步HTTP GET
下面是一个使用HttpClient进行HTTP GET请求数据的例子:
class HttpClientDemo
{
private const string Uri = "http://api.worldbank.org/countries?format=json";
static void Main(string[] args)
{
HttpClient httpClient = new HttpClient();
// 创建一个异步GET请求,当请求返回时继续处理
httpClient.GetAsync(Uri).ContinueWith(
(requestTask) =>
{
HttpResponseMessage response = requestTask.Result;
// 确认响应成功,否则抛出异常
response.EnsureSuccessStatusCode();
// 异步读取响应为字符串
response.Content.ReadAsStringAsync().ContinueWith(
(readTask) => Console.WriteLine(readTask.Result));
});
Console.WriteLine("Hit enter to exit...");
Console.ReadLine();
}
}
代码运行后将先输出“Hit enter to exit...“,然后才输出请求响应内容,因为采用的是GetAsync(string requestUri)异步方法,它返回的是Task<HttpResponseMessage>对象( 这里的 httpClient.GetAsync(Uri).ContinueWith(...)有点类似JavaScript中使用Promise对象进行异步编程的写法,具体可以参考 JavaScript异步编程的四种方法 的第四节和 jQuery的deferred对象详解)。
于是我们可以用.NET 4.5之后支持的async、await关键字来重写上面的代码,仍保持了异步性:
class HttpClientDemo
{
private const string Uri = "http://api.worldbank.org/countries?format=json";
static async void Run()
{
HttpClient httpClient = new HttpClient();
// 创建一个异步GET请求,当请求返回时继续处理(Continue-With模式)
HttpResponseMessage response = await httpClient.GetAsync(Uri);
response.EnsureSuccessStatusCode();
string resultStr = await response.Content.ReadAsStringAsync();
Console.WriteLine(resultStr);
}
static void Main(string[] args)
{
Run();
Console.WriteLine("Hit enter to exit...");
Console.ReadLine();
}
}
注意,如果以下面的方式获取HttpResponseMessage会有什么后果呢?
HttpResponseMessage response = httpClient.GetAsync(Url).Result;
后果是访问Result属性会阻塞程序继续运行,因此就失去了异步编程的威力。类似的:
string resultStr = response.Content.ReadAsStringAsync().Result;
也将导致程序运行被阻塞。
异步HTTP POST
public class OschinaLogin
{
// MD5或SHA1加密
public static string EncryptPassword(string pwdStr, string pwdFormat)
{
string EncryptPassword = null;
if ("SHA1".Equals(pwdFormat.ToUpper()))
{
EncryptPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(pwdStr, "SHA1");
}
else if ("MD5".Equals(pwdFormat.ToUpper()))
{
EncryptPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(pwdStr, "MD5");
}
else
{
EncryptPassword = pwdStr;
}
return EncryptPassword;
}
/// <summary>
/// OsChina登陆函数
/// </summary>
/// <param name="email"></param>
/// <param name="pwd"></param>
public static void LoginOsChina(string email, string pwd)
{
HttpClient httpClient = new HttpClient();
// 设置请求头信息
httpClient.DefaultRequestHeaders.Add("Host", "www.oschina.net");
httpClient.DefaultRequestHeaders.Add("Method", "Post");
httpClient.DefaultRequestHeaders.Add("KeepAlive", "false"); // HTTP KeepAlive设为false,防止HTTP连接保持
httpClient.DefaultRequestHeaders.Add("UserAgent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
// 构造POST参数
HttpContent postContent = new FormUrlEncodedContent(new Dictionary<string, string>()
{
{"email", email},
{"pwd", EncryptPassword(pwd, "SHA1")}
});
httpClient
.PostAsync("http://www.oschina.net/action/user/hash_login", postContent)
.ContinueWith(
(postTask) =>
{
HttpResponseMessage response = postTask.Result;
// 确认响应成功,否则抛出异常
response.EnsureSuccessStatusCode();
// 异步读取响应为字符串
response.Content.ReadAsStringAsync().ContinueWith(
(readTask) => Console.WriteLine("响应网页内容:" + readTask.Result));
Console.WriteLine("响应是否成功:" + response.IsSuccessStatusCode);
Console.WriteLine("响应头信息如下:\n");
var headers = response.Headers;
foreach (var header in headers)
{
Console.WriteLine("{0}: {1}", header.Key, string.Join("", header.Value.ToList()));
}
}
);
}
public static void Main(string[] args)
{
LoginOsChina("你的邮箱", "你的密码");
Console.ReadLine();
}
}
代码很简单,就不多说了,只要将上面的Main函数的邮箱、密码信息替换成你自己的OsChina登录信息即可。上面通httpClient.DefaultRequestHeaders属性来设置请求头信息,也可以通过postContent.Header属性来设置。 上面并没有演示如何设置Cookie,而有的POST请求可能需要携带Cookie,那么该怎么做呢?这时候就需要利用 HttpClientHandler(关于它的详细信息见下一节)了,如下:
1
2
3
4
5
6
7
8
9
CookieContainer cookieContainer = new CookieContainer();
cookieContainer.Add(new Cookie("test", "0")); // 加入Cookie
HttpClientHandler httpClientHandler = new HttpClientHandler()
{
CookieContainer = cookieContainer,
AllowAutoRedirect = true,
UseCookies = true
};
HttpClient httpClient = new HttpClient(httpClientHandler);
然后像之前一样使用httpClient。至于ASP.NET服务端如何访问请求中的Cookie和设置Cookie,可以参考:ASP.NET HTTP Cookies 。
其他HTTP操作如PUT和DELETE,分别由HttpClient的PutAsync和DeleteAsync实现,用法基本同上面,就不赘述了。
异常处理
默认情况下如果HTTP请求失败,不会抛出异常,但是我们可以通过返回的HttpResponseMessage对象的StatusCode属性来检测请求是否成功,比如下面:
1
2
3
4
5
HttpResponseMessage response = postTask.Result;
if (response.StatusCode == HttpStatusCode.OK)
{
// ...
}
或者通过HttpResponseMessage的IsSuccessStatusCode属性来检测:
1
2
3
4
5
HttpResponseMessage response = postTask.Result;
if (response.IsSuccessStatusCode)
{
// ...
}
再或者你更喜欢以异常的形式处理请求失败情况,那么你可以用下面的代码:
1
2
3
4
5
6
7
8
9
try
{
HttpResponseMessage response = postTask.Result;
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException e)
{
// ...
}
HttpResponseMessage对象的EnsureSuccessStatusCode()方法会在HTTP响应没有返回成功状态码(2xx)时抛出异常,然后异常就可以被catch处理。
HttpMessageHandler Pipeline
在ASP.NET服务端,一般采用Delegating Handler模式来处理HTTP请求并返回HTTP响应:一般来说有多个消息处理器被串联在一起形成消息处理器链 (HttpMessageHandler Pipeline),第一个消息处理器处理请求后将请求交给下一个消息处理器...最后在某个时刻有一个消息处理器处理请求后返回响应对象并再沿着消息处 理器链向上返回,如下图所示:(本博客主要与HttpClient相关,所以如果想了解更多ASP.NET Web API服务端消息处理器方面的知识,请参考:HTTP Message Handlers )
HttpClient也使用消息处理器来处理请求,默认的消息处理器是HttpClientHandler(上面异步HTTP POST使用到了),我们也可以自己定制客户端消息处理器,消息处理器链处理请求并返回响应的流程如下图:
如果我们要自己定制一个客户端消息处理器的话,可以继承DelegatingHandler或者HttpClientHandler,并重写下面这个方法:
1
2
Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken);
比如下面自定义了一个客户端消息处理器来记录HTTP错误码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class LoggingHandler : DelegatingHandler
{
StreamWriter _writer;
public LoggingHandler(Stream stream)
{
_writer = new StreamWriter(stream);
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
_writer.WriteLine("{0}\t{1}\t{2}", request.RequestUri,
(int)response.StatusCode, response.Headers.Date);
}
return response;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_writer.Dispose();
}
base.Dispose(disposing);
}
}
然后我们需要用下面的代码将自定义消息处理器绑定到HttpClient对象上:
1
HttpClient client = HttpClientFactory.Create(new LoggingHandler(), new Handler2(), new Handler3());
上面的自定义消息处理器只拦截处理了HTTP响应,如果我们既想拦截处理HTTP请求,又想拦截处理HTTP响应,那么该怎么做呢?如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyHandler : DelegatingHandler
{
private string _name;
public MyHandler(string name)
{
_name = name;
}
// 拦截请求和响应
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("Request path in handler {0}", _name); // 1
return base.SendAsync(request, cancellationToken).ContinueWith( requestTask => {
Console.WriteLine("Response path in handler {0}", _name);
return requestTask.Result; }, TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
上面的1处拦截请求并处理。MSDN博客上还有一个例子利用客户端消息处理器来实现OAuth验证,具体可以移步:Extending HttpClient with OAuth to Access Twitter
WebRequestHandler
这里补充说明另外一个之前没有涉及到的类 - WebRequestHandler。
WebRequestHandler继承自HttpClientHandler,但是包含在System.Net.Http.WebRequest程序集中。它的一些属性见下表:
属性 说明
AllowPipelining 获取或设置是否允许请求被pipeline
AuthenticationLevel 获取或设置认证级别
CachePolicy 获取或设置这个请求的缓存策略
ClientCertificates 获取或设置这个请求的安全证书集
ContinueTimeout 当上传数据时,客户端必须先等待服务端返回100-continue,这个设置了返回100-continue的超时时间
MaxResponseHeadersLength 获取或设置响应头的最大长度
ReadWriteTimeout 获取或设置写请求或读响应的超时时间
ServerCertificateValidationCallback 获取或设置SSL验证完成后的回调函数
一个使用WebRequestHandler的简单示例如下:
WebRequestHandler webRequestHandler = new WebRequestHandler();
webRequestHandler.UseDefaultCredentials = true;
webRequestHandler.AllowPipelining = true;
// Create an HttpClient using the WebRequestHandler();
HttpClient client = new HttpClient(webRequestHandler);
为HttpClient定制下载文件方法
HttpClient没有直接下载文件到本地的方法。我们知道response.Content是HttpContent对象,表示HTTP响应消息的内 容,它已经支持ReadAsStringAsync、ReadAsStreamAsync和ReadAsByteArrayAsync等方法。响应消息内 容也支持异步读取(为什么响应消息内容读取也需要异步呢?原因在于响应消息内容非常大时异步读取好处很明显)。下面我给HttpContent类扩展一个 DownloadAsFileAsync方法,以支持直接异步下载文件到本地:
public static class HttpContentExtension
{
/// <summary>
/// HttpContent异步读取响应流并写入本地文件方法扩展
/// </summary>
/// <param name="content"></param>
/// <param name="fileName">本地文件名</param>
/// <param name="overwrite">是否允许覆盖本地文件</param>
/// <returns></returns>
public static Task DownloadAsFileAsync(this HttpContent content, string fileName, bool overwrite)
{
string filePath = Path.GetFullPath(fileName);
if (!overwrite && File.Exists(filePath))
{
throw new InvalidOperationException(string.Format("文件 {0} 已经存在!", filePath));
}
try
{
return content.ReadAsByteArrayAsync().ContinueWith(
(readBytestTask) =>
{
byte[] data = readBytestTask.Result;
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
fs.Write(data, 0, data.Length);
//清空缓冲区
fs.Flush();
fs.Close();
}
}
);
}
catch (Exception e)
{
Console.WriteLine("发生异常: {0}", e.Message);
}
return null;
}
}
class HttpClientDemo
{
private const string Uri = "http://www.oschina.net/img/iphone.gif";
static void Main(string[] args)
{
HttpClient httpClient = new HttpClient();
// 创建一个异步GET请求,当请求返回时继续处理
httpClient.GetAsync(Uri).ContinueWith(
(requestTask) =>
{
HttpResponseMessage response = requestTask.Result;
// 确认响应成功,否则抛出异常
response.EnsureSuccessStatusCode();
// 异步读取响应为字符串
response.Content.DownloadAsFileAsync(@"C:\TDDOWNLOAD\test.gif", true).ContinueWith(
(readTask) => Console.WriteLine("文件下载完成!"));
});
Console.WriteLine("输入任意字符结束...");
Console.ReadLine();
}
上面我直接利用HttpContent的ReadAsBytesArrayAsync方法,我试过利用ReadAsStringAsync和ReadAsStreamAsync,但是都出现了乱码现象,只有这种读取到字节数组的方法不会出现乱码。
SendAsync和HttpRequestMessage
前面讲的GetAsync、PostAsync、PutAsync、DeleteAsync事实上都可以用一种方法实现:SendAsync结合 HttpRequestMessage。前面我们自定义HTTP消息处理器时重写过SendAsync方法,它的第一个参数就是 HttpRequestMessage类型。一般性的示例如下:
class HttpClientDemo
{
private const string Uri = "http://www.oschina.net/";
static void Main(string[] args)
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, Uri);
httpClient.SendAsync(request).ContinueWith(
(responseMessageTask) =>
{
HttpResponseMessage response = responseMessageTask.Result;
// 确认响应成功,否则抛出异常
response.EnsureSuccessStatusCode();
response.Content.ReadAsStringAsync().ContinueWith(
(readTask) => Console.WriteLine(readTask.Result));
}
);
Console.WriteLine("输入任意字符退出...");
Console.ReadLine();
}
}
参考资料:
http://blogs.msdn.com/b/henrikn/archive/2012/02/16/httpclient-is-here.aspx
http://blogs.msdn.com/b/henrikn/archive/2012/04/27/asp-net-web-api-updates-april-27.aspx
http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client
http://www.asp.net/web-api/overview/working-with-http/http-message-handlers
http://www.asp.net/web-api/overview/working-with-http/http-cookies
http://blogs.msdn.com/b/henrikn/archive/2012/04/27/asp-net-web-api-updates-april-27.aspx
分类: .NET
理解并使用.NET 4.5中的HttpClient(转)的更多相关文章
- 理解并使用.NET 4.5中的HttpClient
HttpClient介绍 HttpClient是.NET4.5引入的一个HTTP客户端库,其命名空间为System.Net.Http..NET 4.5之前我们可能使用WebClient和HttpWeb ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(6)——SQL Server如何编译动态SQL
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(5)--案例:如何应对参数嗅探 我们抛开参数嗅探的话题,回到了本系列的最 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(5)——案例:如何应对参数嗅探
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(4)--收集解决参数嗅探问题的信息 首先我们需要明白,参数嗅探本身不是问 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(4)——收集解决参数嗅探问题的信息
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(3)--不总是参数嗅探的错 前面已经提到过关于存储过程在SSMS中运行很 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(3)——不总是参数嗅探的错
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(2)--SQL Server如何编译存储过程 在我们开始深入研究如何处理 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(2)——SQL Server如何编译存储过程
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(1)--简介 本文介绍SQL Server如何编译存储过程并使用计划缓存 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(1)——简介
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 在工作中发现有不少类似的现象,有幸看到国外大牛写的一篇文章,由于已经完善得不能再添油加醋,所以决定直接翻译,原文出处:http ...
- map和flatmap的区别+理解、学习与使用 Java 中的 Optional
转自:map和flatmap的区别 对于stream, 两者的输入都是stream的每一个元素,map的输出对应一个元素,必然是一个元素(null也是要返回),flatmap是0或者多个元素(为n ...
- 理解ORM的前提:数据库中的范式和约束
理解ORM的前提:数据库中的范式和约束 一.数据库中的范式: 范式, 英文名称是 Normal Form,它是英国人 E.F.Codd(关系数据库的老祖宗)在上个世纪70年代提出关系数据库模型后总结出 ...
随机推荐
- XFire Web Service
Web Service 创建HelloWorldService项目 首先要启动Web Service Project 向导.该向导由三个页面组成,第一页设置Web项目配置的详细信息:第二页设置XFir ...
- codeforces 444 C. DZY Loves Colors(线段树)
题目大意: 1 l r x操作 讲 [l,r]上的节点涂成x颜色,而且每一个节点的值都加上 |y-x| y为涂之前的颜色 2 l r 操作,求出[l,r]上的和. 思路分析: 假设一个区间为同样的颜 ...
- [翻译] RBBAnimation,让你使用关键帧动画更便利
RBBAnimation RBBAnimation is a subclass of CAKeyframeAnimation that allows you to declare your anima ...
- 每天一个linux命令-vi
进入vi的命令 vi filename :打开或新建文件,并将光标置于第一行首 vi +n filename :打开文件,并将光标置于第n行首 vi + filename :打开文件,并将光标置于最后 ...
- iOS获取网络类型的四种方法
Reachability类只能区分WIFI和WWAN类型,却无法区分2G网和3G网. 网上也有些方法,却都存在Bug. 经过网上查找资料和测试,基本上总结了以下几种方法: 1.使用导航栏的方式:(私有 ...
- 文件操作篇 close creat dup dup2 fcntl flock fsync lseek mkstemp open read sync write
文件操作篇 close creat dup dup2 fcntl flock fsync lseek mkstemp open read sync write close(关闭文件) 相关函数 ope ...
- @JVM新一代的垃圾回收算法
垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要 ...
- HTML中显示特殊字符,如尖括号 “<”,">"等等
这几天,做个网页,之后进行解析,总是出错.最后发现是因为错误的使用了尖括号 “<”,">". 如下面的html代码: <body> 显示:<inpu ...
- 一个执行Dos命令的窗口程序,与各位分享。
一个执行Dos命令的窗口程序,与各位分享. 效果图: 具体实现在代码中有详细的注释,请看代码. 实现执行CMD命令的核心代码(Cmd.cs): [csharp] using S ...
- [CSS] Pseduo
#self aside li{ list-style-type: none;padding:5px;border-bottom: 1px solid #ccc;} #self aside li:las ...