[Web API] 自定 HTTP Message Handlers

介紹自定 HTTP Message Handler 與 HttpClient Message Handler 的方法並說明其運作流程。

前言


  上篇 WinForm 使用 HttpClient 呼叫 Web API  針對 HTTP Message Handler 有稍微的說明,這篇文章將針對自定 HTTP Message Handler 說明作法,引述上篇文章提到的內容,在通常情況下,服務收到請求後可能會經過數個 Message Handler 逐步處理後再返回訊息至客戶端,這種處理模式稱為 Delegating Handler,如下圖。

 

  因 Web API 與 HttpClient 都能夠自定訊息處理常式,所以接下來的內容分為 Web API 與 HttpClient 兩部分說明。

 

Web API 自定 HTTP Message Handler


  首先針對 Web API 介紹,以服務端接收到請求後交由訊息處理常式處理的過程來說,會有以下的步驟:

  1. HttpServer 從主機取得請求。
  2. HttpRoutingDispatcher 分派請求路由。
  3. HttpControllerDispatcher 將請求發送至 Web API Controller。

 

  在這個過程裡我們可以將自訂的 Message Handler 插入到這個流程中,而自定的 Message Handler 要處理什麼事情就可以自己來決定,例如增加回應標頭,下圖為一個將自定 Message Handler 插入訊息處理常式通道的示圖。

 

  要自定一個 Message Handler 需要繼承 DelegatingHandler,DelegatingHandler 含於 System.Net.Http 命名空間下,除了繼承 DelegatingHandler 之外還需要覆寫 SendAsync 方法,如下


protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
    return base.SendAsync(request, cancellationToken);
}

 

  在 SendAsync 方法中的處理流程如下

  1. 接收處理請求。
  2. 呼叫 base.SendAsync() 方法,將請求發送到內部處理程序。
  3. 內部處理程序將返回響應訊息,注意此時將為非同步處理。
  4. 處理響應訊息後返回客戶端。

 

  讓我們把以上步驟拆解後會比較清楚,這邊要注意的是因為 base.SendAsync() 是非同步處理方法,所以必須要將 SendAsync() 方法加上 async await 關鍵字。


// 方法須加入 async 關鍵字
protected async override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
    // 1.接受請求
    // 2.呼叫 base.SendAsync() 方法,將請求發送至內部處理常式
    // 3.因 base.SendAsync() 方法為非同步作業方法,故需在呼叫前加入 await 關鍵字等待作業完成後繼續
    HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
    // 4.處理響應的返回訊息後回傳至客戶端
    // do something...
    return response;
}

 

  實際上我們可以附加一個自定標頭於回應訊息中,如下:


protected async override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
    HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
    response.Headers.Add("X-Custom-Header", "自定回應標頭標籤");
    return response;
}

 

  另外先前有提到能夠定義多個 Message Handler,接下來就來看看定義多個 Message Handler 時運作的流程,如下。


public class Custom1Handler : DelegatingHandler
{
    protected async override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        response.Headers.Add("X-Custom1-Header", "This is a customer 2 handler.");
        return response;
    }
}

public class Custom2Handler : DelegatingHandler
{
    protected async override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        response.Headers.Add("X-Custom2-Header", "This is a customer 2 handler.");
        return response;
    }
}

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // 省略...

        // 註冊 CustomerHandlers
        config.MessageHandlers.Add(new Custom1Handler());
        config.MessageHandlers.Add(new Custom2Handler());
    }
}

 

  使用 Debug 模式透過 WinForm 呼叫服務後查看回應訊息內容的可以發現,回應標頭中多了 X-Custom1 與 X-Custom2。

 

  從以上訊息中可以發現 Message Handler 的確會循序執行所定義的 Custom Message Handler,就如同本節一開始說介紹的那張圖一樣,循序流程如下

  1. 接收到客戶端請求
  2. 執行 Custom1Handler 的 base.SendAsync() 方法傳遞訊息
  3. 執行 Custom2Handler 的 base.SendAsync() 方法傳遞訊息
  4. 請求傳入內部訊息處理常式至 Controller 處理
  5. 內部訊息處理常式處理完成後返回響應
  6. Custom2Handler 收到返回訊息後加入自定標頭2
  7. Custom1Handler 收到 Custom2Handler 返回訊息後加入自定標頭1
  8. 返回訊息至客戶端

 

HttpClient 自定 HttpClient Message Handler


  看完了以上 Web API 的自定 Message Handler 的介紹,想必對於 HttpClient 自定 Message Handler 應該是也有些概念了吧,下方是 HttpClient 的自定消息處理常式的通道示圖。

 

  在 HttpClient 自定 Message Handler 基本上是跟 Web API 中差不多,一樣必須透過繼承 DelegatingHandler 或 HttpClientHandler 後覆寫 SendAsync 方法,而其中主要的差異在於 Message Handler 中如需要在發送前修改標頭,則需要在發送請求前處理,如下


protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
    request.Headers.Add("X-Custom-Client-Header", "This is a Client customer handler.");
    return base.SendAsync(request, cancellationToken);
}

 

  依照以上作法發送請求後,Web API 收到請求時將帶入自定標頭,我們從 Debug 模式下查看要求的標頭清單,如下

 

  接下來說明該如何在 HttpClient 中指定使用哪一個自定 Message Handler,在 Web API 中我們可以在 WebApiConfig.cs 的 Register 方法中註冊 Message Handler,但是在 HttpClient 中,我們需要使用另外的方式處理。

 

  首先必須要先加入 System.Net.Http.Formatting 組件參考。

 

  在 System.Net.Http.Formatting 命名空間中,提供了 HttpClientFactory 類別可以使用,HttpClientFactory 用於建立新的 HttpClient 執行個體的工廠,我們需要使用 HttpClientFactory 類別的 Create(DelegatingHandler[]) 方法來加入自定的 Message Handler,建立的方式就是透過工廠的 Create 方法並傳入實體化的 Handler 物件,如下


private async void GetAllProducts()
{
    HttpClient client = HttpClientFactory.Create(new CustomHandler());
    HttpResponseMessage response = await client.GetAsync("http://localhost:49988/api/products");
    response.EnsureSuccessStatusCode();
    string responseBody = await response.Content.ReadAsStringAsync();
    ShowResult(JsonConvert.DeserializeObject<List<Product>>(responseBody));
}

 

  透過 HttpClientFactory 的 Create 方法所實體化的 HttpClient 物件,能夠將 HTTP 回應訊息委派給指定的 Message Handler 處理,在此要注意的是如傳入多個 Message Handler,當發送請求時執行順序是由第一個傳入的 Message Handler 開始,但是當接收返回訊息時,最先收到返回訊息的 Message Handler 將是傳入順序的最後一個 Message Handler,以上就是自定 Message Handlers 的說明。

 

範例程式碼


TWebApi_04.part01.rar

TWebApi_04.part02.rar

TWebApi_04.part03.rar

 

參考資料


HTTP Message Handlers

HttpClient Message Handlers

HttpServer 類別

HttpRoutingDispatcher 類別

HttpClientFactory 類別

 

 


以上文章敘述如有錯誤及觀念不正確,請不吝嗇指教
如有侵權內容也請您與我反應~謝謝您 :)