简介
本文的主要参考资料:C# 5.0 in a nutshell ,由于我的英文能力有限,算是读书笔记。需要此书的可以留邮箱地址。以下是正文:
HttpClient是.NET4.5提供的一个实现了http传输协议的类,该类可以说分装了HttpWebRequest和HttpWebResponse,它可以说是WebClient的精简升级版,适用于新的Metro-Style App以及原生的异步模式,在Metro-Style App中已不能使用原来的WebClient了,所以你可以把它看成是一个替代的类。它与WebClient相比,有几个特点:
使用快速入门
使用HttpClient最简单的方法,便是,先实例化一个HttpClient,然后在”Get* “系列方法中传入一个URI地址,如:
string html = await new HttpClient().GetStringAsync ("http://linqpad.net");
当然,还有GetByteArrayAsync 和GetStreamAsync方法,这些方法很简单。而特点1的代码体现如下:
var client = new HttpClient(); var task1 = client.GetStringAsync ("http://www.linqpad.net"); var task2 = client.GetStringAsync ("http://www.albahari.com"); Console.WriteLine (await task1); Console.WriteLine (await task2);
HttpClient有一个Timeout属性(作用如其名字)和一个BaseAddress属性(可以作为URI的头部分)。
但是HttpClient只是简单的一个外壳而已,具体的配置类被抽象出成了一个接口,叫做HttpMessageHandler,其中,.NET为我们实现了一个具体的类,叫做HttpClientHandler(实现了HttpMessageHandler接口),使用代码如下:
var handler = new HttpClientHandler { UseProxy = false }; var client = new HttpClient (handler); ...
也就是HttpClient有个接收该接口的重载版本,这个例子中,我们使得此次的Http操作不能用代理来进行。该抽象接口在后续介绍。
GetAsync和response messages(即代表http返回报文的一个类)
以上的GetStringAsync, GetByteArrayAsync, 和 GetStreamAsync都是HttpClient的GetAsync方法的简化版本(也就是说调用者更明确地想将返回的结果具体化,而非只是一个response message),若使用底层的GetAsync,那么代码参考如下:
var client = new HttpClient(); // The GetAsync method also accepts a CancellationToken. HttpResponseMessage response = await client.GetAsync ("http://..."); response.EnsureSuccessStatusCode(); string html = await response.Content.ReadAsStringAsync();
HttpResponseMessage对象就是代表了一个Http请求从服务器返回的“报文”,HttpResponseMessage暴露了属性用于访问“报文”中的Headers信息,它是一个HttpResponseHeaders类型的属性(继承自发送和返回Headers共享的HttpHeaders),具体有哪些可以参考API文档。
。HttpResponseMessage当然还有典型的“返回状态码”属性,即StatusCode,这概念了解一下Http协议便可以知道,当然一般它的值是200的时候,说明是返回正常,但你可以用属性IsSuccessStatusCode来指示Http响应是否成功。
不像WebClient,在使用HttpClient时,若StatusCode返回的是非成功的,比如是404(not found),它是不会抛出异常,若你想让它抛出,则需要调用HttpResponseMessage对象的 EnsureSuccessStatusCode方法,然后再去对Content进行处理。Content是一个HttpContent 类型,代表的是Http协议返回报文中的Body部分,它是承载从服务器返回的具体的内容的,算是最重要的payload,很多时候,我们只要把这部分的东东解析成一个XML文档即可。 HttpResponseMessage有个CopyToAsync的方法将承载的内容写到另外一个Stream中,比如将它输出到一个文件流中,代码如下:
using (var fileStream = File.Create ("linqpad.html")) await response.Content.CopyToAsync (fileStream);
最后,GetAsync是Http协议的四个方法之一(分别是GET,POST,PUT和DELETE,在HttpClient都已经有对应的方法了,分别是GetAsync,PostAsync,PutAsync和DeleteAsync)。其实占用流量最多是GET,其次是POST。
SendAsync 和request messages(Http请求报文)
上一节中描述的HttpClient的四大方法,其实是对应到的是一个SendAsync方法,因为对于底层来说,你把对应的协议请求报文准备好,然后统一执行一个发送动作将报文发送出去。而SendAsync方法接收的参数就是一个代表“请求报文”的Request,参考代码如下:
var client = new HttpClient(); var request = new HttpRequestMessage (HttpMethod.Get, "http://..."); HttpResponseMessage response = await client.SendAsync (request); response.EnsureSuccessStatusCode(); ...
而以上这段稍微复杂的代码等效于快速入门那一节的那两句代码。自定义一个HttpRequestMessage对象,意味着,你要自己构建一些Header,以及在Content中传入Post需要的负载信息。
Uploading data and HttpContent
在使用Post方法时,往往是为了给服务器端发送一段数据(比如一个XML,一张图片),而这段数据便可以通过留的形式放在HttpRequestMessage的Content中,它和HttpResponseMessage中的Content一样,都是HttpContent类型。.NET给我们提供了4个继承自HttpContet的类,用以将传输数据,如下:
参考代码如下:
var client = new HttpClient (new HttpClientHandler { UseProxy = false }); var request = new HttpRequestMessage ( HttpMethod.Post, "http://www.albahari.com/EchoPost.aspx"); request.Content = new StringContent ("This is a test"); HttpResponseMessage response = await client.SendAsync (request); response.EnsureSuccessStatusCode(); Console.WriteLine (await response.Content.ReadAsStringAsync());
其中“This is a test”就是你要传给服务器的数据,当然,这没啥意义。
HttpMessageHandler
之前提到过的,大多数自定义的请求属性是定义在HttpClientHandler(.net提供的,实现了HttpMessageHandler)上,而不是在HttpClient上。以下是HttpMessageHandler的部分:
public abstract class HttpMessageHandler : IDisposable { protected internal abstract Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken); public void Dispose(); protected virtual void Dispose (bool disposing); }
你会突然发现,这里面也有一个SendAsync方法,那么其实,HttpClient的SendAsync方法就是调用HttpMessageHandler的这个方法的。
Unit testing and mocking(使用自定义HttpMessageHandler来单元测试和模拟)
代码1,自定义的HttpMessageHandler,即MockHandler:
class MockHandler : HttpMessageHandler { Func <HttpRequestMessage, HttpResponseMessage> _responseGenerator; public MockHandler (Func <HttpRequestMessage, HttpResponseMessage> responseGenerator) { _responseGenerator = responseGenerator; } protected override Task <HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var response = _responseGenerator (request); response.RequestMessage = request; return Task.FromResult (response); } }
代码2,在单元测试中使用MockHandler
var mocker = new MockHandler (request => new HttpResponseMessage (HttpStatusCode.OK) { Content = new StringContent ("You asked for " + request.RequestUri) }); var client = new HttpClient (mocker); var response = await client.GetAsync ("http://www.linqpad.net"); string result = await response.Content.ReadAsStringAsync(); Assert.AreEqual ("You asked for http://www.linqpad.net/", result);
使用 DelegatingHandler实现“职责链模式”
DelegatingHandler是.NET提供的,继承自HttpResponseMessage,用来实现职责链模式的类,使用方法如下:
class LoggingHandler : DelegatingHandler { public LoggingHandler (HttpMessageHandler nextHandler) { InnerHandler = nextHandler; } protected async override Task <HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) { Console.WriteLine ("Requesting: " + request.RequestUri); var response = await base.SendAsync (request, cancellationToken); Console.WriteLine ("Got response: " + response.StatusCode); return response; } }
其中,DelegatingHandler定义了一个InnerHandler属性(根据设计模式也能知道,它也是HttpMessageHandler类型),用于内部使用,也就是base.SendAsync调用时,会调用InnerHandler的SendAsync。那么我们只需要进行一些自己需要的操作即可,这是关于设计模式的,不比多说。