可靠的 XML Web Service (2)
2024-09-05 20:55:53
供稿:網(wǎng)友
標頭的作用
在查看代碼之前,我們需要了解一下 soap 主題,即標頭。soap 1.1 規(guī)范中談論最少的內(nèi)容之一就是 soap 標頭。標頭提供了一種擴展消息處理體系結(jié)構(gòu)的簡單方法。soap 1.1 規(guī)范中提到:標頭在實現(xiàn)與消息主體沒有特定關(guān)系的處理規(guī)則(例如驗證和事務管理)時非常有用。對任何類型的消息來說,soap 標頭都是以獨立方式對可靠性信息進行編碼的完美解決方案。規(guī)范中還概述了實施和處理這些標頭的標準和規(guī)則。
下面我們來看看如何實現(xiàn)包含可靠性信息的 soap 標頭。首先要為標頭確定架構(gòu)。這很重要,因為它正是最終用戶在支持該標頭的 web 服務的 wsdl 文件中看到的實際效果。對于此實現(xiàn)方案,我直接將處理 api 映射到 soap 標頭。
我來解釋一下。在我的處理 api 中有一個名為 reliabilityinfo 的類。對此類進行實例化時,它將在運行時變成動態(tài)對象。也就是說,您可以從可靠性的角度設(shè)置確定如何處理出站消息的屬性。此對象還可以在運行時序列化為 soap 標頭。下面是該類及其成員的一個快照。為了清楚起見,我刪除了具體的實現(xiàn)和私有成員。
[xmlrootattribute(elementname="reliableheader", _
namespace="http://ericrp/reliableheader/2001/", isnullable=false)]
public class reliabilityinfo : soapheader
{
public string destination{}
public string conversationid{}
public int messageid{}
public messagestatus status{}
public datetime senddate{}
public datetime expiredate{}
public string ackurl{}
public enum messagestatus{}
[xmlignore]
public int maxretry{}
[xmlignore]
public string text{}
}
序列化為出站 soap 消息時,標頭如下所示:
<soap:header>
<reliableheader xmlns="http://ericrp/reliableheader/2001/">
<conversationid>b9e029e1-af0f-42cb-83b0-7888f9e3ffc4</conversationid>
<messageid>1</messageid>
<status>新</status>
<senddate>2001-11-06t14:59:02.1021226-08:00</senddate>
<expiredate>2001-11-06t18:59:02.1021226-08:00</expiredate>
<ackurl>http://localhost:8082/ericrpack/poack.asmx</ackurl>
</reliableheader>
</soap:header>
這是通過 .net 框架中兩個非常重要的類實現(xiàn)的。基本 xml 序列化塊使用 system.xml.serialization 名稱空間類來進行處理。這兩個類一個是 xmlrootattribute 屬性類,我使用它告訴序列化程序?qū)⑽臋n碎片的根稱為 reliableheader,并將它與名稱空間相關(guān)聯(lián)。此后,所有公共成員也同樣會被序列化,除非您告訴序列化程序忽略該成員。我使用的另一個重要名稱空間是 system.web.services.protocols。具體地說,即 soapheader 類。從該類進行繼承時,它會自動將序列化的 xml 加入到 soap 消息中。本文稍后將論述如何將標頭加入消息。
這非常強大,因為我不僅可以使用類型明確的、編譯好的對象作為標頭的基礎(chǔ),而且在這些類的成員內(nèi)部還有特定的實現(xiàn)。
綜述
好啦,上面只是對可靠性進行了簡單的論述,并闡述了我自己的規(guī)范。下面讓我們看一看它的代碼。
擴展 web 服務客戶端代理
第一步是重新設(shè)置現(xiàn)有的 .net web 服務客戶端代理。記住,此協(xié)議可以在任何 soap 處理引擎中實現(xiàn)。我選擇了 .net 框架,因為它易于使用、基于標準且可擴展。
選取一個現(xiàn)有的 web 服務客戶端代理(例如 purchaseorderproxy),然后添加以下代碼:
該類中必須有名為 reliableheader 的公共成員,并且其類型必須為 ericrp.reliabilityinfo。此成員將在運行時通過調(diào)用客戶端進行設(shè)置。稍后將使用此成員為出站消息提供標頭信息,并提供在跟蹤過程中應用了該標頭的消息的狀態(tài)信息。例如:
public class purchaseorderproxy :
system.web.services.protocols.soaphttpclientprotocol
{
public reliabilityinfo reliableheader;
//為了清楚起見,此處省略了其他代碼
}
在 web 服務客戶端代理中調(diào)用的方法必須使用以下屬性進行批注:
[soapheader("reliableheader", required=true)]
在序列化過程中,此屬性將在運行時把適當?shù)臉祟^值加入到出站 soap 消息中。此處的 submitmessage 方法使用 soapheader 屬性進行標記。注意,reliableheader 是在步驟 1 中實現(xiàn)的成員,而且是必須的。也就是說,如果不在運行時設(shè)置此成員,將產(chǎn)生異常。例如:
[soapheader("reliableheader", required=true)]
public void submitmessage(object message)
{
this.invoke("submitmessage", new object[] {message});
}
在 web 服務客戶端代理中調(diào)用的方法必須包含以下屬性:
[ericrp.client.rpclienttrace.traceextension()]
此屬性表示該方法支持自定義 soap 擴展。在消息被序列化之前和之后、且在消息被發(fā)送至底層傳輸機制之前,這種 soap 擴展將在運行時被調(diào)用。我通常是在消息從客戶端計算機發(fā)送出去之前,對它進行一些簡單的跟蹤和記錄。稍后再查看此擴展的實現(xiàn)情況。例如:
[ericrp.client.rpclienttrace.traceextension()]
[soapheaderattribute("reliableheader", required=true)]
public void submitmessage(object message)
{
this.invoke("submitmessage", new object[] {message});
}
該類必須從 ericrp.client.rpclienttrace.iclienttrace 實現(xiàn)。這種接口實現(xiàn)方案提供了基本的跟蹤功能,以檢查調(diào)用程序是否支持特定的跟蹤協(xié)議。在后面的跟蹤功能中可以看到此代碼。例如:
public class purchaseorderproxy :
system.web.services.protocols.soaphttpclientprotocol,
ericrp.client.rpclienttrace.iclienttrace
{
public reliabilityinfo reliableheader;
}
最后,該類必須實現(xiàn) iclienttrace.getreliabilityinfo 函數(shù),該函數(shù)是 iclienttrace 接口要求的唯一函數(shù)。這是一個簡單的機制,客戶端可以使用它在運行時將消息的狀態(tài)信息發(fā)送到跟蹤服務。例如:
public class purchaseorderproxy :
system.web.services.protocols.soaphttpclientprotocol,
ericrp.client.rpclienttrace.iclienttrace
{
public reliabilityinfo reliableheader;
ericrp.reliabilityinfo ericrp.client.rpclienttrace.iclienttrace.getreliabilityinfo()
{
return reliableheader;
}
}
看起來代碼可能很多,但實際上卻很簡單。事實上,如果時間再多一些,我可以創(chuàng)建從 soaphttpclientprotocol 繼承的新類并明確加以實現(xiàn),但這種方式對于服務器端來說會更有趣。
擴展 web 服務服務器存根
下一步,我們來擴展現(xiàn)有的 web 服務服務器存根。選取一個現(xiàn)有的 .net web 服務類(例如 processpurchaseorder),然后添加以下代碼:
該類中必須有一個類型為 ericrp.reliabilityinfo 的公共成員 reliableheader。稍后將使用此成員為入站消息提供反序列化標頭信息,并提供在跟蹤過程中應用了該標頭的消息的狀態(tài)信息。例如:
public class processpurchaseorder :
system.web.services.webservice
{
public reliabilityinfo reliableheader;
}
在 web 服務存根中調(diào)用的方法必須使用以下屬性進行批注:
[soapheader("reliableheader", required=true)]
在反序列化過程中,此屬性將在運行時對入站 soap 消息中適當?shù)臉祟^值進行反序列化。此處的 submitmessage 方法使用 soapheader 屬性進行標記。例如:
[webmethod]
[soapheader("reliableheader", required=true)]
public void submitmessage(object message)
{
//為了清楚起見,此處省略了一些代碼
}
在 web 服務存根中調(diào)用的方法必須使用以下屬性進行批注:
[ericrp.server.rpservertrace.traceextension()]
此屬性表示該方法支持自定義 soap 擴展。稍后再查看此擴展的實現(xiàn)情況。
注意:此擴展與客戶端擴展不同。
例如:
[soapheader("reliableheader", required=true)]
[ericrp.server.rpservertrace.traceextension()]
public void submitmessage(object message)
{
//為了清楚起見,此處省略了一些代碼
}
在 web 服務存根中調(diào)用的方法必須使用以下屬性進行批注:
[soapdocumentmethod(oneway=true)]
此屬性表示被調(diào)用的函數(shù)不返回值。更具體地說,即一旦消息被反序列化,此屬性就會強制 web 服務向客戶端返回 http 202 響應。對于分離消息的最終處理來說,這不失為一個有效的機制,否則,客戶端就不得不同步地等待服務返回響應。例如:
[webmethod]
[soapdocumentmethod(oneway=true)]
[soapheader("reliableheader", required=true)]
[ericrp.server.rpservertrace.traceextension()]
public void submitmessage(object message)
{
//為了清楚起見,此處省略了一些代碼
}
該類必須從 ericrp.server.rpservertrace.iservertrace 實現(xiàn)。這種接口實現(xiàn)方案提供了基本的跟蹤功能,以檢查調(diào)用程序是否支持特定的跟蹤協(xié)議。在后面的跟蹤功能中可以看到此代碼。例如:
public class processpurchaseorder :
system.web.services.webservice, ericrp.server.rpservertrace.iservertrace
{
public reliabilityinfo reliableheader;
}
最后,該類必須實現(xiàn) iservertrace.getreliabilityinfo 函數(shù)。這是 iservertrace 接口要求的唯一函數(shù)。例如:
public class processpurchaseorder :
system.web.services.webservice, ericrp.server.rpservertrace.iservertrace
{
public reliabilityinfo reliableheader;
ericrp.reliabilityinfo ericrp.server.rpservertrace.iservertrace.getreliabilityinfo()
{
return reliableheader;
}
}
好啦,現(xiàn)有的 web 服務客戶端和服務器已準備就緒。下面我們來看看 soapextension 是如何工作的。
查看 rpclienttrance 和 rpservertrace
為了實現(xiàn)可靠性處理,我決定使用 soapextension。soapextension 是一個可繼承的基類,使用它可以跟蹤 soap 消息的出站序列化和入站反序列化。正是在這個跟蹤過程中,對消息進行記錄并檢查其狀態(tài)。記得前面講過,web 服務客戶端代理方法實現(xiàn) [ericrp.client.rpclienttrace.traceextension()]。當調(diào)用該方法時,此屬性將在 soap 消息出站序列化時調(diào)用以下代碼。
需要特別指出的主要函數(shù)是 processmessage。發(fā)送出站消息時,processmessage 將提供有關(guān)該消息序列化之前和之后的全部狀態(tài)信息。這時,將檢查誰在調(diào)用并將 client 屬性的類級別成員與當前消息分離。然后檢查消息是否處于 afterserialize 狀態(tài)。如果已經(jīng)序列化,則可以在消息被發(fā)送至服務器之前進行記錄。通過名為 processoutgoingmessagetext 的自定義函數(shù),首先進行一些流交換以免破壞底層消息流。然后檢查客戶端是否支持 iclienttrace 接口。如果客戶端支持該接口,則表明它們也支持可靠性協(xié)議。通過接口檢查功能,可以調(diào)用 getreliabilityinfo 以便將當前消息返回可應用于該消息的 conversationmanager,然后設(shè)置一些屬性并調(diào)用 logmessage。logmessage 將當前消息信息寫入本地數(shù)據(jù)庫,然后發(fā)送事件,通知客戶端已記錄該消息。
public class rpclienttrace : soapextension
{
public override void processmessage(soapmessage message)
{
soapclientmessage tmpmsg = (soapclientmessage)message;
_client = tmpmsg.client;
if (message.stage == soapmessagestage.afterserialize)
{
processoutgoingmessagetext(message);
}
}
public void processoutgoingmessagetext(soapmessage message)
{
newstream.position = 0;
textreader reader = new streamreader(newstream);
stringbuilder strmessage = new stringbuilder();
strmessage.append(reader.readtoend());
newstream.position = 0;
copy(newstream, oldstream);
if(_client is client.rpclienttrace.iclienttrace)
{
try
{
client.rpclienttrace.iclienttrace _ptrclient = _
(client.rpclienttrace.iclienttrace)_client;
reliabilityinfo rinfo = _ptrclient.getreliabilityinfo();
rinfo.text = strmessage.tostring();
rinfo.destination = message.url;
rinfo.manager.logmessage(rinfo);
}
catch(exception e)
{
throw e;
}
}
}
}
服務器端的情況有點復雜,因為服務器需要為入站消息執(zhí)行額外的操作。被調(diào)用的 web 方法用于實現(xiàn) [ericrp.server.rpservertrace.traceextension()]。以下代碼將在入站消息反序列化之前和之后被調(diào)用:
public class rpservertrace : soapextension
{
public override void processmessage(soapmessage message)
{
try
{
switch (message.stage)
{
case soapmessagestage.beforedeserialize:
readincomingmessagetext(message);
break;
case soapmessagestage.afterdeserialize:
soapservermessage tmpmsg = (soapservermessage)message;
_server = tmpmsg.server;
if(_server is server.rpservertrace.iservertrace)
{
server.rpservertrace.iservertrace _ptrserver = _
(server.rpservertrace.iservertrace)_server;
reliabilityinfo rinfo = _ptrserver.getreliabilityinfo();
ericrp.reliabilityinfo tempinfo = (ericrp.reliabilityinfo)message.headers[0];
tempinfo.text = _tempmessage;
server.conversationmanager manager = new server.conversationmanager();
manager.processmessage(tempinfo);
}
break;
}
}
進行流交換和接口檢查后,將在服務器對話管理器上調(diào)用 processinboundmessage。processinboundmessage 用于檢查核心消息的狀態(tài)。消息限制程序即在此使用。首先檢查消息是否過期,然后檢查其是否重復,最后檢查其是否有序。如果滿足所有三個條件,則將記錄該消息并向客戶端發(fā)送確認;如果不能滿足任一條件,則將更改消息的狀態(tài)并向客戶端發(fā)送確認。
public void processinboundmessage(ericrp.reliabilityinfo rinfo)
{
try
{
if(isexpired(rinfo))
{
rinfo.status = ericrp.reliabilityinfo.messagestatus.expired;
sendack(rinfo);
}
else if(isdulplicate(rinfo))
{
rinfo.status = ericrp.reliabilityinfo.messagestatus.duplicate;
sendack(rinfo);
}
else if(isnotordered(rinfo))
{
rinfo.status = ericrp.reliabilityinfo.messagestatus.notordered;
sendack(rinfo);
}
else
{
rinfo.status = ericrp.reliabilityinfo.messagestatus.success;
logmessage(rinfo);
sendack(rinfo);
}
}
catch(exception e)
{
throw e;
}
}
}
要使本示例在生產(chǎn)環(huán)境中可行,還需要做大量的實施工作。更重要的是,api 應當基于一個以后將會發(fā)布的公共標準。本文的主要目的就是引發(fā)讀者思考問題,了解一下 .net 框架中的主要 web 服務類,我想這兩個目的都已經(jīng)達到。如果您正在尋求可靠異步消息處理的成熟可用的實現(xiàn)方案,建議您看一看 microsoft biztalk™ server 2000。
最后,您可以使用類似的跟蹤功能執(zhí)行所有類型的操作,例如加密、客戶分配和通知。記住,這項附加的功能增加了 web 服務所需的處理基礎(chǔ)結(jié)構(gòu)。
展望
無論是在規(guī)范還是在實現(xiàn)方面,xml web service 的未來都是光明的。microsoft 將以協(xié)作的、標準驅(qū)動的方式工作,確保 xml web service 成為編寫松散耦合的分散式應用程序的最佳體系結(jié)構(gòu)。可靠的消息處理和事務規(guī)范將在以后發(fā)布。盡管還有大量的實現(xiàn)工作要做,但您現(xiàn)在就可以開始使用 soap 和 wsdl 構(gòu)建您的框架。與公共規(guī)范進程越接近,以后的改動工作就越容易進行。soap 規(guī)范就是這種概念的一個顯著例子。世界各地的開發(fā)人員都可以跟蹤 soap 規(guī)范的發(fā)展進程,因此可以毫不費力地對自己的實現(xiàn)方案進行相應地調(diào)整。
希望我能有一個水晶球,為您準確地描繪未來 xml web service 的基礎(chǔ)結(jié)構(gòu);希望我們目前依賴的所有公用服務,例如安全性、事務、存儲、查詢、路由、進程協(xié)調(diào)等等,將來都能夠直接映射至 web 服務體系結(jié)構(gòu);而且 gxa 實現(xiàn)方案可以滲透到核心開發(fā)語言、應用程序框架、業(yè)務處理框架和企業(yè)基礎(chǔ)結(jié)構(gòu)中。到那時,我們就能夠真正地以無縫方式將任意兩種服務耦合在一起。但愿 ericrp 不會真的成為服務背后的可靠性層。<微笑>