java 使用 spring webservice 發佈的 service,由php使用SoapClient調用。
遇到有一個奇怪的現象,java調用發佈的webservice,沒有問題,使用工具SOAP UI調用,沒有問題,
同樣的php代碼調用google的天氣服務沒問題,調用我們的wsdl就不行。
經過摸索調試,發現如下問題,以及解決的過程。
首先發布的wsdl如下:
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
- <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://www.phpxiaoxin.com/doorway" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.phpxiaoxin.com/doorway" targetNamespace="http://www.phpxiaoxin.com/doorway">
- <wsdl:types>
- <s:schema xmlns:dw="http://www.phpxiaoxin.com/doorway" xmlns:s="http://www.w3.org/2001/XMLSchema" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.phpxiaoxin.com/doorway">
- <s:complexType name="Error">
- <s:attribute name="Code" type="s:string" use="required"/>
- <s:attribute name="Message" type="s:string" use="required"/>
- </s:complexType>
- <s:simpleType name="Status">
- <s:restriction base="s:string">
- <s:enumeration value="Successful"/>
- <s:enumeration value="Failed"/>
- </s:restriction>
- </s:simpleType>
- <s:complexType abstract="true" name="BaseRequest">
- <s:attribute name="Token" type="s:string" use="required"/>
- <s:attribute name="UserName" type="s:string" use="required"/>
- <s:attribute name="Password" type="s:string" use="required"/>
- </s:complexType>
- <s:complexType abstract="true" name="BaseResponse">
- <s:sequence>
- <s:element minOccurs="0" name="Error" type="dw:Error"/>
- </s:sequence>
- <s:attribute name="Token" type="s:string" use="required"/>
- <s:attribute name="Status" type="dw:Status" use="required"/>
- </s:complexType>
- <s:complexType name="StatusResponse">
- <s:complexContent>
- <s:extension base="dw:BaseResponse"/>
- </s:complexContent>
- </s:complexType>
- <s:attribute name="RatePlanCode" type="s:string" use="required"/>
- <s:attribute name="RoomTypeCode" type="s:string" use="required"/>
- <s:attribute name="NumberOfUnits" type="s:int" use="required"/>
- <s:attribute name="HotelCode" type="s:string" use="required"/>
- <s:attribute name="DistributorReservationId" type="s:string" use="required"/>
- </s:complexType>
- <s:element name="PingRequest">
- <s:complexType>
- <s:complexContent>
- <s:extension base="dw:BaseRequest">
- <s:attribute name="Echo" type="s:string" use="required"/>
- </s:extension>
- </s:complexContent>
- </s:complexType>
- </s:element>
- <s:element name="PingResponse">
- <s:complexType>
- <s:complexContent>
- <s:extension base="dw:StatusResponse">
- <s:attribute name="Echo" type="s:string" use="required"/>
- </s:extension>
- </s:complexContent>
- </s:complexType>
- </s:element>
- </s:schema>
- </wsdl:types>
- <wsdl:message name="PingResponse">
- <wsdl:part element="tns:PingResponse" name="PingResponse">
- </wsdl:part>
- </wsdl:message>
- <wsdl:message name="PingRequest">
- <wsdl:part element="tns:PingRequest" name="PingRequest">
- </wsdl:part>
- </wsdl:message>
- <wsdl:portType name="Doorway">
- <wsdl:operation name="Ping">
- <wsdl:input message="tns:PingRequest" name="PingRequest">
- </wsdl:input>
- <wsdl:output message="tns:PingResponse" name="PingResponse">
- </wsdl:output>
- </wsdl:operation>
- </wsdl:portType>
- <wsdl:binding name="DoorwaySoap11" type="tns:Doorway">
- <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
- <wsdl:operation name="Ping">
- <soap:operation soapAction=""/>
- <wsdl:input name="PingRequest">
- <soap:body use="literal"/>
- </wsdl:input>
- <wsdl:output name="PingResponse">
- <soap:body use="literal"/>
- </wsdl:output>
- </wsdl:operation>
- </wsdl:binding>
- <wsdl:service name="HotelDoorwayService">
- <wsdl:port binding="tns:DoorwaySoap11" name="DoorwaySoap11">
- <soap:address location="/soap/doorway/"/>
- </wsdl:port>
- </wsdl:service>
- </wsdl:definitions>
其中定義了一個方法"Ping" 輸入爲PingRequest 和 PingResponse 其中PingRequest包含一個echo的屬性,並集成一個BaseRequest的對象。
下面是使用php調用的代碼:
- <?php
- ini_set("soap.wsdl_cache_enabled", "0"); // disabling WSDL cache
- $wsdl="http://10.10.10.10:8888/doorway/soap/doorway/doorway.wsdl";
- $soap=new SoapClient($wsdl, array( 'trace'=>true,'cache_wsdl'=>WSDL_CACHE_NONE, 'soap_version' => SOAP_1_1));
- $method="Ping";
- $params = array('Token'=>'E30ED3AA-65DE-48F9-BEA4-BA021B119625','UserName'=>'cccc','Password'=>'pppp', 'Echo'=>'hello');
- try{
- $result=$soap->$method($params);
- }catch(Exception $e) {
- echo "Exception: " . $e->getMessage();
- }
- //$result爲stdClass類型,因此不能使用 echo $result的方式輸出,會報錯的。
- echo $result->Echo;
- ?>
最初我們調用的時候獲取到的錯誤是找不到Ping方法,這個後來不確定是如何解決的。
後來加了SoapClient 的調用參數WSDL_CACHE_NONE讓soap調用的時候不緩存wsd,以避免服務器修改了wsdl無法及時更新。設置此參數後,每次調用,都會重 新load wsdl文件,因此監控java的log,將會拿到一個get請求,起初我們以爲是調用wsdl的請求,後來發現,他是拿wsdl的請求。
過了這關後,會遇到如下錯誤:Unable to parse URL
這個原因經過google之後發現問題出在wsdl上面:<soap:address location="/soap/doorway/"/>
這裏有的wsdl會:<soap:address location=""/>
這種方式使用java調用是沒有問題的,但是使用php調用就是不行。再加上錯誤的提示信息,可以理解爲,SoapClient沒有智能的解析這個 location,因此無法調用到soap的地址(這裏稱之爲地址,其實我也不知道是什麼意思,要想搞懂的可以看wsdl的協議。)
按照往上介紹的方法,在SoapClient調用裏面增加參數:location =>"http://www.phpxiaoxin.com/soap/doorway"
但這種方法顯然是不太好的,更好的方法是在生成的wsdl中就將location參數直接設置成絕對的url地址不要是相對的,也不要是空。
另外需要注意一個細節就是soap_version,分爲:SOAP_1_1,SOAP_1_2 這兩者發送的header是不一樣的,一個是:text/xml一個是soap/xml,有的時候不兼容,就會將請求拒絕掉。
最後還有一個問題就是組裝調用方法參數:
所調用方法應該組裝的參數,主要看方法對應的wsdl中的input,對於Ping方法來說,就是PingRequest。
pingRequest繼承了一個BaseRequest,那麼相當於PingRequest有:token、username、password,以及自身的echo,因此將這些屬性直接組裝成array塞進去就ok了。
這裏沒有實驗過一個方法對應多個輸入對象應該如何處理,大家可以自己去試。而對已簡單到String類型的輸入參數來說,我這邊的param爲:$params = array('arg0'=>"hello111");
不過我的這個我使用的是另外一個wsdl,其輸入參數的wsdl爲(使用spring+cxf+aegis數據綁定生成):
- <xsd:complexType name="sayHello">
- <xsd:sequence>
- <xsd:element minOccurs="0" name="arg0" nillable="true" type="xsd:string"/>
- </xsd:sequence>
- </xsd:complexType>
大家可以看看對應關係。是否能看出點什麼規律。而對於複雜的對象,則可以層層的array進行嵌套。可以參開這個文章:PHP SOAP如何傳入複雜對象
最後提醒一下:
php使用webservice的時候,需要確認開啓了php_soap、php_curl的擴展(php.ini)
關於php的soap client 以及其option參數,可以參考官網:http://www.php.net/manual/en/soapclient.soapclient.php
使用php最好配置上xdebug以便可以調試,看到對象的值,相當明瞭,
SoapClient裏面有發送的request的xml一看就知道是否有問題,以及問題出在哪裏。
詳情可以看一下xdebug等文章。
再就是php調用的時候我增加了try,catch,並且將exception輸出,這樣輸出的結果似乎比直接跑異常要詳細。
因此建議大家遇到問題的時候可以catch一下,以便能夠看到詳細的異常信息。
另外如果有對spring發佈webservice以及spring+cxf發佈webservice有問題的也可以找相關文章看一下。這裏就不貼地址了。我也曾經介紹過cxf的。
總結一下,出錯的緣由,由於本次webservice使用了手寫xsd的方式,先手工寫出了xsd,然後再生成的接口和對象,因此導致location設置不兼容。
參考:
nusoap-how-to-change-content-type-of-request
分享一個xml在線格式化的工具:http://www.shell-tools.net/index.php?op=xml_format
文章轉自http://phpxiaoxin.iteye.com/blog/1555715