ASP.NET Web API 有一個管道(pipeline)在 Client 端與 Server 端之間去處理 HTTP訊息(HTTP messages)。在 Client 端與 Server 端之間被設計成對稱但是獨立,你能只使用 Client 或 Server 來處理。它們兩者都是建立在相同的物件上:
以下都是在 .NET Framework 4.5 Beta 或 System.Net.Http 命名空間(處理 Client 端)或 System.Web.Http 命名空間。
- HttpRequestMessage 表示 HTTP 請求(HTTP request)
- HttpResponseMessage 表示 HTTP 回應(HTTP response)
- HttpMessageHandler 請求與回應的物件處理
Client 訊息處理
在 Client 端 .NET 應用程式能使用 HttpClient 類別來傳送 HTTP 請求。每一個請求,HttpClient 建立一個 HttpRequestMessage 執行個體。或者,應用程式能建立一個 HttpRequestMessage 直接傳遞至 HttpClient。
![]() |
| 圖一:Client 端 HTTP 訊息處理 |
如果你沒有指定訊息處理常式(message handler),HttpMessageHandler 類別會被當成預設使用。HttpMessageHandler 會轉換 HttpRequestMessage 到一個 HTTP 請求,然後透過網路傳送 HTTP 請求,一樣會轉換 HTTP 回應到一個 HttpResponseMessage 類別。
![]() |
| 圖二:HttpClientHandler 轉換 |
應用程式也能提供自訂訊息處理常式給 HttpClient,或是使用 delegator pattern 來鏈結(chain)數個處理常式。
![]() |
| 圖三:自訂訊息處理常式 |
Server 訊息處理
在 Server 端,主機(host)接收 HTTP 請求,並將它們傳遞給從 HttpMessageHandler 而來的 HttpServer 類別,請求會通過數個訊息處理常式,在鏈結最後一個處理常式為 HttpControllerDispatcher,此處理常式使用路由表(routing table)去路由請求至 Web API Controller,Controller 建立回應,然後將回應回傳給訊息處理常式的鏈結。
![]() |
| 圖四:HttpServer 類別處理流程 |
任何訊息處理常式都能快捷的處理此程序和建立回應,直接繞過其它的鏈結。這樣能讓你在訊息處理管道裡早先去執行驗證(validation)與認證(authentication),甚至在訊息到達 controller 之前。
因為 HttpServer 是一個訊息處理常式,你能直接外掛它到 HttpClient 類別中,類似以下的程式碼:
Dim config = New HttpConfiguration()
config.Routes.MapHttpRoute("default",
"api/{controller}/{id}",
New With {.id = RouteParameter.Optional})
Dim server As HttpServer = New HttpServer(config)
' client 直接連絡到 server
Dim client As HttpClient = New HttpClient(server)
Dim response = client.GetAsync("http://kkbruce.net/api/products").Result
在此範例,HTTP 請求是在記憶體中處理完畢,沒有透過網路。當我們沒有實際管理(hosting)的伺服器,以上情境能提供有用的伺服器管道(pipeline)函式測試。
撰寫自訂訊息處理常式
顧名思義,訊息處理常式(message handlers)是對 HTTP 訊息進行操作。在伺服器端,他們處理該請求,在任何 model 綁定發生之前,它將被路由到 Controller。
若要編寫自訂訊息處理常式,需衍生自 System.Net.Http.DelegatingHandler 類別。DelegatingHandler 類別使用授權模式(delegator pattern)。每個 DelegatingHandler 的執行個體都會去參考其他的訊息處理常式,必須呼叫 innerHandler 屬性。這種方式,你可以透過鏈結(chain)方式連結幾個處理常式在一起。
在你的衍生類別,覆寫 SendAsync 方法,此方法有以下的簽章:
Function SendAsync(request As HttpRequestMessage,
cancellationToken As CancellationToken) _
As Task(Of HttpResponseMessage)
此方法採用 HttpRequestMessage 作為輸入,並以非同步方式返回 HttpResponseMessage。典型的 SendAsync 實作如下:
- 處理請求訊息(request message)
- 呼叫 MyBase.SendAsync 去傳送訊息到 innerHandler。此步驟是非同步。
- 處理回應訊息(response message()和回傳給呼叫者
或者,可跳過步驟 2,並直接建立回應訊息,繞過訊息管道的其餘部分。
範例:支援 X-HTTP-Method-Override
X-HTTP-Method-Override 是個不標準的 HTTP 標頭,它設計用戶端為不能發送如 PUT 或 DELETE 等類型的請求,相反的,用戶端可以發送 HTTP POST 並將 X-HTTP-Method-Override 標頭來設置為所需的方法。例如:
X-HTTP-Method-Override: PUT
如果伺服器支援此標頭,它就會將此請求當成一個 HTTP PUT 請求。
下面範例會顯示一個訊息處理常式如何去加入支援 X-HTTP-Method-Override 標頭。
Imports System.Net.Http
''' <summary>
''' 自訂訊息處理常式
''' </summary>
''' <remarks>支援 X-HTTP-Method-Override 標頭</remarks>
Public Class MethodOverrideHandler
Inherits DelegatingHandler
ReadOnly _methods() As String = {"DELETE", "HEAD", "PUT"}
Const _header As String = "X-HTTP-Method-Override"
''' <summary>
''' 覆寫 SendAsync 方法
''' </summary>
''' <param name="request">HttpRequestMessage 物件</param>
''' <param name="cancellationToken">CancellationToken 物件</param>
''' <returns>HttpResponseMessage 物件</returns>
Protected Overrides Function SendAsync(request As HttpRequestMessage,
cancellationToken As Threading.CancellationToken) _
As Threading.Tasks.Task(Of HttpResponseMessage)
' 檢查 HTTP POST 與 X-HTTP-Method-Override
If request.Method = HttpMethod.Post AndAlso request.Headers.Contains(_header) Then
Dim method = request.Headers.GetValues(_header).FirstOrDefault()
If (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase)) Then
' 改變請求方法
request.Method = New HttpMethod(method)
End If
End If
Return MyBase.SendAsync(request, cancellationToken)
End Function
End Class
在 SendAsync 方法,處理常式確認請求訊息是否為 POST 請求,也確認是否含有 X-HTTP-Method-Override 標頭。如果有,驗證標頭的值,然後修改請求的方法。然後處理常式呼叫 MyBase.SendAsync 傳遞訊息到下一個處理常式。
當請求到達 HttpControllerDispatcher 類別,HttpControllerDispatcher 類別將在已變更的請求方法上路由此請求。
範例:新增自訂 Response 標頭
以下訊息處理常式會新增自己標頭(header)到所有回應訊息中:
Imports System.Net.Http
''' <summary>
''' 新增自訂回應標頭
''' </summary>
Public Class CustomHeaderHandler
Inherits DelegatingHandler
''' <summary>
''' 覆寫 SendAsync 函式
''' </summary>
''' <param name="request">HttpRequestMessage 物件</param>
''' <param name="cancellationToken">CancellationToken 物件</param>
''' <returns>HttpResponseMessage 物件</returns>
''' <remarks>透過 Task.ContinueWith 方法來新增自訂回該標頭</remarks>
Protected Overrides Function SendAsync(request As HttpRequestMessage,
cancellationToken As Threading.CancellationToken) _
As Threading.Tasks.Task(Of HttpResponseMessage)
Return MyBase.SendAsync(request, cancellationToken).
ContinueWith(
Function(task)
Dim response As HttpResponseMessage = task.Result
response.Headers.Add("X-Custom-Header", "This is my custom header.")
Return response
End Function)
End Function
End Class
此處理常式會先呼叫 MyBase.SendAsync 去傳遞請求訊息到內部訊息處理常式(inner message handler)。內部處理常式回傳回應訊息,但它不會去使用非同步的 Task(Of T) 物件。回應訊息直到 MyBase.SendAsync 完成非同前不是有效的。
要修改回應,使用 Task.ContinueWith 方法,它將繼續(continuation)增加到非同步任務中。繼續(continuation)是當前任務完成後接下去執行的任務。
範例:驗證 API Key
有些 Web Services 需要用戶端去包含一組 API Key,以下範例會示範如何在訊息處理常式去驗證 API Key。
Imports System.Net.Http
''' <summary>
''' 驗證 API Key
''' </summary>
Public Class ApiKeyHandler
Inherits DelegatingHandler
Property Key As String
Sub New(key As String)
Me.Key = key
End Sub
''' <summary>
''' 覆寫 SendAsync 函式
''' </summary>
''' <param name="request">HttpRequestMessage 物件</param>
''' <param name="cancellationToken">CancellationToken 物件</param>
''' <returns>HttpResponseMessage 物件</returns>
Protected Overrides Function SendAsync(request As HttpRequestMessage,
cancellationToken As Threading.CancellationToken) _
As Threading.Tasks.Task(Of HttpResponseMessage)
If Not ValidateKey(request) Then
Return Threading.Tasks.Task(Of HttpResponseMessage).
Factory.
StartNew(Function()
Return New HttpResponseMessage(Net.HttpStatusCode.Forbidden)
End Function)
End If
Return MyBase.SendAsync(request, cancellationToken)
End Function
''' <summary>
''' 驗證 API Key
''' </summary>
''' <param name="message">傳入訊息</param>
''' <returns>Boolean</returns>
''' <remarks>將訊息參數中的key與物件屬性key進行比對</remarks>
Private Function ValidateKey(message As HttpRequestMessage) As Boolean
Dim query = message.RequestUri.ParseQueryString()
Dim Querykey As String = query("key")
Return Querykey = Me.Key
End Function
End Class
此處理常式會在 URI 查詢字串(query string)搜尋 API Key。(此範例中,我們假設 key 是一個固定字串,在直實實作中應該要有更複雜的驗證。)如果 Key 出現在查詢字串中,處理常式會傳遞此請求到內部處理常式進行處理。
不管如何,如果請求沒有包含合法的 Key,處理常式會去建立一個新的 HTTP 403, Forbidden 狀態的回應訊息。處理常式回傳的回應是一個非同步任務,必須呼叫 Task.Factory.StartNew 來替代呼叫 MyBase.SendAsync。這意思是,內部處理常式從未收到請求,而請求也會繞過 Controller。因此,Controller 可以假設所有傳入的請求都有一個有效的 API Key。
注意:如果 API Key 僅適用於某些 Controller,應該考慮使用動作過濾器(action filter)來替代訊息處理常式。動作過濾器會運作在 URI 路由執行之後。
範例:新增自訂訊息處理常式
如果你管理(hosting)一個 ASP.NET 應用程式,可在 Global.asax 檔案的 Application_Start 方法中去定義訊息處理常式。新增一個處理常式到 HttpConfiguration 物件,此物件是靜態 GlobalConfiguration 物件的成員。
' Note: For instructions on enabling IIS6 or IIS7 classic mode,
' visit http://go.microsoft.com/?LinkId=9394802
Imports System.Data.Entity
Imports System.Data.Entity.Infrastructure
Imports System.Web.Http
Imports System.Web.Optimization
Public Class MvcApplication
Inherits System.Web.HttpApplication
''' <summary>
''' 設定所要增加的自訂訊息處理常式
''' </summary>
''' <param name="config">HTTP 組態設定物件</param>
Shared Sub Configure(config As HttpConfiguration)
config.MessageHandlers.Add(New MethodOverrideHandler())
config.MessageHandlers.Add(New CustomHeaderHandler())
config.MessageHandlers.Add(New ApiKeyHandler("secret"))
End Sub
Sub Application_Start()
' 進行全域組態設定
Configure(GlobalConfiguration.Configuration)
AreaRegistration.RegisterAllAreas()
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters)
RouteConfig.RegisterRoutes(RouteTable.Routes)
BundleConfig.RegisterBundles(BundleTable.Bundles)
End Sub
End Class
以上是 ASP.NET MVC 4 Beta 裡的 Global.asax 檔案,我們新增一個靜態函式 Sub Configure(),增加我們想增加的自訂訊息處理常式。然後到 Application_Start() 將 GlobalConfiguration.Configuration 物件傳入進行設定的動作即可。
在伺服器端,你不需要去設定內部處理常式,Web API Framework 會自動去連接相關訊息處理常式。訊息處理常式會依相反順序去呼叫 MessageHandlers 集合。以上面範例而言,ApiKeyHandler 會先被呼叫,然後是 CustomHeaderHandler,最後是 MethodOverrideHandler。回應訊息會傳播到另一個方向。
如果你是 self-hosting (Web API 的另一種服務形式,以後會介紹),你可以建立一個 HttpSelfHostConfiguration 類別的執行個體,然後新增處理常式到 MessageHandlers 集合裡。
' 需先從 NuGet 安裝 ASP.NET Web API Self Host package
Dim config As New System.Web.Http.SelfHost.HttpSelfHostConfiguration("http://localhost")
config.MessageHandlers.Add(New MethodOverrideHandler())
config.MessageHandlers.Add(New CustomHeaderHandler())
config.MessageHandlers.Add(New ApiKeyHandler("secret"))
在用戶端,傳遞外部訊息處理常式經由 HttpClient 建構式。若要鏈結(chain)多個訊息處理常式,必須設置鏈結(chain)中的每個訊息處理常式的 InnerHandler 屬性。
下面範例示範如何去新增一個自訂處理常式:
Dim handler As New CustomHeaderHandler With {
.InnerHandler = New HttpClientHandler()
}
Dim client As New HttpClient(handler)




沒有留言:
張貼留言
感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。