1、揭秘通用平台的 HttpClient (譯)


  原文鏈接:Demystifying HttpClient APIs in the Universal Windows Platform

  正打算翻譯這篇文章時,發現園子里已經有朋友翻譯過了,既然已經開始了,就再概要的翻譯一遍吧,就不逐字逐句了 :)  。這段時間沒有春節前那么忙了,正好整理一下技術文檔。

 

譯文:

  作為一個 Universal Windows Platform (UWP) app 開發者,如果想通過 HTTP 協議與 web服務器端進行交互,你有很多的 API 可以選擇。兩個最常用、較為推薦的是  System.Net.Http.HttpClientWindows.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 的項目經理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM