1、揭秘通用平台的 HttpClient (译)
原文链接:Demystifying HttpClient APIs in the Universal Windows Platform
正打算翻译这篇文章时,发现园子里已经有朋友翻译过了,既然已经开始了,就再概要的翻译一遍吧,就不逐字逐句了 :) 。这段时间没有春节前那么忙了,正好整理一下技术文档。
译文:
作为一个 Universal Windows Platform (UWP) app 开发者,如果想通过 HTTP 协议与 web服务器端进行交互,你有很多的 API 可以选择。两个最常用、较为推荐的是 System.Net.Http.HttpClient 和 Windows.Web.Http.HttpClient。相对于较老的 WebClient 和 HttpWebRequest(虽然为了向后兼容, HttpWebRequest 仍然在 UWP中可以使用),推荐使用 HttpClient。
我们已经收到一些关于这两个 API 的问题,它们之间功能的异同,在什么时候使用哪一个等等。在这个文章里面我们会说明这两个 API 的目的。
概述
在 .NET 4.5 中 System.Net.Http.HttpClient API 首次被引入,并且通过 NuGet 包,向下为 .NET 4.0 和 Windows Phone8 Sliverlight app 提供支持。相对于老的 HttpWebRequest,这个 API 的目的只为了提供一个 HTTP 客户端一个轻量级、易扩展的抽象层。直到 Windows8.1,这个 API 在底层通过托管的 .NET完全实现了。在 Windows10 上,这个 API 在 UWP中改为基于 Window.Web.Http 和 Windows 的 WinlNet HTTP 协议栈实现了。
另一方面, Windows.Web.Http.HttpClient API 第一次在 Windows 8.1 和 Windows Phone8.1 上引入。目的是在不同的 Windows app 语言(C#、VB、C++、JavaScript)提供一个统一的 HTTP API 支持。大多数的 Syste.Net.Http 命名空间下的 API 是基于Windows D的 WinlNet HTTP协议栈设计实现的。
在 Windows Store app 中使用这些 API,支持的系统版本和编程语言:
API | 系统版本 | 支持的语言 |
System.Net.Http.HttpClient | Windows,Windows Phone8 以上 | 仅 .NET 语言 |
Windows.Web.Http.HttpClient | Windows,Windows Phone8.1 以上 | 所有 Windows Store app 语言 |
应该使用哪个?
这个两个 API 在 UWP 中都可以使用,最大的问题就是在 app 中使用哪一个。这取决于两个因素:
1、你是否需要继承原 UI来收集用户凭据;控制 HTTP 缓存读、写;传递客户端指定的 SSL 验证凭据?
如果是,那么就使用 Windows.Web.Http.HttpClient。到目前为止,这个 Windows.Web.Http API 提供了比 System.Net.Http API 对 Http 设置更强的控制。在未来 UWP 的版本中,这个 System.Net.Http API 可能会获得相同的功能支持。
2、你是否想写跨 .NET 平台的代码(UWP/ASP.NET 5/ iOS 和 Android)?
如果是,那么就使用 System.Net.Http API。这可以让你写其它 .NET 平台的代码,比如 ASP.NET 和 .NET Framework 桌面应用。基于 Xamarin, 这 API 同样支持 iOS 和 Android。
对象模型
现在我们理解了这两个相似 API 的目的和原理,我们分别详细看一下它们的对象模型。
System.Net.Http
最上层的抽象是 HttpClient 对象,它作为 HTTP 协议中 “客户端-服务器” 模型中的客户端实体。这个 Client 可以向服务器端发出多个请求(由 HttpRequestMessage 表示),然后收到相应的响应(由 HttpResponseMessage表示)。每个 HTTP 请求和响应的实体 body 和 headers 由基类 HttpContent 、派生类如 StreamContent、MultipartContent 和 StringContent 表示。它们表示不同的 HTTP 实体 body。他们分别提供一些 ReadAs* 方法来读取请求的 body 或者作为 string 、byte 数组或者 stream 进行响应。
每一个 HttpClient 对象在底层有一个代表所有 HTTP协议设置相关的 handler 对象。理论上说,你可以认为这个 handler 代表客户端的 HTTP协议栈。它负责发送客户端的 HTTP请求到服务器端,并且把响应传输回客户端。
在 System.Net.Http API 中使用的默认 handler 类是 HttpClientHandler 。当你创建一个 HttpClient 实体对象时 — 例如,调用 new HttpClient() — 会自动为你的默认 HTTP 协议栈设置创建一个 HttpClientHandler 对象。如果你想更改默认的设置,比如缓存行为、自动压缩、认证、代理,你可以创建你自己的 HttpClientHandler 实体,更改它的属性,然后把它传递给 HttpClient 的构造函数中,比如:
HttpClientHandler myHandler = new HttpClientHandler();
myHandler.AllowAutoRedirect = false;
HttpClient myClient = new HttpClient(myHandler);
链式处理(Chaining of Handlers)
System.Net.Http.HttpClient API 设计的一个核心的优点就是能够插入自定义的 handlers 并且在一个 HttpClient对象的底层创建链式处理对象。比如,你的 app向一个 Web service 查询一些数据。你自定义客户端的逻辑来处理 HTTP 4xx(客户端错误) 和 5xx(服务器端错误),并且采取特定的重试步骤,比如尝试一个不同的地址或者添加用户的凭据。你希望把 HTTP协议相关的设置工作,跟你关心的 web service 返回数据的处理逻辑区分开。
你可以通过创建一个继承自 DelegatingHandler 的新 handler 类(比如 CustomHandler1),然后创建一个它的实体 传递给 HttpClient 的构造函数中。DelegatingHandler 类的 InnerHandler 属性用来指定在这个响应链中另一个 handler — 比如,你可以添加另一个自定义的 handler(如 CustomHandler2)。对于最后的 handler,你可以赋值 inner handeler 为一个 HttpClientHandler 实体 — 这会传递这个请求到系统的 HTTP协议栈。看起来类似于:
实现逻辑的示例:
public class CustomHandler1 : DelegatingHandler { // Constructors and other code here. protected async override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // Process the HttpRequestMessage object here. Debug.WriteLine("Processing request in Custom Handler 1"); // Once processing is done, call DelegatingHandler.SendAsync to pass it on the // inner handler. HttpResponseMessage response = await base.SendAsync(request, cancellationToken); // Process the incoming HttpResponseMessage object here. Debug.WriteLine("Processing response in Custom Handler 1"); return response; } } public class CustomHandler2 : DelegatingHandler { // Similar code as CustomHandler1. } public class Foo { public void CreateHttpClientWithChain() { HttpClientHandler systemHandler = new HttpClientHandler(); CustomHandler1 myHandler1 = new CustomHandler1(); CustomHandler2 myHandler2 = new CustomHandler2(); // Chain the handlers together. myHandler1.InnerHandler = myHandler2; myHandler2.InnerHandler = systemHandler; // Create the client object with the topmost handler in the chain. HttpClient myClient = new HttpClient(myHandler1); } }
注意:
1、如果你打算发送请求到远程服务器,这个 handler 链的最后经常是一个 HttpClientHandler,它主要用作发送请求,然后接收 OS的 HTTP 协议栈响应。或者,你可以使用一个假冒的 handler 来模拟 server 端,然后返回伪造的响应。
2、在发送和接收请求时,为 inner handler 添加额外的处理逻辑,可能导致性能上的损失。所以避免昂贵的异步操作。
有关 chaining handler 更详细的理论,可以参考 Henrik Nielsen 的这篇文章。(注意这个是关于 ASP.NET Web API 版本的,可能和这里 .NET framework 的版本稍微有区别,不过 chaining handler 理论是一样的)
Windows.Web.Http
Windows.Web.Http API 对象模型和上面描述的 System.Net.Http 版本类似 — 它也有一个客户端实体的概念,一个 handler(在这个命名空间叫做 “filter”),并且可以选择在 Client 对象和系统默认过滤器之间插入自定义的逻辑。
大多数的类型定义直接模拟 System.Net.Http 的对象模型,如下:
HTTP client role aspect | System.Net.Http type | Corresponding Windows.Web.Http type |
Client entity | HttpClient | HttpClient |
HTTP request | HttpRequestMessage | HttpRequestMessage |
HTTP response | HttpResponseMessage | HttpResponseMessage |
Entity body of an HTTP request or response | HttpContent | IHttpContent |
Representations of HTTP content as string/stream/etc. | StringContent, StreamContent and ByteArrayContent | HttpStringContent, HttpStreamContent and HttpBufferContent respectively |
HTTP stack/settings | HttpClientHandler | HttpBaseProtocolFilter |
Base class/interface for creating custom handlers/filters | DelegatingHandler | IHttpFilter |
上面关于 System.Net.Http 的链式处理(chaining of handlers)的讨论,同样适用于 Windows.Web.Http API,这里你可以创建一个自定义 filters链,然后把它们传递到 HttpClient 的构造函数中。
实现常见的 HTTP 场景
现在我们通过一些代码片段演示一下两个 HttpClient API 的常见使用场景。如果想参考更详细的内容,可以分别浏览 MSDN 文档 Windows.Web.Http.HttpClient 和 System.Net.Http.HttpClient 。
更改报文头
System.Net.Http:
如果修改所有 HttpClient 发出请求的报文头,使用下面的模式:
var myClient = new HttpClient(); myClient.DefaultRequestHeaders.Add("X-HeaderKey", "HeaderValue"); myClient.DefaultRequestHeaders.Referrer = new Uri("http://www.contoso.com");
如果只修改指定请求的报文头,使用:
HttpRequestMessage myrequest = new HttpRequestMessage(); myrequest.Headers.Add("X-HeaderKey", "HeaderValue"); myrequest.Headers.Referrer = new Uri("http://www.contoso.com");
Windows.Web.Http:
上面的模式同样适用于 Windows.Web.Http API。
注意:
1、一些 headers 是集合类型的,需要使用 Add 和 Remove 方法来编辑它们
2、这个 HttpClient.DefaultRequestHeaders 属性表示对 headers 的默认设置,会被加入到 app 的所有请求中。由于这个请求会被操作系统的 HTTP栈处理,在请求被发送出去前会被加入额外的 headers。
超时设置
System.Net.Http:
在 System.Net.Http 的 API中,有两种方法设置超时。设置客户端所有请求的超时,使用:
myClient.Timeout = TimeSpan.FromSeconds(30);
设置一个请求的超时,使用 cancellation token 模式:
var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(30)); var httpClient = new HttpClient(); var resourceUri = new Uri("http://www.contoso.com"); try { HttpResponseMessage response = await httpClient.GetAsync(resourceUri, cts.Token); } catch (TaskCanceledException ex) { // Handle request being canceled due to timeout. } catch (HttpRequestException ex) { // Handle other possible exceptions. }
Windows.Web.Http:
在 Windows.Web.Http.HttpClient 类型中没有 timeout 属性。因此,你必须使用上面的这种 cancellation token 模式。
身份验证凭据的使用
System.Net.Http:
为了保护用户的验证信息,系统的 HTTP 协议栈默认没有为任何发出的请求添加证书。要使用指定用户的证书,使用下面的模式:
var myClientHandler = new HttpClientHandler(); myClientHandler.Credentials = new NetworkCredential(myUsername, myPassword);
Windows.Web.Http:
对于 Windows.Web.Http API,发送请求时如果需要用户的证书,默认会弹出一个索要用户证书的对话框。要不想使用这个对话框,可以设置 HttpBaseProtocolFilter 的 AllowUI 属性为 false。可以使用指定的证书取代:
var myFilter = new HttpBaseProtocolFilter(); myFilter.ServerCredential = new PasswordCredential(“fooBar”, myUsername, myPassword);
注意:
1、在上面的例子中,myUsername 和 myPassword 是字符串类型的变量,可以弹出 UI来获取用户的输入,或者从 app 的配置中获取。
2、在 UWP app中,这个 HttpClientHandler.Credentials 属性只能被赋值为 null,DefaultCredentials 或者一个 NetworkCredential 类型的对象。
使用 Client 证书
System.Net.Http:
为了保护用户的证书信息,这个API 默认不会发送任何客户端证书到服务器端。想用客户端身份证书验证,使用:
var myClientHandler = new HttpClientHandler(); myClientHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
Windows.Web.Http:
有两种选择来使用客户端证书 — 默认弹出一个 UI来让用户选择证书。或者,你可以通过编程的方式来设置客户端证书,例如:
var myFilter = new HttpBaseProtocolFilter(); myFilter.ClientCertificate = myCertificate;
注意:
1、为了在两个 API 中使用客户端证书,你必须先按照这些步骤把它添加到 app 的商店认证中。在添加了 enterprise capability 的 app,在用户的 “My” store 中也可以使用现有的客户端证书。
2、对于 HttpClientHandler.ClientCertificateOptions,有两种允许的值:自动和手动(Automatic and Manual)。把它设置为 Automatic 时会在 app的 certificate store 中选择最匹配的 client certificate。把它设置为 Manual 时,会确保没有 client certificate 被发出,即使服务器端需要它。
代理设置
对于两个 API,代理的设置会默认使用 Internet Explorer/Microsoft Edge 的设置,并且所有的 HTTP访问都会使用这个设置。这使得 app能自动工作,即使用户通过代理访问互联网。两个 API都没有提供方法让你为 app 设置自定义的代理。不过,你可以选择设置 HttpClientHandler.UserProxy 为 false(System.Net.Http) 或者 HttpBaseProtocolFilter.UseProxy 为 false(Windows.Web.Http)来禁用默认的代理。
Cookie 的处理
默认情况下,对于同一个 app container 来说,两个 API 都会自动保存服务器端返回的 cookies,然后把它们添加到那个 URI后续的请求中。对于相应 URI的 cookies可以进行读取,并且添加自定义的 cookies。最后,两个 API也可以选择禁用发送 cookies 到服务器端:对于 System.Net.Http ,设置 HttpClientHandler.UseCookies 为 false;对于 Windows.Web.Http,设置 HttpBaseProtocolFilter.CookieUsageBehavior为 HttpCookieUsageBehavior.NoCookies 。
Syste.Net.Http:
为当前 app的所有请求添加一个 cookie:
// Add a cookie manually. myClientHandler.CookieContainer.Add(resourceUri, myCookie);
为指定的请求添加一个 cookie:
HttpRequestMessage myRequest = new HttpRequestMessage(); myRequest.Headers.Add("Cookie", "user=foo; key=bar");
对于一个给定的 URI,检查所有 Cookies:
var cookieCollection = myClientHandler.CookieContainer.GetCookies(resourceUri);
Windows.Web.Http:
为当前 app的所有请求添加一个 cookie:
// Add a cookie manually. filter.CookieManager.SetCookie(myCookie);
上面为指定的请求添加一个 Cookie的方式,也适用于 Windows.Web.Http API。
管理 Cookies:
// Get all cookies for a given URI. var cookieCollection = filter.CookieManager.GetCookies(resourceUri); // Delete a cookie. filter.CookieManager.DeleteCookie(myCookie);
注意:
1、对于 Windows.Web.Http API,在当前的应用程序容器(app container) 一些 networking APIs 底层使用 WinlNet 栈实现,所以在 cookie manager里的 cookies 是共享的,比如 Windows.Web.Syndication、Windows.Web.AtomPub、XHR 等等。因此,对于同一个服务器的请求,当通过 Syndication API 从服务器收到的一个 cookie 可能被添加到后面的 HttpClient 的请求中。
每台服务器的最大连接数
默认情况下,对于每台服务器,操作系统底层的 HTTP协议栈最多使用 6个HTTP连接。对于 System.Net.Http 的 HttpClient API没有提供控制这个的方式。对于 Windows.Web.Http API,可以使用:
var myFilter = new HttpBaseProtocolFilter(); myFilter.MaxConnectionsPerServer = 15;
最近的更新
在 Windows10 的 UWP app上,我们为两种 API 都添加了 HTTP/2 的支持。默认这个支持是开启的,所以对开发者来说,你不需要更改代码就可以获得一些好处,比如更低的延迟。对于两个 APIs(System.Net.Http 和 Windows.Web.Http) 都可以显示禁用这个特性,强制使用 HTTP 1.1 或者 1.0 的版本。
对于未来,我们计划添加呼声较高的特性,比如自定义 server SSL 证书的验证,并且在其他地方创建的 HttpClient 添加 handlers/filters 的能力的支持。为了写出更出色的 Windows 的 app,我们期待你反馈这些 API中需要的新的特性。你可以在 UserVoice提出想法,或者最好加入 Windows Insiders program并且通过论坛或者 Windows Feedback app 提交反馈。
这篇文章由 Sidharth Nabar 撰写,Windows Networking APIs team 的项目经理。