近期在搞一個項目,甲方只給了一個WSDL文件,讓我們實現響應接口。
於是研究了WSDL和SOAP通信相關知識,獲益甚深,把我的理解寫下來,希望對PHP新手有幫助。
1.基本概念
soap是簡單對象訪問協議的縮寫,是一種可以基於HTTP協議的訪問方式。客戶端發送請求,然后調用帶參數的服務端函數得到服務端的數據;服務端編寫處理函數並響應客戶端。
wsdl是網絡服務者動態語言的縮寫,這里面定義了雙方通信時包含的東西。客戶端把wsdl文件給服務端,服務端分析wsdl並寫出里面的函數,這樣兩者無論是什么平台、什么語言都可以通信。
我對soap的理解就是類似於POST請求的一種傳遞參數的方式,只是要求格式比POST更嚴格。
我認為wsdl也僅僅是一種xml標記文本,跟html文本沒有什么區別。
但是兩者結合起來就很頭疼了。
我是第一次接觸soap和wsdl,這兩者同時出現,我把他們倆混在一起搞不清。
參看了很多文檔才清楚這兩者到底是什么。
soap結合wsdl,其實就是把soap通信方式限制的更死,死到你服務端里只能按照wsdl里面規定的函數來寫,並嚴格限制參數和返回結果。
2.我走的彎路
一開始我拿到這個wsdl文檔,打開來看密密麻麻的,一共將近200行。
我沒搞清是什么東西,連里面的標記符都沒有搞懂,就開始在網上搜php與wsdl有關的博客。
搜到一個SoapDiscovery.class.php文件,說可以創建wsdl文件。
我就開始盲目的想創建wsdl文件,並使這個wsdl文件和甲方的一樣。
我錯誤的認為客戶端發送wsdl文件給我,我來解析里面是什么,然后我處理后再次封裝成wsdl文件發送給客戶端。
其實,這個wsdl根本就不用創建,它甚至都不是重點。
這個wsdl文件是雙方通信前就規定好的,雙方都按照這個wsdl里面的規定訪問函數或返回函數等內容。
所以重點不是解析、構建wsdl文件,而是根據這個wsdl寫好服務端的函數和返回值。
3.解決問題階段一
理解wsdl的幾個網站:http://staff.ustc.edu.cn/~shizhu/DotNet/WSDLxj.htm 【Web Service描述語言 WSDL 詳解】
https://www.ibm.com/developerworks/cn/education/webservices/ws-dewsdl/ws-dewsdl.html 【描述 Web 服務:WSDL】
認真看完上面兩個網站,基本就知道wsdl里面講的是什么了。
我在網上下載了很多關於php與wsdl的實例,
感覺這個例子很好:http://www.cnblogs.com/VipBin/archive/2011/12/07/2279927.html 【在PHP中利用wsdl創建標准webservice】
有了以上的基礎。
我開始理解wsdl工作原理了。
客戶端client就是發送請求的,服務端service就是接收請求的,再來一個class類處理函數(這里我叫它Robot類),就行了。
上面怎么寫客戶端與服務端用到了SoapClient,SoapServer兩個類。
怎么寫Robot類的函數用到了wsdl文件里規定的函數,也就是wsdl知識。
4.解決問題階段二
通過上面的例子,我可以成功運行。但已加上我Robot類就直接http 500錯誤,沒有任何提示信息,調試及其麻煩。
還好網上有兩款很好的軟件。
一款是讓wsdl文件直觀化,直接看到里面定義函數的規則,叫XMLSpy軟件。
另一款是SoapUI,不需要寫客戶端,可以直接加入wsdl文件,訪問服務端。
從這兩個軟件中,我認識到wsdl文件雖然很亂,但只有幾個是重點。
一個是wsdl里的soap:address,要定義好訪問的服務端的地址。
一個是wsdl里的portType的operation,每一個operation的name就是Robot類中必須寫的函數名,
最后一個是wsdl里operation的out,也就是每個函數的返回值的規定。
通過SoapUI,我可以清晰的看到客戶端發送和服務端返回的到底是什么內容。
其實里面沒有任何wsdl的標記,都是正規的soap標記,所以wsdl根本就沒有在通信過程用到,只在通信兩端用到。
我們就不需要管wsdl了,只要能實現里面的函數就行了。
5.解決問題階段三
到了這個階段,客戶端與服務端的函數訪問什么的都有了,唯一有問題的就是來回的參數格式問題。
這時不會報http500錯誤,而是200 OK,但頁面不會顯示任何東西。因為參數是有問題的。
先說怎么知道參數的格式的。
1 //下面兩個看懂,輸出就是你要寫的類 2 var_dump($client->__getFunctions());//打印暴露的方法 3 print("<br/>"); 4 var_dump($client->__getTypes());//打印對應方法的參數和參數類型 5 print("<br/>");
然后打印出來的參數是下面這樣的:
array(7) { [0]=> string(97) "struct standPointInfo { string POINTDES; string STANDPOINT; string STOR_TYPE; string WH_NO; }" [1]=> string(57) "struct webServiceResult { string INFO; string STATUS; }" [2]=> string(79) "struct taskResultInfo { string EXEC_STATE; string PICTURE; string TASK_NO; }" [3]=> string(265) "struct inventoryResultInfo { string CCDD; string CCLX; string CKH; string CW; string GC; string KCLX; double KCSL; string PC; double PDCY; string PDSJ; double PDSL; string RWH; string TSKCBH; string TSKCLX; string WLBH; string WZSFM; string ZHXM; }" [4]=> string(52) "struct standPointInfoArray { standPointInfo item; }" [5]=> string(62) "struct inventoryResultInfoArray { inventoryResultInfo item; }" [6]=> string(37) "struct Exception { string message; }" }
看到上面的參數格式,然后找到合適的php數據類型就行了。
里面有struct類型,但php沒有,不用怕,直接當array來用就行了,比如構建參數standPointInfoArray :
1 $standPointInfoArray = array( 2 array( 3 'POINTDES' => 'hujun', 4 'STANDPOINT' => 'standPoint', 5 'STOR_TYPE' => 'storType', 6 'WH_NO' => 'whNo1', 7 ), 8 array( 9 'POINTDES' => 'hujun', 10 'STANDPOINT' => 'standPoint', 11 'STOR_TYPE' => 'storType', 12 'WH_NO' => 'whNo2', 13 ), 14 );
這個參數在wsdl里定義為二維數組,所以我用php也構建了一個二維數組,鍵名不能改要與wsdl名字一模一樣而其必須加上,不然沒法傳參數。
然后把這個參數扔到客戶端的訪問函數參數里就行了,不用轉什么stdClass和json之類的,多余,直接用array就行了。
客戶端參數搞定了,下面看看服務端形參怎么搞。
下面是我得到的wsdl規定的函數:
array(3) { [0]=> string(76) "webServiceResult receiveStandPointInfo(standPointInfoArray $StandPointInfos)" [1]=> string(82) "webServiceResult receiveInventoryResult(inventoryResultInfoArray $InventoryResult)" [2]=> string(66) "webServiceResult receiveTaskResult(taskResultInfo $TaskResultInfo)" }
以receiveStandPointInfo函數為例。
在Robot類里必須寫這個函數,而且函數名必須一模一樣,不能有一點改變。
然后看形參是standPointInfoArray $StandPointInfos,這個standPointInfoArray類型在PHP很難自定義,但PHP的好處就是可以不用寫類型。
所以直接在形成里寫變量名就行了,把前面的類型去掉。
函數名如下:
public function receiveStandPointInfo($standPointInfoArray)
這樣形參就好了,$standPointInfoArray可以被客戶端賦值。
這個時候是最折磨人的,因為你沒法知道這個$standPointInfoArray是什么東西。服務端不給打印信息。
只能先猜測為數組,然后用數組的方式調用,直接報錯。
Fatal error: Cannot use object of type stdClass as array
上面講的很清楚,這是個stdClass的類型。
要是一維數組還好訪問,直接用$standPointInfo->POINTDES就可以訪問了。
但是剛才客戶端傳遞的是二維數組呀,這怎么訪問呢?
所以必須知道$standPointInfoArray到底是什么東西。
在網上找到了stdClass的打印信息示例如下:
stdClass Object ( [item] => Array ( [0] => stdClass Object ( [date] => 2008-07-17T01:23:06Z [directory] => 1 [downloadCount] => 0 ) [1] => stdClass Object ( [date] => 2009-11-03T23:03:15Z [directory] => 2 [downloadCount] => 5 ) ) )
這下明朗了,原理這個鍵名叫item,是Soap協議傳輸多個復雜類型規定的。
所以可以使用$standPointInfoArray->item[0]->data來訪問日期等等。
到這里,參數傳遞就完成了。
返回的參數構造與發送的參數一樣,這里就不寫了。
6.源代碼
客戶端的client.php
1 <?php 2 $client = new SoapClient("robot_origin.wsdl", array('trace'=>true)); 3 try { 4 $parms = array( 5 'EXEC_STATE' => "hujun", 6 'PICTURE' => 'pictures', 7 'TASK_NO' => 'task_nos', 8 ); 9 $result = $client->receiveTaskResult($parms); 10 var_dump($result); 11 print("<br/>=======================<br/>"); 12 13 $standPointInfo = array( 14 'POINTDES' => 'hujun', 15 'STANDPOINT' => 'standPoint', 16 'STOR_TYPE' => 'storType', 17 'WH_NO' => 'whNo', 18 ); 19 $standPointInfoArray = array( 20 array( 21 'POINTDES' => 'hujun', 22 'STANDPOINT' => 'standPoint', 23 'STOR_TYPE' => 'storType', 24 'WH_NO' => 'whNo1', 25 ), 26 array( 27 'POINTDES' => 'hujun', 28 'STANDPOINT' => 'standPoint', 29 'STOR_TYPE' => 'storType', 30 'WH_NO' => 'whNo2', 31 ), 32 ); 33 $result = $client->receiveStandPointInfo($standPointInfoArray); 34 var_dump($result); 35 print("<br/>=======================<br/>"); 36 37 //下面兩個看懂,輸出就是你要寫的類 38 var_dump($client->__getFunctions());//打印暴露的方法 39 print("<br/>"); 40 var_dump($client->__getTypes());//打印對應方法的參數和參數類型 41 print("<br/>"); 42 echo("\nDumping request headers:\n"); 43 var_dump($client->__getLastRequestHeaders()); 44 echo "<br>"; 45 echo("\nDumping request:\n"); 46 var_dump($client->__getLastRequest()); 47 echo "<br>"; 48 echo("\nDumping response headers:\n"); 49 var_dump($client->__getLastResponseHeaders()); 50 echo "<br>"; 51 echo("\nDumping response:\n"); 52 var_dump($client->__getLastResponse()); 53 } 54 catch (SoapFault $f){ 55 echo "Error Message: {$f->getMessage()}"; 56 } 57 ?>
服務端的service.php
1 <?php 2 include("robot.class.php"); 3 ini_set('soap.wsdl_cache_enabled','0'); //關閉WSDL緩存 4 $objSoapServer = new SoapServer("robot_origin.wsdl");//person.wsdl是剛創建的wsdl文件 5 6 $objSoapServer->setClass("Robot");//注冊person類的所有方法 7 $objSoapServer->handle();//處理請求
服務端處理類Robot.class.php
1 <?php 2 class Robot{ 3 public function receiveInventoryResult($inventoryResultInfoArray){ 4 $webServiceResult = array( 5 'INFO' => 'info', 6 'STATUS' => 'status', 7 ); 8 9 $webServiceResult['INFO'] = $inventoryResultInfoArray->item[0]->KCSL; 10 11 return $webServiceResult; 12 } 13 public function receiveStandPointInfo($standPointInfoArray){ 14 $webServiceResult = array( 15 'INFO' => 'info', 16 'STATUS' => 'status', 17 ); 18 if($standPointInfoArray->item[0]->POINTDES){ 19 $webServiceResult['INFO'] = 'we can change it'; 20 } 21 return $webServiceResult; 22 } 23 24 public function receiveTaskResult($taskResultInfo){ 25 $EXEC_STATE = $taskResultInfo->EXEC_STATE; 26 $PICTURE = $taskResultInfo->PICTURE; 27 $TASK_NO = $taskResultInfo->TASK_NO; 28 29 $webServiceResult = array( 30 'INFO' => 'info', 31 'STATUS' => $PICTURE, 32 ); 33 $webServiceResult['INFO'] = $EXEC_STATE; 34 return $webServiceResult; 35 } 36 37 public function Exception($exception){ 38 $this->exception = $exception; 39 return $this->exception; 40 } 41 }
一直是電腦在用的wsdl文件
<?xml version='1.0' encoding='utf-8'?><wsdl:definitions name="RobotWebServiceImplService" targetNamespace="http://robot.server.webService/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://robot.server.webService/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:types> <!------schema才是關鍵,注意類型的尋址,下面是xs開頭的,意思是這些類型到xmlns:xs=里面找------------> <xs:schema attributeFormDefault="unqualified" elementFormDefault="unqualified" targetNamespace="http://robot.server.webService/" xmlns:tns="http://robot.server.webService/" xmlns:xs="http://www.w3.org/2001/XMLSchema"> //------------------------------------------------------------------------------------------------------------ <!--設計一個復雜的數據類型standPointInfo--> <xs:complexType name="standPointInfo"> <!--sequence是順序限制--> <!--<minOccurs> 指示器可規定某個元素能夠出現的最小次數--> <xs:sequence> <xs:element minOccurs="0" name="POINTDES" type="xs:string"></xs:element> <xs:element minOccurs="0" name="STANDPOINT" type="xs:string"></xs:element> <xs:element minOccurs="0" name="STOR_TYPE" type="xs:string"></xs:element> <xs:element minOccurs="0" name="WH_NO" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> //-------------------------------1.1.1 output <xs:complexType name="webServiceResult"> <xs:sequence> <xs:element minOccurs="0" name="INFO" type="xs:string"></xs:element> <xs:element minOccurs="0" name="STATUS" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> //--------------------------------不需要 <xs:complexType name="taskResultInfo"> <xs:sequence> <xs:element minOccurs="0" name="EXEC_STATE" type="xs:string"></xs:element> <xs:element minOccurs="0" name="PICTURE" type="xs:string"></xs:element> <xs:element minOccurs="0" name="TASK_NO" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> //----------------------------------1.2.1 input <xs:complexType name="inventoryResultInfo"> <xs:sequence> <xs:element minOccurs="0" name="CCDD" type="xs:string"></xs:element> <xs:element minOccurs="0" name="CCLX" type="xs:string"></xs:element> <xs:element minOccurs="0" name="CKH" type="xs:string"></xs:element> <xs:element minOccurs="0" name="CW" type="xs:string"></xs:element> <xs:element minOccurs="0" name="GC" type="xs:string"></xs:element> <xs:element minOccurs="0" name="KCLX" type="xs:string"></xs:element> <xs:element minOccurs="0" name="KCSL" type="xs:double"></xs:element> <xs:element minOccurs="0" name="PC" type="xs:string"></xs:element> <xs:element minOccurs="0" name="PDCY" type="xs:double"></xs:element> <xs:element minOccurs="0" name="PDSJ" type="xs:string"></xs:element> <xs:element minOccurs="0" name="PDSL" type="xs:double"></xs:element> <xs:element minOccurs="0" name="RWH" type="xs:string"></xs:element> <xs:element minOccurs="0" name="TSKCBH" type="xs:string"></xs:element> <xs:element minOccurs="0" name="TSKCLX" type="xs:string"></xs:element> <xs:element minOccurs="0" name="WLBH" type="xs:string"></xs:element> <xs:element minOccurs="0" name="WZSFM" type="xs:string"></xs:element> <xs:element minOccurs="0" name="ZHXM" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> //------------------------------------arrary <xs:complexType final="#all" name="standPointInfoArray"> <xs:sequence> <xs:element maxOccurs="unbounded" minOccurs="0" name="item" nillable="true" type="tns:standPointInfo"></xs:element> </xs:sequence> </xs:complexType> //-------------------------------------arrary <xs:complexType final="#all" name="inventoryResultInfoArray"> <xs:sequence> <xs:element maxOccurs="unbounded" minOccurs="0" name="item" nillable="true" type="tns:inventoryResultInfo"></xs:element> </xs:sequence> </xs:complexType> //--------------------------------------異常 <xs:element name="Exception" type="tns:Exception"></xs:element> <xs:complexType name="Exception"> <xs:sequence> <xs:element minOccurs="0" name="message" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> </xs:schema> </wsdl:types> //---------------------------------------message是operation的參數 <!--<message> 元素將數據(數據類型在 <types> 元素中進行定義)分組成一個用於邏輯網絡傳輸的特征符,並將數據綁定到一個名稱上--> <!--上面的name用於operation引用,下面的name是聲明的實例--> <wsdl:message name="receiveInventoryResult"> <wsdl:part name="InventoryResult" type="tns:inventoryResultInfoArray"> </wsdl:part> </wsdl:message> <wsdl:message name="receiveTaskResult"> <wsdl:part name="TaskResultInfo" type="tns:taskResultInfo"> </wsdl:part> </wsdl:message> <wsdl:message name="receiveStandPointInfoResponse"> <wsdl:part name="return" type="tns:webServiceResult"> </wsdl:part> </wsdl:message> <wsdl:message name="receiveInventoryResultResponse"> <wsdl:part name="return" type="tns:webServiceResult"> </wsdl:part> </wsdl:message> <wsdl:message name="receiveTaskResultResponse"> <wsdl:part name="return" type="tns:webServiceResult"> </wsdl:part> </wsdl:message> <wsdl:message name="receiveStandPointInfo"> <wsdl:part name="StandPointInfos" type="tns:standPointInfoArray"> </wsdl:part> </wsdl:message> <wsdl:message name="Exception"> <wsdl:part element="tns:Exception" name="Exception"> </wsdl:part> </wsdl:message> //-------------------------------------------------------------- <!---相當於 Java 中的接口--> <!---它將對 <message> 元素的引用分組成邏輯操作,一個進程可以對另一個進程執行這些操作,並將它們綁定到一個名稱--> <wsdl:portType name="RobotWebService"> //--------------------------------------------------------------- <!---<input> 元素聲明客戶機向 Web 服務請求傳輸的需求。<output> 聲明 Web 服務響應的內容。<fault> 元素描述當 Web 服務設法響應客戶機的請求時所發生的任何消息級異常--> <wsdl:operation name="receiveStandPointInfo"> <wsdl:input message="tns:receiveStandPointInfo" name="receiveStandPointInfo"></wsdl:input> <wsdl:output message="tns:receiveStandPointInfoResponse" name="receiveStandPointInfoResponse"></wsdl:output> <wsdl:fault message="tns:Exception" name="Exception"></wsdl:fault> </wsdl:operation> //----------------------------- <wsdl:operation name="receiveTaskResult"> <wsdl:input message="tns:receiveTaskResult" name="receiveTaskResult"></wsdl:input> <wsdl:output message="tns:receiveTaskResultResponse" name="receiveTaskResultResponse"></wsdl:output> <wsdl:fault message="tns:Exception" name="Exception"></wsdl:fault> </wsdl:operation> //------------------------------ <wsdl:operation name="receiveInventoryResult"> <wsdl:input message="tns:receiveInventoryResult" name="receiveInventoryResult"> </wsdl:input> <wsdl:output message="tns:receiveInventoryResultResponse" name="receiveInventoryResultResponse"> </wsdl:output> <wsdl:fault message="tns:Exception" name="Exception"> </wsdl:fault> </wsdl:operation> </wsdl:portType> //---------------------------------------------------------------- <!--到這里,上面的定義什么的都是抽象的,binding將這些抽象的鈎接點與 Web 協議束縛起來--> <!--描述綁定的元素嵌套在 <binding> 元素的這些子元素之中,這是為了將消息傳遞協議的詳細內容鏈接到目標 portType 中所提到的通則上。--> <wsdl:binding name="RobotWebServiceImplServiceSoapBinding" type="tns:RobotWebService"> <!--創建 SOAP 綁定--> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"></soap:binding> <wsdl:operation name="receiveStandPointInfo"> <!--soapAction,在 SOAP 1.2 中,不贊成使用這個頭,而贊成將請求中的請求目的聲明為一個參數,稱作“action”。因而通常就讓這個頭空着。--> <soap:operation soapAction="" style="rpc"></soap:operation> <!--輸入值中,除了出錯數據應當包含在 SOAP <Fault> 元素中之外,其它所有消息都將包含在常規的 SOAP <Body> 元素中--> <wsdl:input name="receiveStandPointInfo"><soap:body namespace="http://robot.server.webService/" use="literal"></soap:body></wsdl:input> <wsdl:output name="receiveStandPointInfoResponse"><soap:body namespace="http://robot.server.webService/" use="literal"></soap:body></wsdl:output> <wsdl:fault name="Exception"><soap:fault name="Exception" use="literal"></soap:fault></wsdl:fault> </wsdl:operation> //--- <wsdl:operation name="receiveInventoryResult"> <soap:operation soapAction="" style="rpc"></soap:operation> <wsdl:input name="receiveInventoryResult"> <soap:body namespace="http://robot.server.webService/" use="literal"></soap:body> </wsdl:input> <wsdl:output name="receiveInventoryResultResponse"> <soap:body namespace="http://robot.server.webService/" use="literal"></soap:body> </wsdl:output> <wsdl:fault name="Exception"> <soap:fault name="Exception" use="literal"></soap:fault> </wsdl:fault> </wsdl:operation> //-- <!--literal 編碼的優點是沒有對正在傳輸的 XML 文檔作任何限制。它的編碼依賴於模式,因此是完全可擴展的。--> <wsdl:operation name="receiveTaskResult"> <soap:operation soapAction="" style="rpc"></soap:operation> <wsdl:input name="receiveTaskResult"> <soap:body namespace="http://robot.server.webService/" use="literal"></soap:body> </wsdl:input> <wsdl:output name="receiveTaskResultResponse"> <soap:body namespace="http://robot.server.webService/" use="literal"></soap:body> </wsdl:output> <wsdl:fault name="Exception"> <soap:fault name="Exception" use="literal"></soap:fault> </wsdl:fault> </wsdl:operation> //-- </wsdl:binding> //----------------------------------------------------------服務器地址 <!--將某個具體的綁定與網絡上的一個或多個進程相關聯,這些進程可以根據綁定所實現的 portType 來處理請求--> <!--文檔樣式的消息傳遞表示 SOAP <Body> 元素的內容是任意的 XML 文檔。--> <!--盡管可以在請求-響應類型的通信方案中使用文檔樣式的消息傳遞,但是在異步通信中使用它非常理想,因為這個自包含的 XML 文檔可以放入隊列等待處理。--> <wsdl:service name="RobotWebServiceImplService"> <wsdl:port binding="tns:RobotWebServiceImplServiceSoapBinding" name="RobotWebServiceImplPort"> <soap:address location="http://soap.cn/service.php"></soap:address> </wsdl:port> </wsdl:service> </wsdl:definitions>
==
參閱的網站:
SOAP-ERROR: Parsing WSDL: Couldn't load from - but works on WAMP
php soap連接https的wsdl報錯SOAP-ERROR: Parsing WSDL:Couldn't load from
Soap data gives Fatal error: Cannot use object of type stdClass as array in