首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

服務站:打造 WCF 路由器基礎架構(第 1 部分)

     編輯:關於.NET

託管與使用 WCF 服務通常經歷基本步驟:實作服務、配置端點、託管服務、產生 WSDL 或啟用中繼資料交換讓客戶端產生 Proxy、編寫程式碼實例化 Proxy、呼叫服務操作。雖無需深入內部原理,但即使最簡單情況,客戶端與服務通道也依賴相容配置處理定址語義與訊息篩選,確保呼叫正確操作。

有時在客戶端與目標服務間引入中介服務或路由器服務,以攔截訊息或執行日誌記錄、優先級路由、上線/離線路由、負載平衡、建立安全邊界等活動極為有用。引入此類中介服務時,需對定址與訊息篩選行為做相應調整。

本文深入探討如何使用中介服務(統稱路由器),介紹 WCF 定址與訊息篩選概念,著重路由器方案、路由配置與相應設定選項。第 2 部分將展示如何利用基礎原理實現更高階、更實用的路由功能。

預設定址語義回顧

參考 2007 年 6 月「服務站」Aaron Skonnard 專欄,WCF 處理邏輯/物理端點定址、定址標頭與訊息篩選。本節回顧基本定址功能及其對路由方案的影響。

通常客戶端使用從服務描述產生的 Proxy 直接發送訊息至目標服務。為相容,雙方共享等效約定與端點配置。以圖 1 服務約定與配置為例:

[ServiceContract(Namespace = "https://www.thatindigogirl.com/samples/2008/01")]
public interface IMessageManagerService {
    [OperationContract] string SendMessage(string msg);
    [OperationContract] void SendOneWayMessage(string msg);
}
<endpoint address="https://localhost:8000/MessageManagerService" contract="MessageManager.IMessageManagerService" binding="basicHttpBinding" />

三大關鍵定址需求:

  1. Action 標頭:SendMessage 請求預期 Action = https://.../IMessageManagerService/SendMessage。源自約定命名空間、約定名稱(預設介面名)、操作名稱(預設方法名)。
  2. ReplyAction 標頭:回應預期 ReplyAction = .../SendMessageResponse。源自相同規則附加 "Response" 後綴。
  3. To 標頭:定位服務端點預期 To = https://localhost:8000/MessageManagerService。源自端點 address 屬性(視為邏輯位址)。物理位址通常與邏輯位址匹配,客戶端發送至匹配 To 標頭的物理位址。

服務中繼資料描述這些需求,客戶端產生相容 Proxy 與配置。客戶端端點配置範例:

<endpoint address="https://localhost:8000/MessageManagerService" binding="basicHttpBinding" contract="localhost.IMessageManagerService" name="basicHttp" />

Proxy 使用端點 address 作為邏輯與物理位址。呼叫 SendMessage 時發送含 To、Action 標頭的訊息。通道調度程序依 To 標頭匹配端點邏輯位址、依 Action 標頭匹配操作。

路由器架構:透傳式 vs 處理式

圖 4:透傳式路由器運作——客戶端關係對象為下游服務,訊息經路由器透傳

圖 5:處理式路由器運作——客戶端關係對象為路由器,路由器處理協定標頭並為下游服務建構新訊息

  • 透傳式路由器:對客戶端透明。客戶端關係屬下游服務,訊息恰經路由器傳遞。客戶端需用相容傳輸/編碼發送至路由器,但安全性、可靠會話等協定滿足客戶端生成訊息需求。路由器可查看/插入標頭,但原始訊息元素仍原樣轉發。
  • 處理式路由器:主動處理訊息。客戶端仍需發送與下游服務相容訊息,但傳輸/編碼/協定相容性上客戶端與路由器發生關係。安全性、可靠會話等標頭由路由器處理,路由器可用適當協定為下游服務建構新訊息。

各配置皆有實用執行方法,亦可混合方案。

路由器約定:處理非類型化訊息

路由器接收下游服務訊息並轉發,亦負責接收服務回應返回客戶端。典型路由器約定展示處理任意訊息請求/回應的單一操作:

[ServiceContract(Namespace = "https://www.thatindigogirl.com/samples/2008/01")]
public interface IRouterService {
    [OperationContract(Action = "*", ReplyAction = "*")]
    Message ProcessMessage(Message requestMessage);
}

預設 OperationContract 的 Action/ReplyAction 源自約定命名空間/名稱/操作名。通道調度程序需找到與 Action 標頭完全匹配的操作。設為 "*" 時,無論 Action 為何,未映射具體操作的所有訊息皆發送給全能操作。Action 或 ReplyAction 僅允許一個操作指定 "*"。

ProcessMessage 以 Message 類型收發非類型化訊息。路由器可存取標頭集合與訊息本文,除常用定址標頭(透過強類型屬性反序列化)外無自動序列化。基本路由器僅接收非類型化訊息並原樣轉發至下游服務,等待回應,回應同樣原樣轉發至呼叫客戶端。

轉發訊息與 To 標頭變更問題

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class RouterService : IRouterService {
    public Message ProcessMessage(Message requestMessage) {
        using (ChannelFactory<IRouterService> factory = new ChannelFactory<IRouterService>("serviceEndpoint")) {
            IRouterService proxy = factory.CreateChannel();
            using (proxy as IDisposable) { return proxy.ProcessMessage(requestMessage); }
        }
    }
}

Proxy 通常強類型為目標服務約定,此處用路由器約定利用其轉發任意訊息能力。簡單範例中路由器僅原樣轉發訊息並返回回應。但有一個可能未預期的變更:訊息發送至服務前,"To" 標頭發生了改變。

圖 7:簡單路由器下的定址語義——To 標頭被代理端點邏輯位址覆蓋

預設代理使用其端點配置邏輯位址設定輸出訊息 To 標頭,即使傳入 Message 已有 To 標頭。雖看似好事(服務要求 To 匹配其端點邏輯位址),但若更新的 To 標頭未簽名且服務啟用安全性,訊息將被拒絕。

理想情況:客戶端發送匹配目標服務的 To 標頭,路由器接收並不改變 To 標頭轉發給服務。可透過繫結配置處理。

邏輯定址與物理定址分離策略

引入路由器時,理想狀態:客戶端使用正確服務 To 標頭發送訊息,又能將訊息發送至路由器。兩種主要方法:

方法一:ClientViaBehavior(客戶端感知路由器存在)

<endpoint address="https://localhost:8000/MessageManagerService" binding="wsHttpBinding" ... behaviorConfiguration="viaBehavior"/>
<behavior name="viaBehavior"> <clientVia viaUri="https://localhost:8010/RouterService"/> </behavior>

客戶端 Proxy 依端點邏輯位址生成 To 標頭,但透過路由器物理位址發送。缺點:將客戶端與路由器存在綁定。

方法二:服務端 listenUri(服務感知路由器)

<endpoint address="https://localhost:8010/RouterService" contract="..." binding="wsHttpBinding" listenUri="https://localhost:8000/MessageManagerService"/>

服務中繼資料發布路由器位址給客戶端,客戶端端點反映路由器位址。服務接收匹配其 listenUri 的 To 標頭,路由器先收到訊息。缺點:將服務與路由器綁定,理想是服務無需知曉。

方法三:非繫結 URI 邏輯位址 + 手動通知客戶端物理位址(推薦)

<endpoint address="urn:MessageManagerService" contract="..." binding="wsHttpBinding" listenUri="https://localhost:8000/MessageManagerService"/>

服務使用不繫結路由器/服務的 URI 邏輯位址(如 urn:),手動通知客戶端物理位址(非中繼資料一部分)。兩種情況服務皆收到匹配端點配置的 To 標頭且路由器先收到訊息。

路由器應承擔配置重任,讓客戶端與服務不受其存在約束。 To 標頭絕不能匹配路由器邏輯位址。預設服務用 EndpointAddressMessageFilter 判斷 To 標頭匹配。路由器無法同樣操作,應安裝 MatchAllMessageFilter。透過 ServiceBehavior 的 AddressFilterMode 屬性設為 AddressFilterMode.Any 允許所有 To 標頭通過。

手動定址:保持 To 標頭不變

預設 To 標頭總根據代理端點配置更新。為抑制此行為讓路由器使用原始 To 標頭轉發,路由器繫結配置需啟用手動定址。標準繫結無法設定此屬性,必須使用自訂繫結:

<customBinding>
  <binding name="manualAddressing">
    <textMessageEncoding />
    <httpTransport manualAddressing="true"/>
  </binding>
</customBinding>

圖 9:啟用手動定址的路由器定址語義——標頭不改變

MustUnderstand 標頭處理

簡單路由實作僅在服務未啟用安全性、可靠會話或豐富協定時工作。圖 10 為簡化繫結協定視圖。

圖 10:服務約定與端點配置簡化視圖

圖 11:啟用安全性與可靠會話的透傳配置——協定特定訊息也透傳至服務

問題:服務發送/接收訊息包含接收方必須理解的標頭(MustUnderstand)。透傳路由器未啟用安全性/可靠會話,故無處理相關協定標頭的通道。

解決:將 ServiceBehavior 的 ValidateMustUnderstand=false 指示路由器忽略 MustUnderstand 標頭。但這僅保證來自客戶端的傳入訊息,來自下游服務的回應不在保證範圍。

完整解決:初始化呼叫下游服務的通道工廠時,加入行為指定 MustUnderstandBehavior(false):

factory.Endpoint.Behaviors.Add(new MustUnderstandBehavior(false));

現在協定與服務訊息可在客戶端與服務間自由透傳——假設使用 HTTP 協定。

若使用 TCP 或命名管道等雙向協定,出現另一複雜性:服務可向客戶端發起訊息(如啟用可靠會話時)。高階路由器配置可處理此特例,將在第 2 部分介紹。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved