理解并使用.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年代提出关系数据库模型后总结出 ...
随机推荐
- java反射知识点总结
一.java反射基础 1.1 什么叫java反射? 答:程序运行期间,动态的获取类的基本信息.比如:创建对象,调用类的方法,获得类的基本结构.这样给程序设计提供了很大的灵活性.个人总结就是:根据动态需 ...
- 在使用完全拷贝过来的类文件(带xib文件)时,要及时修改 File's Owner
- SQL游标、函数的使用方法
游标的的使用有日常的开发和维护的过程不使用的并不多,但是碰到一些棘手的问题的时候,游标时常是个非常好的帮手,下面就说下游标的使用方法,方法自己以后查阅,和加深一些印象,下面以一个存储过程为例 ...
- pytest文档26-运行上次失败用例(--lf 和 --ff)
前言 "80%的bug集中在20%的模块,越是容易出现bug的模块,bug是越改越多"平常我们做手工测试的时候,比如用100个用例需要执行,其中10个用例失败了, 当开发修复完bu ...
- 利用 FastCoding 将对象进行本地持久化
FastCoding https://github.com/nicklockwood/FastCoding A faster and more flexible binary file format ...
- struts2中的namespace意义
<package name="user" namespace="/user" extends="struts-default"> ...
- linux下so获得自己文件位置的路径
打开这个设备/proc/self/maps 返回的就是这个进程当前使用的so列表 cat /proc/self/maps00400000-0040b000 r-xp 00000000 08:01 14 ...
- java 中的resultset的类型
结果集(ResultSet)是数据中查询结果返回的一种对象,可以说结果集是一个存储查询结果的对象,但是结果集并不仅仅具有存储的功能,他同时还具有操纵数据的功能,可能完成对数据的更新等. 结果集读取数据 ...
- 解决spark中遇到的数据倾斜问题
一. 数据倾斜的现象 多数task执行速度较快,少数task执行时间非常长,或者等待很长时间后提示你内存不足,执行失败. 二. 数据倾斜的原因 常见于各种shuffle操作,例如reduceByKey ...
- jquery解析XML及获取XML节点名称
).tagName $().tagName [].tagName[] $(].tagName context.nodeName $(this).context.nodeName function ge ...