背景

在平时工作中我偶尔会写一些脚本监控HTTP接口健康状况,基本上都是发送HTTP GET或HTTP POST请求,然后检测响应内容。但一直用的是WebClient和HttpWebRequest,虽然用它们也能进行异步请求(可参考我分享的代码C#异步GET的3个例子),但总感觉那样用起来不太自然。网上搜索后发现.NET 4.5引入的HttpClient库能够完成异步HTTP请求。于是结合多方资料总结下HttpClient的特性和使用。本文属于阅读笔记性质博客

准备工作

    在Visual Studio 2012中新建一个控制台应用,然后添加对system.Net和System.Net.Http程序集的引用。

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请求数据的例子:

using System;
using System.Net.Http; namespace HttpClientProject
{
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关键字来重写上面的代码,仍保持了异步性:

using System;
using System.Net.Http; namespace HttpClientProject
{
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();

也将导致程序被阻塞。

异步HTTP POST

我以前写过一个用HttpWebRequest自动登录开源中国的代码(见 C#自动登录开源中国 ),在此基础上我用HttpClient的PostAsync方法改写如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Security; namespace AsycLoginOsChina
{
public class OschinaLogin
{
/// <summary>
/// 设置请求头信息
/// </summary>
/// <param name="client"></param>
public static void SetRequestHeader(HttpClient client)
{
client.DefaultRequestHeaders.Add("Host", "www.oschina.net");
client.DefaultRequestHeaders.Add("Method", "Post");
client.DefaultRequestHeaders.Add("KeepAlive", "false"); // HTTP KeepAlive设为false,防止HTTP连接保持
client.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");
} // 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(关于它的详细信息见下一节)了,如下:

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属性来检测请求是否成功,比如下面:

HttpResponseMessage response = postTask.Result;
if (response.StatusCode == HttpStatusCode.OK)
{
// ...
}

或者通过HttpResponseMessage的IsSuccessStatusCode属性来检测:

HttpResponseMessage response = postTask.Result;
if (response.IsSuccessStatusCode)
{
// ...
}

再或者你更喜欢以异常的形式处理请求失败情况,那么你可以用下面的代码:

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,并重写下面这个方法:

Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken);

比如下面自定义了一个客户端消息处理器来记录HTTP错误码:

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对象上:

HttpClient client = HttpClientFactory.Create(new LoggingHandler(), new Handler2(), new Handler3());

上面的自定义消息处理器只拦截处理了HTTP响应,如果我们既想拦截处理HTTP请求,又想拦截处理HTTP响应,那么该怎么做呢?如下:

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.GetAsync(url).Result.Content.ReadAsStringAsync().Result;

本文完!希望对读者你有所帮助,O(∩_∩)O

http client transfer的更多相关文章

  1. Linux I2C设备驱动编写(二)

    在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_driver.i2c_client.三者的关系也在上一节进行了描述.应该已经算是对Linux I2C子系统有了初步 ...

  2. 【转】Linux I2C设备驱动编写(二)

    原文网址:http://www.cnblogs.com/biglucky/p/4059582.html 在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_drive ...

  3. golang 简单的实现内 网 穿 透,用户访问本地服务。

    一.功能描述: 客户端通过访问外网服务器上指定端口,间接访问自已本地的内网服务. 二.原理图如下: 三.实现代码如下: server.go代码: package main; import ( &quo ...

  4. i2c设备驱动注册

      Linux I2C设备驱动编写(二) 原创 2014年03月16日 23:26:50   在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_driver.i2c ...

  5. 协议 - DNS

    目录 1 DNS 1.1 域名解析的历史:/etc/hosts, DNS, FQDN 1.2 域名解析流程: 域名架构 查询流程, DNS端口 1.3 合法 DNS :申请域查询授权 1.4 主机名交 ...

  6. SSH Secure File Transfer Client 无法登陆

    嘉之叹息 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATI ...

  7. SSH Secure File Transfer Client连接远程设备报“algorithm negotiation failed”错的解决方法

    SSH Secure File Transfer Client连接远程设备报"algorithm negotiation failed"错的解决方法 ssh client 报 al ...

  8. If WCF Service side and Client side config is different?!

    from stackoverflow http://stackoverflow.com/questions/4879310/when-setting-up-a-wcf-client-and-serve ...

  9. Response.Redirect()、Server.Execute和Server.Transfer的区别

    1.Response.Redirect(): Response.Redirect方法导致浏览器链接到一个指定的URL. 当Response.Redirect()方法被调用时,它会创建一个应答,应答头中 ...

随机推荐

  1. Dede技巧

    解决DEDE图集上传图片时跳出302错误   本地上传图集的时候突然提示网页出错,还爆出302错误. 解决办法是在include/userlogin.class.php文件中的第二行session_s ...

  2. 正确适配苹果ATS审核要求的姿势

    首先,ATS的技术行为不会有任何变化(除了新增两个字段NSAllowsArbitraryLoadsInWebContent和NSRequiresCertificateTransparency,也就是更 ...

  3. 数据预处理之数据规约(Data Reduction)

    数据归约策略 数据仓库中往往具有海量的数据,在其上进行数据分析与挖掘需要很长的时间 数据归约 用于从源数据中得到数据集的归约表示,它小的很多,但可以产生相同的(几乎相同的)效果 数据归约策略 维归约  ...

  4. B1002 写出这个数

    读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字. 输入格式: 每个测试输入包含 1 个测试用例,即给出自然数 n 的值.这里保证 n 小于 1. 输出格式: 在一行内输出 n 的 ...

  5. 常用的windows小工具指令和如何打开自定义的程序

    windows可以通过 开始->运行->输入程序名 或 windows键+R键 两种方式来启动windows中自带的程序或手动安装的程序.下面介绍一些常用的windows工具的指令和如何打 ...

  6. 初学Python01

    1.文本编辑器区别于交互模式的Python,它可以保存Python代码文件,再次打开还是存在.文件保存时要注意是.py的模式. 2.Windows系统下,应使用命令行模式打开.py 首先进入文件所在磁 ...

  7. GoF23种设计模式之结构型模式之组合模式

    一.概述 将对象组合成树型结构以表示“部分--整体”的层次关系.组合模式使得用户对单个对象和组合对象的使用具有一致性. 二.适用性 1.你想表示对象的部分--整体层次结构的时候. 2.你希望用户忽略组 ...

  8. 通过cookies信息模拟登陆

    import requests # 这个练习演示的是通过传入cookie信息模拟登陆,这样操作的前提是需要预先在浏览器登陆账户抓包得到cookie字段信息 url = "http://www ...

  9. 牛客网暑期ACM多校训练营(第七场)A Minimum Cost Perfect Matching(找规律)

    题意: 给定n, 求一个0~n-1的全排列p, 使得的和最小 分析: 打表发现最优解肯定是和为0的, 然后如果为2的幂就是直接反转即可, 不然的话就要分开从前面到后面逐步拆分, 具体思想模拟一下n = ...

  10. PAT Basic 1078

    1078 字符串压缩与解压 文本压缩有很多种方法,这里我们只考虑最简单的一种:把由相同字符组成的一个连续的片段用这个字符和片段中含有这个字符的个数来表示.例如 ccccc 就用 5c 来表示.如果字符 ...