1. 程式人生 > >手工建立SOAP訊息中名稱空間的處理

手工建立SOAP訊息中名稱空間的處理

 引言

  在典型的 Web 服務場景中,通常使用工具技術來處理名稱空間的所有細微差別。但是有些時候,特別是在使用 SAAJ(SOAP with Attachments API for Java)為特定的 Web 服務構造 SOAP 訊息時,您必須自己處理名稱空間問題。在沒有任何工具輔助的情況下構造訊息――或是部分訊息時――可以使用該技巧。

  雖然名稱空間看似複雜,但您真正只需要掌握的是以下一份簡短的規則清單:

  如果 WSDL 樣式為 RPC,那麼可在 WSDL 繫結的 wsdlsoap:body 元素中檢視名稱空間。
  如果 wsdlsoap:body 有名稱空間屬性(且 Web 服務互操作性組織(WS-I)的 Basic Profile(參見參考資料部分)需要該屬性用於 RPC 樣式),那麼這就是 SOAP 訊息中操作元素的名稱空間。
  如果 wsdlsoap:body 沒有名稱空間,那麼該操作元素不符合要求。

  對於資料元素而言:

  如果元素通過根元素(不是根型別)定義,那麼它的名稱空間就是根元素的名稱空間;
  如果元素不是通過根定義的,那麼該元素不符合要求。

  這些都是簡單的規則,但如同大多數規則一樣,需要對其進行少許說明。本文的其餘部分將展示使用這些規則的各類例項。有兩種常用型別的 Web 服務描述語言(WSDL)檔案: RPC/literal 和 document/literal 封裝。當然還有其它的型別,但在本文中只包含這兩種。

  RPC/literal WSDL

  清單 1 中的 RPC/literal WSDL 有三個操作:op1、op2 和 op3。注意 WSDL 檔案中用粗體強調的不同名稱空間。

  清單 1. RPC/literal WSDL

<?xml version="1.0" encoding="UTF-8"?>
<definitions targetNamespace="http://apiNamespace.com"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://apiNamespace.com"
    xmlns:data="http://dataNamespace.com"
    xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
  <types>
    <schema targetNamespace="http://refNamespace.com"
        xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:tns="http://refNamespace.com">
      <element type="int"/>
    </schema>
    <schema targetNamespace="http://dataNamespace.com"
        xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:ref="http://refNamespace.com"
        xmlns:tns="http://dataNamespace.com"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <import namespace="http://refNamespace.com"/>
      <complexType >
        <sequence>
          <element type="int"/>
          <element type="int"/>
        </sequence>
      </complexType>
          <element nillable="true" type="tns:Data"/>
      <complexType >
        <sequence>
          <element ref="ref:RefDataElem"/>
        </sequence>
      </complexType>
    </schema>
  </types>
  <message >
    <part type="data:Data"/>
  </message>
  <message >
    <part type="data:Data"/>
  </message>
  <message >
    <part type="data:Data"/>
  </message>
  <message >
    <part type="data:Data"/>
  </message>
  <message >
    <part element="data:DataElem"/>
    <part type="data:Data2"/>
  </message>
  <message >
    <part type="data:Data2"/>
  </message>
  <portType >
    <operation >
      <input message="tns:op1Request"/>
      <output message="tns:op1Response"/>
    </operation>
    <operation >
      <input message="tns:op2Request"/>
      <output message="tns:op2Response"/>
    </operation>
    <operation >
      <input message="tns:op3Request"/>
      <output message="tns:op3Response"/>
    </operation>
  </portType>
  <binding type="tns:Sample">
    <wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation >
      <wsdlsoap:operation soapAction=""/>
      <input>
        <wsdlsoap:body namespace= "http://apiNamespace.com" use="literal"/>
      </input>
      <output>
        <wsdlsoap:body namespace= "http://apiNamespace.com" use="literal"/>
      </output>
    </operation>
    <operation >
      <wsdlsoap:operation soapAction=""/>
      <input>
        <wsdlsoap:body namespace= "http://op2Namespace.com" use="literal"/>
      </input>
      <output>
        <wsdlsoap:body namespace= "http://op2Namespace.com" use="literal"/>
      </output>
    </operation>
    <operation >
      <wsdlsoap:operation soapAction=""/>
      <input>
        <wsdlsoap:body use="literal"/>
      </input>
      <output>
        <wsdlsoap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service >
    <port binding="tns:SampleSoapBinding" "Sample">
      <wsdlsoap:address location= "http://localhost:9080/RPCNamespaces/services/Sample"/>
    </port>
  </service>
</definitions>

  WS-I 遵從性

  WS-I為 WSDL 定義遵從性標準。從兩個方面來講,op3 不遵從 RPC/literal WSDL:它並不在繫結的 wsdlsoap:body 中定義名稱空間;它的訊息部分引用了元素,而不是型別。在此提出是為了展示可以用 WS-I 的 Basic Profile 解決的一些名稱空間問題。

  檢視用於每個操作的繫結的 wsdlsoap:body 元素中的名稱空間。op1 和 op2 是規則 1.1 的例項。op3 是規則 1.2 的例項。op1 展示了使用 targetNamespace 的常規例項――在這種情況下是“http://apiNamespace.com”――作為該操作的名稱空間,但是這僅僅是通常情況。op2 使用的名稱空間將不會在 WSDL 中的其他任何地方被使用。op3 無需定義任何名稱空間。

  清單 2、3 和 4 分別展示了 op1、op2 和 op3 的 SOAP 訊息。注意訊息中用粗體強調的名稱空間。

  清單 2. op1 的 RPC/literal 請求/響應 SOAP 訊息

<soapenv:Envelope xmlns:soapenv=
"http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p582:op1 xmlns:p582="http://apiNamespace.com">
      <in>
         <data1>1</data1>
         <data2>2</data2>
      </in>
    </p582:op1>
  </soapenv:Body>
</soapenv:Envelope>

<soapenv:Envelope xmlns:soapenv=
"http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p582:op1Response xmlns:p582="http://apiNamespace.com">
      <op1Return>
        <data1>10</data1>
        <data2>20</data2>
      </op1Return>
    </p582:op1Response>
  </soapenv:Body>
</soapenv:Envelope>

  在上文中已經提及,清單 2 中的 SOAP 訊息遵從規則 1.1。op1 的名稱空間為“http://apiNamespace.com”。這些訊息同樣遵從規則 2.2。所有引數資料都不通過根元素定義,僅僅是根型別――資料――以及它的子元素。既然沒有使用根元素,那麼這些元素都是不合要求的。

  清單 3. op2 的 RPC/literal 請求/響應 SOAP 訊息

<soapenv:Envelope xmlns:soapenv=
         "http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p999:op2 xmlns:p999="http://op2Namespace.com">
      <in>
        <data1>3</data1>
        <data2>4</data2>
      </in>
    </p999:op2>
  </soapenv:Body>
</soapenv:Envelope>

<soapenv:Envelope xmlns:soapenv=
"http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p999:op2Response xmlns:p999="http://op2Namespace.com">
      <op2Return>
        <data1>300</data1>
        <data2>400</data2>
      </op2Return>
    </p999:op2Response>
  </soapenv:Body>
</soapenv:Envelope>

  op1 和 op2 訊息中的唯一真正差別(比較清單 2 和 清單 3)是:op2 的訊息說明了可以使用任意選擇的名稱空間;無需使用 WSDL 定義的 targetNamespace。

  清單 4. op3 的 RPC/literal 請求/響應 SOAP 訊息

<soapenv:Envelope xmlns:soapenv=
         "http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <op3>
      <p10:DataElem xmlns:p10= "http://dataNamespace.com">
        <data1>5</data1>
        <data2>6</data2>
      </p10:DataElem>
      <in2>
        <p971:RefDataElem xmlns:p971= "http://refNamespace.com">7</p971:RefDataElem>
      </in2>
    </op3>
  </soapenv:Body>
</soapenv:Envelope>

<soapenv:Envelope xmlns:soapenv=
"http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <op3Response>
      <op3Return>
        <p971:RefDataElem xmlns:p971="http://refNamespace.com">7</p971:RefDataElem>
      </op3Return>
    </op3Response>
  </soapenv:Body>
</soapenv:Envelope>

  op3 和其它兩個操作區別很大。首先,沒有定義名稱空間,因此根據規則 1.2, <op3> 和 <op3Response> 標籤都是不合要求的。

  第二,名為 in1 的部分是根元素而不是根型別。由於它是元素部分,因此它的名稱被元素名稱 DataElem 取代。由於使用了元素名稱,則根據規則 2.1,必須使用元素的名稱空間“http://dataNamespace.com”。

  最後,規則 2.1 再次為 in2 的型別元素而呼叫。in2 引用了根元素:RefDataElem。該元素用其它名稱空間定義:“http://refNamespace.com”。

  document/literal 封裝的 WSDL

  清單 5 中的 WSDL 和清單 1 中的 WSDL 是等價的。它是 document/literal 封裝而不是 RPC/literal。為這個 WSDL 而生成的 Java API 和為 RPC/literal WSDL 生成的 Java API 是相同,但它們的 SOAP 訊息可能還是有些差別。名稱空間再次用粗體標出。

  清單 5. document/literal 封裝的 WSDL

<?xml version="1.0" encoding="UTF-8"?>
<definitions targetNamespace="http://apiNamespace.com"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://apiNamespace.com"
    xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
  <types>
    <schema targetNamespace="http://refNamespace.com"
        xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:tns="http://refNamespace.com">
      <element type="int"/>
    </schema>
    <schema targetNamespace="http://dataNamespace.com"
        xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:ref="http://refNamespace.com"
        xmlns:tns="http://dataNamespace.com"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <import namespace="http://refNamespace.com"/>
      <complexType >
        <sequence>
          <element type="int"/>
          <element type="int"/>
        </sequence>
      </complexType>
      <element nillable="true" type="tns:Data"/>
      <complexType >
        <sequence>
          <element ref="ref:RefDataElem"/>
        </sequence>
      </complexType>
    </schema>
    <schema targetNamespace="http://apiNamespace.com"
        xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:data="http://dataNamespace.com"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <import namespace="http://dataNamespace.com"/>
      <element >
        <complexType>
          <sequence>
            <element type="data:Data"/>
          </sequence>
        </complexType>
      </element>
      <element >
        <complexType>
          <sequence>
            <element type="data:Data"/>
          </sequence>
        </complexType>
      </element>
      <element >
        <complexType>
          <sequence>
            <element type="data:Data"/>
          </sequence>
        </complexType>
      </element>
      <element >
        <complexType>
          <sequence>
            <element type="data:Data"/>
          </sequence>
        </complexType>
      </element>
      <element >
        <complexType>
          <sequence>
            <element ref="data:DataElem"/>
            <element type="data:Data2"/>
          </sequence>
        </complexType>
      </element>
      <element >
        <complexType>
          <sequence>
            <element type="data:Data2"/>
          </sequence>
        </complexType>
      </element>
    </schema>
  </types>
  <message >
    <part element="tns:op1" />
  </message>
  <message >
    <part element="tns:op1Response" />
  </message>
  <message >
    <part element="tns:op2" />
  </message>
  <message >
    <part element="tns:op2Response" />
  </message>
  <message >
    <part element="tns:op3" />
  </message>
  <message >
    <part element="tns:op3Response" />
  </message>
  <portType >
    <operation >
      <input message="tns:op1Request"/>
      <output message="tns:op1Response"/>
    </operation>
    <operation >
      <input message="tns:op2Request"/>
      <output message="tns:op2Response"/>
    </operation>
    <operation >
      <input message="tns:op3Request"/>
      <output message="tns:op3Response"/>
    </operation>
  </portType>
  <binding type="tns:Sample">
    <wsdlsoap:binding style="document" transport= "http://schemas.xmlsoap.org/soap/http"/>
    <operation >
      <wsdlsoap:operation soapAction=""/>
      <input>
        <wsdlsoap:body namespace=
        "http://apiNamespace.com" use="literal"/>
      </input>
      <output>
        <wsdlsoap:body namespace=
        "http://apiNamespace.com" use="literal"/>
      </output>
    </operation>
    <operation >
      <wsdlsoap:operation soapAction=""/>
      <input>
        <wsdlsoap:body namespace=
        "http://op2Namespace.com" use="literal"/>
      </input>
      <output>
        <wsdlsoap:body namespace=
        "http://op2Namespace.com" use="literal"/>
      </output>
    </operation>
    <operation >
      <wsdlsoap:operation soapAction=""/>
      <input>
        <wsdlsoap:body use="literal"/>
      </input>
      <output>
        <wsdlsoap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service >
    <port binding="tns:SampleSoapBinding" >
      <wsdlsoap:address location= "http://localhost:9080/Wrapped/services/Sample"/>
    </port>
  </service>
</definitions>

  對於 op1 而言,用於 document/literal 封裝服務的 SOAP 訊息和用於 RPC/literal 服務的 SOAP 訊息是相同的。這是最常見的一類 document/literal 封裝操作。請注意此時遵從的是規則 2.1 而不是規則 1.1;名稱空間是從元素而不是從 wsdl:soapbody 獲得。請注意 WSDL 中的約定,封裝元素和 WSDL 自身一樣,都是用相同的名稱空間定義:“http://apiNamespace.com”。它們可以用任意的名稱空間定義,根據該約定,document/literal 封裝的訊息和 RPC/literal 訊息是相同的。

<soapenv:Envelope xmlns:soapenv=
         "http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p582:op2 xmlns:p582="http://apiNamespace.com">
      <in>
        <data1>3</data1>
        <data2>4</data2>
      </in>
    </q0:op2>
  </soapenv:Body>
</soapenv:Envelope>

<soapenv:Envelope xmlns:soapenv=
"http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p582:op2Response xmlns:p582="http://apiNamespace.com">
      <op2Return>
        <data1>300</data1>
        <data2>400</data2>
      </op2Return>
    </p582:op2Response>
  </soapenv:Body>
</soapenv:Envelope>

  WS-I 遵從性

  WS-I 要求 document/literal 繫結不應在其 wsdl:soapbody 中擁有名稱空間屬性。所以 op1 和 op2 都不符合要求。我在 IBM® WebSphere® Studio Application Developer 中執行該 WSDL,IBM® WebSphere® Studio Application Developer 接受不合要求的 WSDL,但可通過忽略任何 wsdl:soapbody 名稱空間來強制符合 WS-I。

  從 op2 的 document/literal 封裝的訊息可以更明顯的看出,您遵循的是規則 2.1 而不是規則 1.1。此時完全忽略了繫結的 wsdl:soapbody 中的名稱空間。

  最後,對比 RPC/literal op3 的 SOAP 訊息(清單 4)和 document/literal 封裝的 op3 SOAP 訊息(清單 7)。和 op2 一樣,唯一的差別是 op3 的名稱空間。 RPC 版本沒有名稱空間,但是 document 版本有名稱空間;按照規則 2.1,如同其它所有的 document 訊息一樣,它從元素的名稱空間來獲取名稱空間。

  清單 7. op3 的 Document/literal 封裝的請求/響應 SOAP 訊息

<soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p582:op3 xmlns:p582="http://apiNamespace.com">
      <p10:DataElem xmlns:p10="http://dataNamespace.com">
        <data1>5</data1>
        <data2>6</data2>
      </p10:DataElem>
      <in2>
        <p971:RefDataElem xmlns:p971= "http://refNamespace.com">7</p971:RefDataElem>
      </in2>
    </p582:op3>
  </soapenv:Body>
</soapenv:Envelope>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p582:op3Response xmlns:p582="http://apiNamespace.com">
      <op3Return>
        <p971:RefDataElem xmlns:p971= "http://refNamespace.com">7</p971:RefDataElem>
      </op3Return>
    </p582:op3Response>
  </soapenv:Body>
</soapenv:Envelope>

  關於預設名稱空間

  您可以使用預設名稱空間來取消對字首的需求。例如,如清單 8 所示,e 和 f 都在預設名稱空間中,此時定義名稱空間為 urn:default;e 在預設名稱空間中的原因是因為預設名稱空間定義在 e 上,且 f 在其中的原因是因為 f 是 e 的子元素。

  清單 8. 預設名稱空間使用例項

<e xmlns="urn:default">
  <f/>
</e>

  以下是一些建議:在例項資料中不要使用預設名稱空間。

  如果使用預設名稱空間,在從上下文中取出某些內容時,例如――那麼此時根本不知道該元素是否符合要求,或者該元素是否已通過某父元素定義為符合要求。

  如果想使子元素符合要求,則必須引入空名稱空間。例如,在清單 9 中,e 在 urn:default 名稱空間中,且 f 通過看起來有些奇怪的方式,被定義為不合要求。

  清單 9. 預設名稱空間中不合要求名稱的例項

<e xmlns="urn:default">
  <f xmlns=""/>
</e>

  可對名稱空間產生影響的模式屬性是 elementFormDefault。該屬性的預設設定是“unqualified”,到目前為止您已經看到的,子元素是不合要求的。但是如果您將 elementFormDefault="qualified" 新增到 document/literal 封裝的 WSDL 中的所有模式中,那麼訊息中所有的元素將通過其父元素的名稱空間而被定義為符合要求。例如,清單 10 包含了 op3 操作的 document/literal 封裝的訊息,該訊息來自清單 5 中的 WSDL,此時 elementFormDefault 是符合要求的。通常,您不希望使用 elementFormDefault="qualified" 屬性,因為它會使訊息變得臃腫。但是在一年甚至更久以前,在各廠商之間存在著互操作性問題,通過設定該屬性有時可以解決這些問題。

  清單 10.使用 elementFormDefault="qualified" 的 op1 Document/literal 封裝請求/響應 SOAP 訊息

<soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p582:op1 xmlns:p582="http://apiNamespace.com">
      <p582:in>
        <p10:data1 xmlns:p10= "http://dataNamespace.com">1</p10:data1>
        <p10:data2 xmlns:p10= "http://dataNamespace.com">2</p10:data2>
      </p582:in>
    </p582:op1>
  </soapenv:Body>
</soapenv:Envelope>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <p582:op1Response xmlns:p582="http://apiNamespace.com">
      <p582:op1Return>
        <p10:data1 xmlns:p10= "http://dataNamespace.com">10</p10:data1>
        <p10:data2 xmlns:p10= "http://dataNamespace.com">20</p10:data2>
      </p582:op1Return>
    </p582:op1Response>
  </soapenv:Body>
</soapenv:Envelope>

  結束語

  在一般環境下,無須考慮 SOAP 訊息中的名稱空間。然而,在某些情況下,例如,必須手工建立 SOAP 訊息,就必須予以考慮。此時,您必須對 WSDL 如何對映到 SOAP 有深入的瞭解。如果按照本文中的規則操作,您就可以用合適的名稱空間手寫 SOAP 訊息,並且不會出現問題。