http client transfer

背景

在平时工作中我偶尔会写一些脚本监控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);

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

时间: 2024-10-21 16:45:31

http client transfer的相关文章

Linux I2C设备驱动编写(二)

在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_driver.i2c_client.三者的关系也在上一节进行了描述.应该已经算是对Linux I2C子系统有了初步的了解.下面再对他们之间的关系进行代码层的深入分析,我认为对他们的关系了解的越好,越有助于I2C设备的驱动开发及调试. 带着问题去分析可能会更有帮助吧,通过对(一)的了解后,可能会产生以下的几点疑问: i2c_adapter驱动如何添加? i2c_client与i2c_board_info究竟是什么

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

原文网址:http://www.cnblogs.com/biglucky/p/4059582.html 在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_driver.i2c_client.三者的关系也在上一节进行了描述.应该已经算是对Linux I2C子系统有了初步的了解.下面再对他们之间的关系进行代码层的深入分析,我认为对他们的关系了解的越好,越有助于I2C设备的驱动开发及调试. 带着问题去分析可能会更有帮助吧,通过对(一)的了解后,可能会产生以下的几点疑

SSH Secure File Transfer Client传送文件

SSH Secure File Transfer Client是连接Linux的主要客户端工具之一,其特点就是传送文件方便.虽然SSH Secure File Transfer Client显示中文时有时会乱码,但仍然挡人们对它的喜爱 http://jingyan.baidu.com/article/19192ad815fd0ee53e570719.html

dbus 创建Client过程中几个有用的函数

/**  * g_type_init:  *  * This function used to initialise the type system.  Since GLib 2.36,  * the type system is initialised automatically and this function does  * nothing.  *  * Deprecated: 2.36: the type system is now initialised automatically

【甘道夫】Hadoop2.2.0 NN HA详细配置+Client透明性试验【完整版】

引言: 前面转载过一篇团队兄弟[伊利丹]写的NN HA实验记录,我也基于他的环境实验了NN HA对于Client的透明性. 本篇文章记录的是亲自配置NN HA的详细全过程,以及全面测试HA对客户端访问透明性的全过程,希望对大家有帮助. 实验环境: Hadoop2.2.0的4节点集群,ZK节点3个(ZK节点数最好为奇数个),hosts文件和各节点角色分配如下: hosts: 192.168.66.91 master 192.168.66.92 slave1 192.168.66.93 slave2

SSH Secure Shell Client安装和使用

SSH是一个用来替代TELNET.FTP以及R命令的工具包,主要是想解决口令在网上明文传输的问题.为了系统安全和用户自身的权益,推广SSH是必要的. SSH是英文Secure Shell的简写形式.通过使用SSH,你可以把所有传输的数据进行加密,这样"中间人"这种攻击方式就不可能实现了,而且也能够防止DNS欺骗和IP欺骗. SSH Secure Shell Client是一款不错的远程linux的工具,下载地址:http://down.51cto.com/data/643694,缺点就

WCF Client is Open Source

WCF Client is Open Source Wednesday, May 20, 2015 Announcement New Project WCF We’re excited to announce a new open source project on GitHub from the WCF team at Microsoft.  This new version of WCF targets .NET Core and has been donated to the family

用Wireshark抓包来揭开ftp client GG和ftp server MM的勾搭内容并用C代码来简要模拟实现Windows自带的ftp client

前面, 我们玩过http, 颇有点意思, 在本文中, 我们继续来玩ftp(file transfer protocol).   http和ftp都是建立在tcp之上的应用层协议, 无论他们怎么包装, 怎么装bigger, 最终还是基于tcp端到端传输的.本文主要分为两个部分: 一. 用Wireshark抓包来揭开ftp client GG和ftp server MM的勾搭内容.二.用C代码来简要模拟实现Windows自带的ftp client. 说明, 本文中的实验, 我用了两台电脑, 分别是p

MySQL多线程同步-Transfer使用测试

由淘宝核心系统研发-数据库组开发的MySQL-Transfer,用于解决MySQL主从同步延迟的问题,从MySQL单线程到多线程的工作模式.可以观看@丁奇的相关资料: MySQL多线程同步-Transfer使用说明MySQL异步复制延迟解决的架构设计与运维架构-在线播放-优酷网 系统结构 : 传统的主从结构是 [Master] à [Slave], Master和slave主从关系: 使用transfer以后,[Master] à [Transfer] .--> [Slave], Master和